Passed
Push — master ( 8dc8d6...0b0f56 )
by Julito
10:41
created

learnpathItem::close()   B

Complexity

Conditions 8
Paths 36

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 20
nc 36
nop 0
dl 0
loc 34
rs 8.4444
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Framework\Container;
5
6
/**
7
 * Class learnpathItem
8
 * lp_item defines items belonging to a learnpath. Each item has a name,
9
 * a score, a use time and additional information that enables tracking a user's
10
 * progress in a learning path.
11
 *
12
 * @author  Yannick Warnier <[email protected]>
13
 */
14
class learnpathItem
15
{
16
    public const DEBUG = 0; // Logging parameter.
17
    public $iId;
18
    public $attempt_id; // Also called "objectives" SCORM-wise.
19
    public $audio; // The path to an audio file (stored in document/audio/).
20
    public $children = []; // Contains the ids of children items.
21
    public $condition; // If this item has a special condition embedded.
22
    public $current_score;
23
    public $current_start_time;
24
    public $current_stop_time;
25
    public $current_data = '';
26
    public $db_id;
27
    public $db_item_view_id = '';
28
    public $description = '';
29
    public $file;
30
    /**
31
     * At the moment, interactions are just an array of arrays with a structure
32
     * of 8 text fields: id(0), type(1), time(2), weighting(3),
33
     * correct_responses(4), student_response(5), result(6), latency(7).
34
     */
35
    public $interactions = [];
36
    public $interactions_count = 0;
37
    public $objectives = [];
38
    public $objectives_count = 0;
39
    public $launch_data = '';
40
    public $lesson_location = '';
41
    public $level = 0;
42
    public $core_exit = '';
43
    public $lp_id;
44
    public $max_score;
45
    public $mastery_score;
46
    public $min_score;
47
    public $max_time_allowed = '';
48
    public $name;
49
    public $next;
50
    public $parent;
51
    public $path; // In some cases the exo_id = exercise_id in courseDb exercices table.
52
    public $possible_status = [
53
        'not attempted',
54
        'incomplete',
55
        'completed',
56
        'passed',
57
        'failed',
58
        'browsed',
59
    ];
60
    public $prereq_string = '';
61
    public $prereq_alert = '';
62
    public $prereqs = [];
63
    public $previous;
64
    public $prevent_reinit = 1; // 0 =  multiple attempts   1 = one attempt
65
    public $seriousgame_mode;
66
    public $ref;
67
    public $save_on_close = true;
68
    public $search_did = null;
69
    public $status;
70
    public $title;
71
    /**
72
     * Type attribute can contain one of
73
     * link|student_publication|dir|quiz|document|forum|thread.
74
     */
75
    public $type;
76
    public $view_id;
77
    public $oldTotalTime;
78
    public $view_max_score;
79
    public $courseInfo;
80
    public $courseId;
81
    //var used if absolute session time mode is used
82
    private $last_scorm_session_time = 0;
83
    private $prerequisiteMaxScore;
84
    private $prerequisiteMinScore;
85
86
    /**
87
     * Prepares the learning path item for later launch.
88
     * Don't forget to use set_lp_view() if applicable after creating the item.
89
     * Setting an lp_view will finalise the item_view data collection.
90
     *
91
     * @param int        $id           Learning path item ID
92
     * @param int        $user_id      User ID
93
     * @param int        $courseId     Course int id
94
     * @param array|null $item_content An array with the contents of the item
95
     */
96
    public function __construct(
97
        $id,
98
        $user_id = 0,
99
        $courseId = 0,
100
        $item_content = null
101
    ) {
102
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
103
        $id = (int) $id;
104
        $this->courseId = $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
105
        $this->courseInfo = api_get_course_info_by_id($this->courseId);
106
107
        if (empty($item_content)) {
108
            $sql = "SELECT * FROM $items_table
109
                    WHERE iid = $id";
110
            $res = Database::query($sql);
111
            if (Database::num_rows($res) < 1) {
112
                $this->error = 'Could not find given learnpath item in learnpath_item table';
113
            }
114
            $row = Database::fetch_array($res);
115
        } else {
116
            $row = $item_content;
117
        }
118
119
        $this->lp_id = $row['lp_id'];
120
        $this->iId = $row['iid'];
121
        $this->max_score = $row['max_score'];
122
        $this->min_score = $row['min_score'];
123
        $this->name = $row['title'];
124
        $this->type = $row['item_type'];
125
        $this->ref = $row['ref'];
126
        $this->title = $row['title'];
127
        $this->description = $row['description'];
128
        $this->path = $row['path'];
129
        $this->mastery_score = $row['mastery_score'];
130
        $this->parent = $row['parent_item_id'];
131
        $this->next = $row['next_item_id'];
132
        $this->previous = $row['previous_item_id'];
133
        $this->display_order = $row['display_order'];
134
        $this->prereq_string = $row['prerequisite'];
135
        $this->max_time_allowed = $row['max_time_allowed'];
136
        $this->setPrerequisiteMaxScore($row['prerequisite_max_score']);
137
        $this->setPrerequisiteMinScore($row['prerequisite_min_score']);
138
        $this->oldTotalTime = 0;
139
        $this->view_max_score = 0;
140
        $this->seriousgame_mode = 0;
141
        $this->audio = self::fixAudio($row['audio']);
142
        $this->launch_data = $row['launch_data'];
143
        $this->save_on_close = true;
144
        $this->db_id = $id;
145
146
        // Load children list
147
        if (!empty($this->lp_id)) {
148
            $sql = "SELECT iid FROM $items_table
149
                    WHERE
150
                        c_id = $courseId AND
151
                        lp_id = ".$this->lp_id." AND
152
                        parent_item_id = $id";
153
            $res = Database::query($sql);
154
            if (Database::num_rows($res) > 0) {
155
                while ($row = Database::fetch_assoc($res)) {
156
                    $this->children[] = $row['iid'];
157
                }
158
            }
159
160
            // Get search_did.
161
            if ('true' == api_get_setting('search_enabled')) {
162
                $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
163
                $sql = 'SELECT *
164
                        FROM %s
165
                        WHERE
166
                            course_code=\'%s\' AND
167
                            tool_id=\'%s\' AND
168
                            ref_id_high_level=%s AND
169
                            ref_id_second_level=%d
170
                        LIMIT 1';
171
                // TODO: Verify if it's possible to assume the actual course instead
172
                // of getting it from db.
173
                $sql = sprintf(
174
                    $sql,
175
                    $tbl_se_ref,
176
                    api_get_course_id(),
177
                    TOOL_LEARNPATH,
178
                    $this->lp_id,
179
                    $id
180
                );
181
                $res = Database::query($sql);
182
                if (Database::num_rows($res) > 0) {
183
                    $se_ref = Database::fetch_array($res);
184
                    $this->search_did = (int) $se_ref['search_did'];
185
                }
186
            }
187
        }
188
    }
189
190
    public static function fixAudio($audio)
191
    {
192
        $courseInfo = api_get_course_info();
193
        // Do not check in DB as we expect the call to come from the
194
        if (empty($audio) || empty($courseInfo)) {
195
            return '';
196
        }
197
        // learnpath class which should be aware of any fake.
198
        // Old structure
199
        $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
200
        if (file_exists($file)) {
201
            $audio = '/audio/'.$audio;
202
            $audio = str_replace('//', '/', $audio);
203
204
            return $audio;
205
        }
206
207
        $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
208
209
        if (file_exists($file)) {
210
            return $audio;
211
        }
212
213
        return '';
214
    }
215
216
    /**
217
     * Adds an interaction to the current item.
218
     *
219
     * @param int   $index  Index (order ID) of the interaction inside this item
220
     * @param array $params Array of parameters:
221
     *                      id(0), type(1), time(2), weighting(3), correct_responses(4),
222
     *                      student_response(5), result(6), latency(7)
223
     */
224
    public function add_interaction($index, $params)
225
    {
226
        $this->interactions[$index] = $params;
227
        // Take the current maximum index to generate the interactions_count.
228
        if (($index + 1) > $this->interactions_count) {
229
            $this->interactions_count = $index + 1;
230
        }
231
    }
232
233
    /**
234
     * Adds an objective to the current item.
235
     *
236
     * @param    array    Array of parameters:
237
     * id(0), status(1), score_raw(2), score_max(3), score_min(4)
238
     */
239
    public function add_objective($index, $params)
240
    {
241
        if (empty($params[0])) {
242
            return null;
243
        }
244
        $this->objectives[$index] = $params;
245
        // Take the current maximum index to generate the objectives_count.
246
        if ((count($this->objectives) + 1) > $this->objectives_count) {
247
            $this->objectives_count = (count($this->objectives) + 1);
248
        }
249
    }
250
251
    /**
252
     * Closes/stops the item viewing. Finalises runtime values.
253
     * If required, save to DB.
254
     *
255
     * @param bool $prerequisitesCheck Needed to check if asset can be set as completed or not
256
     *
257
     * @return bool True on success, false otherwise
258
     */
259
    public function close()
260
    {
261
        $debug = self::DEBUG;
262
        $this->current_stop_time = time();
263
        $type = $this->get_type();
264
        if ($debug) {
265
            error_log('Start - learnpathItem:close');
266
            error_log("Type: ".$type);
267
            error_log("get_id: ".$this->get_id());
268
        }
269
        if ($type !== 'sco') {
270
            if ($type == TOOL_QUIZ || $type == TOOL_HOTPOTATOES) {
271
                $this->get_status(
272
                    true,
273
                    true
274
                ); // Update status (second option forces the update).
275
            } else {
276
                /*$this->status = $this->possible_status[2];
277
                if (self::DEBUG) {
278
                    error_log("STATUS changed to: ".$this->status);
279
                }*/
280
            }
281
        }
282
        if ($this->save_on_close) {
283
            if ($debug) {
284
                error_log('save_on_close');
285
            }
286
            $this->save();
287
        }
288
289
        if ($debug) {
290
            error_log('End - learnpathItem:close');
291
        }
292
        return true;
293
    }
294
295
    /**
296
     * Deletes all traces of this item in the database.
297
     *
298
     * @return bool true. Doesn't check for errors yet.
299
     */
300
    public function delete()
301
    {
302
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
303
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
304
        $courseId = $this->courseId;
305
306
        $sql = "DELETE FROM $lp_item_view
307
                WHERE c_id = $courseId AND lp_item_id = ".$this->db_id;
308
        Database::query($sql);
309
310
        $sql = "SELECT * FROM $lp_item
311
                WHERE iid = ".$this->db_id;
312
        $res_sel = Database::query($sql);
313
        if (Database::num_rows($res_sel) < 1) {
314
            return false;
315
        }
316
317
        $sql = "DELETE FROM $lp_item
318
                WHERE iid = ".$this->db_id;
319
        Database::query($sql);
320
321
        if ('true' == api_get_setting('search_enabled')) {
322
            if (!is_null($this->search_did)) {
323
                $di = new ChamiloIndexer();
324
                $di->remove_document($this->search_did);
325
            }
326
        }
327
328
        return true;
329
    }
330
331
    /**
332
     * Gets the current attempt_id for this user on this item.
333
     *
334
     * @return int attempt_id for this item view by this user or 1 if none defined
335
     */
336
    public function get_attempt_id()
337
    {
338
        $res = 1;
339
        if (!empty($this->attempt_id)) {
340
            $res = (int) $this->attempt_id;
341
        }
342
343
        return $res;
344
    }
345
346
    /**
347
     * Gets a list of the item's children.
348
     *
349
     * @return array Array of children items IDs
350
     */
351
    public function get_children()
352
    {
353
        $list = [];
354
        foreach ($this->children as $child) {
355
            if (!empty($child)) {
356
                $list[] = $child;
357
            }
358
        }
359
360
        return $list;
361
    }
362
363
    /**
364
     * Gets the core_exit value from the database.
365
     */
366
    public function get_core_exit()
367
    {
368
        return $this->core_exit;
369
    }
370
371
    /**
372
     * Gets the credit information (rather scorm-stuff) based on current status
373
     * and reinit autorization. Credit tells the sco(content) if Chamilo will
374
     * record the data it is sent (credit) or not (no-credit).
375
     *
376
     * @return string 'credit' or 'no-credit'. Defaults to 'credit'
377
     *                Because if we don't know enough about this item, it's probably because
378
     *                it was never used before.
379
     */
380
    public function get_credit()
381
    {
382
        if (self::DEBUG > 1) {
383
            error_log('learnpathItem::get_credit()', 0);
384
        }
385
        $credit = 'credit';
386
        // Now check the value of prevent_reinit (if it's 0, return credit as
387
        // the default was).
388
        // If prevent_reinit == 1 (or more).
389
        if (0 != $this->get_prevent_reinit()) {
390
            // If status is not attempted or incomplete, credit anyway.
391
            // Otherwise:
392
            // Check the status in the database rather than in the object, as
393
            // checking in the object would always return "no-credit" when we
394
            // want to set it to completed.
395
            $status = $this->get_status(true);
396
            if (self::DEBUG > 2) {
397
                error_log(
398
                    'learnpathItem::get_credit() - get_prevent_reinit!=0 and '.
399
                    'status is '.$status,
400
                    0
401
                );
402
            }
403
            //0=not attempted - 1 = incomplete
404
            if ($status != $this->possible_status[0] &&
405
                $status != $this->possible_status[1]
406
            ) {
407
                $credit = 'no-credit';
408
            }
409
        }
410
        if (self::DEBUG > 1) {
411
            error_log("learnpathItem::get_credit() returns: $credit");
412
        }
413
414
        return $credit;
415
    }
416
417
    /**
418
     * Gets the current start time property.
419
     *
420
     * @return int Current start time, or current time if none
421
     */
422
    public function get_current_start_time()
423
    {
424
        if (empty($this->current_start_time)) {
425
            return time();
426
        }
427
428
        return $this->current_start_time;
429
    }
430
431
    /**
432
     * Gets the item's description.
433
     *
434
     * @return string Description
435
     */
436
    public function get_description()
437
    {
438
        if (empty($this->description)) {
439
            return '';
440
        }
441
442
        return $this->description;
443
    }
444
445
    /**
446
     * Gets the file path from the course's root directory, no matter what
447
     * tool it is from.
448
     *
449
     * @param string $path_to_scorm_dir
450
     *
451
     * @return string The file path, or an empty string if there is no file
452
     *                attached, or '-1' if the file must be replaced by an error page
453
     */
454
    public function get_file_path($path_to_scorm_dir = '')
455
    {
456
        $courseId = $this->courseId;
457
        $path = $this->get_path();
458
        $type = $this->get_type();
459
460
        if (empty($path)) {
461
            if ('dir' == $type) {
462
                return '';
463
            } else {
464
                return '-1';
465
            }
466
        } elseif ($path == strval(intval($path))) {
467
            // The path is numeric, so it is a reference to a Chamilo object.
468
            switch ($type) {
469
                case 'dir':
470
                    return '';
471
                case TOOL_DOCUMENT:
472
                    $table_doc = Database::get_course_table(TABLE_DOCUMENT);
473
                    $sql = 'SELECT path
474
                            FROM '.$table_doc.'
475
                            WHERE
476
                                c_id = '.$courseId.' AND
477
                                iid = '.$path;
478
                    $res = Database::query($sql);
479
                    $row = Database::fetch_array($res);
480
                    $real_path = 'document'.$row['path'];
481
482
                    return $real_path;
483
                case TOOL_STUDENTPUBLICATION:
484
                case TOOL_QUIZ:
485
                case TOOL_FORUM:
486
                case TOOL_THREAD:
487
                case TOOL_LINK:
488
                default:
489
                    return '-1';
490
            }
491
        } else {
492
            if (!empty($path_to_scorm_dir)) {
493
                $path = $path_to_scorm_dir.$path;
494
            }
495
496
            return $path;
497
        }
498
    }
499
500
    /**
501
     * Gets the DB ID.
502
     *
503
     * @return int Database ID for the current item
504
     */
505
    public function get_id()
506
    {
507
        if (!empty($this->db_id)) {
508
            return $this->db_id;
509
        }
510
        // TODO: Check this return value is valid for children classes (SCORM?).
511
        return 0;
512
    }
513
514
    /**
515
     * Loads the interactions into the item object, from the database.
516
     * If object interactions exist, they will be overwritten by this function,
517
     * using the database elements only.
518
     */
519
    public function load_interactions()
520
    {
521
        $this->interactions = [];
522
        $courseId = $this->courseId;
523
        $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
524
        $sql = "SELECT id FROM $tbl
525
                WHERE
526
                    c_id = $courseId AND
527
                    lp_item_id = ".$this->db_id." AND
528
                    lp_view_id = ".$this->view_id." AND
529
                    view_count = ".$this->get_view_count();
530
        $res = Database::query($sql);
531
        if (Database::num_rows($res) > 0) {
532
            $row = Database::fetch_array($res);
533
            $lp_iv_id = $row[0];
534
            $iva_table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
535
            $sql = "SELECT * FROM $iva_table
536
                    WHERE c_id = $courseId AND lp_iv_id = $lp_iv_id ";
537
            $res_sql = Database::query($sql);
538
            while ($row = Database::fetch_array($res_sql)) {
539
                $this->interactions[$row['interaction_id']] = [
540
                    $row['interaction_id'],
541
                    $row['interaction_type'],
542
                    $row['weighting'],
543
                    $row['completion_time'],
544
                    $row['correct_responses'],
545
                    $row['student_responses'],
546
                    $row['result'],
547
                    $row['latency'],
548
                ];
549
            }
550
        }
551
    }
552
553
    /**
554
     * Gets the current count of interactions recorded in the database.
555
     *
556
     * @param bool $checkdb Whether to count from database or not (defaults to no)
557
     *
558
     * @return int The current number of interactions recorder
559
     */
560
    public function get_interactions_count($checkdb = false)
561
    {
562
        $return = 0;
563
        if (api_is_invitee()) {
564
            // If the user is an invitee, we consider there's no interaction
565
            return 0;
566
        }
567
        $courseId = $this->courseId;
568
569
        if ($checkdb) {
570
            $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
571
            $sql = "SELECT iid FROM $tbl
572
                    WHERE
573
                        c_id = $courseId AND
574
                        lp_item_id = ".$this->db_id." AND
575
                        lp_view_id = ".$this->view_id." AND
576
                        view_count = ".$this->get_attempt_id();
577
            $res = Database::query($sql);
578
            if (Database::num_rows($res) > 0) {
579
                $row = Database::fetch_array($res);
580
                $lp_iv_id = $row[0];
581
                $iva_table = Database::get_course_table(
582
                    TABLE_LP_IV_INTERACTION
583
                );
584
                $sql = "SELECT count(id) as mycount
585
                        FROM $iva_table
586
                        WHERE c_id = $courseId AND lp_iv_id = $lp_iv_id ";
587
                $res_sql = Database::query($sql);
588
                if (Database::num_rows($res_sql) > 0) {
589
                    $row = Database::fetch_array($res_sql);
590
                    $return = $row['mycount'];
591
                }
592
            }
593
        } else {
594
            if (!empty($this->interactions_count)) {
595
                $return = $this->interactions_count;
596
            }
597
        }
598
599
        return $return;
600
    }
601
602
    /**
603
     * Gets the JavaScript array content to fill the interactions array.
604
     *
605
     * @param bool $checkdb Whether to check directly into the database (default no)
606
     *
607
     * @return string An empty string if no interaction, a JS array definition otherwise
608
     */
609
    public function get_interactions_js_array($checkdb = false)
610
    {
611
        $return = '';
612
        if ($checkdb) {
613
            $this->load_interactions(true);
614
        }
615
        foreach ($this->interactions as $id => $in) {
616
            $return .= "[
617
                '$id',
618
                '".$in[1]."',
619
                '".$in[2]."',
620
                '".$in[3]."',
621
                '".$in[4]."',
622
                '".$in[5]."',
623
                '".$in[6]."',
624
                '".$in[7]."'],";
625
        }
626
        if (!empty($return)) {
627
            $return = substr($return, 0, -1);
628
        }
629
630
        return $return;
631
    }
632
633
    /**
634
     * Gets the current count of objectives recorded in the database.
635
     *
636
     * @return int The current number of objectives recorder
637
     */
638
    public function get_objectives_count()
639
    {
640
        $res = 0;
641
        if (!empty($this->objectives_count)) {
642
            $res = $this->objectives_count;
643
        }
644
645
        return $res;
646
    }
647
648
    /**
649
     * Gets the launch_data field found in imsmanifests (this is SCORM- or
650
     * AICC-related, really).
651
     *
652
     * @return string Launch data as found in imsmanifest and stored in
653
     *                Chamilo (read only). Defaults to ''.
654
     */
655
    public function get_launch_data()
656
    {
657
        if (!empty($this->launch_data)) {
658
            return str_replace(
659
                ["\r", "\n", "'"],
660
                ['\r', '\n', "\\'"],
661
                $this->launch_data
662
            );
663
        }
664
665
        return '';
666
    }
667
668
    /**
669
     * Gets the lesson location.
670
     *
671
     * @return string lesson location as recorded by the SCORM and AICC
672
     *                elements. Defaults to ''
673
     */
674
    public function get_lesson_location()
675
    {
676
        if (!empty($this->lesson_location)) {
677
            return str_replace(
678
                ["\r", "\n", "'"],
679
                ['\r', '\n', "\\'"],
680
                $this->lesson_location
681
            );
682
        }
683
684
        return '';
685
    }
686
687
    /**
688
     * Gets the lesson_mode (scorm feature, but might be used by aicc as well
689
     * as chamilo paths).
690
     *
691
     * The "browse" mode is not supported yet (because there is no such way of
692
     * seeing a sco in Chamilo)
693
     *
694
     * @return string 'browse','normal' or 'review'. Defaults to 'normal'
695
     */
696
    public function get_lesson_mode()
697
    {
698
        $mode = 'normal';
699
        if (0 != $this->get_prevent_reinit()) {
700
            // If prevent_reinit == 0
701
            $my_status = $this->get_status();
702
            if ($my_status != $this->possible_status[0] && $my_status != $this->possible_status[1]) {
703
                $mode = 'review';
704
            }
705
        }
706
707
        return $mode;
708
    }
709
710
    /**
711
     * Gets the depth level.
712
     *
713
     * @return int Level. Defaults to 0
714
     */
715
    public function get_level()
716
    {
717
        if (empty($this->level)) {
718
            return 0;
719
        }
720
721
        return $this->level;
722
    }
723
724
    /**
725
     * Gets the mastery score.
726
     */
727
    public function get_mastery_score()
728
    {
729
        if (isset($this->mastery_score)) {
730
            return $this->mastery_score;
731
        }
732
733
        return -1;
734
    }
735
736
    /**
737
     * Gets the maximum (score).
738
     *
739
     * @return int Maximum score. Defaults to 100 if nothing else is defined
740
     */
741
    public function get_max()
742
    {
743
        if ('sco' === $this->type) {
744
            if (!empty($this->view_max_score) && $this->view_max_score > 0) {
745
                return $this->view_max_score;
746
            } else {
747
                if (!empty($this->max_score)) {
748
                    return $this->max_score;
749
                }
750
751
                return 100;
752
            }
753
        } else {
754
            if (!empty($this->max_score)) {
755
                return $this->max_score;
756
            }
757
758
            return 100;
759
        }
760
    }
761
762
    /**
763
     * Gets the maximum time allowed for this user in this attempt on this item.
764
     *
765
     * @return string Time string in SCORM format
766
     *                (HH:MM:SS or HH:MM:SS.SS or HHHH:MM:SS.SS)
767
     */
768
    public function get_max_time_allowed()
769
    {
770
        if (!empty($this->max_time_allowed)) {
771
            return $this->max_time_allowed;
772
        }
773
774
        return '';
775
    }
776
777
    /**
778
     * Gets the minimum (score).
779
     *
780
     * @return int Minimum score. Defaults to 0
781
     */
782
    public function get_min()
783
    {
784
        if (!empty($this->min_score)) {
785
            return $this->min_score;
786
        }
787
788
        return 0;
789
    }
790
791
    /**
792
     * Gets the parent ID.
793
     *
794
     * @return int Parent ID. Defaults to null
795
     */
796
    public function get_parent()
797
    {
798
        if (!empty($this->parent)) {
799
            return $this->parent;
800
        }
801
        // TODO: Check this return value is valid for children classes (SCORM?).
802
        return null;
803
    }
804
805
    /**
806
     * Gets the path attribute.
807
     *
808
     * @return string Path. Defaults to ''
809
     */
810
    public function get_path()
811
    {
812
        if (empty($this->path)) {
813
            return '';
814
        }
815
816
        return $this->path;
817
    }
818
819
    /**
820
     * Gets the prerequisites string.
821
     *
822
     * @return string empty string or prerequisites string if defined
823
     */
824
    public function get_prereq_string()
825
    {
826
        if (!empty($this->prereq_string)) {
827
            return $this->prereq_string;
828
        }
829
830
        return '';
831
    }
832
833
    /**
834
     * Gets the prevent_reinit attribute value (and sets it if not set already).
835
     *
836
     * @return int 1 or 0 (defaults to 1)
837
     */
838
    public function get_prevent_reinit()
839
    {
840
        if (self::DEBUG > 2) {
841
            error_log('learnpathItem::get_prevent_reinit()', 0);
842
        }
843
        if (!isset($this->prevent_reinit)) {
844
            if (!empty($this->lp_id)) {
845
                $table = Database::get_course_table(TABLE_LP_MAIN);
846
                $sql = "SELECT prevent_reinit
847
                        FROM $table
848
                        WHERE iid = ".$this->lp_id;
849
                $res = Database::query($sql);
850
                if (Database::num_rows($res) < 1) {
851
                    $this->error = "Could not find parent learnpath in lp table";
852
                    if (self::DEBUG > 2) {
853
                        error_log(
854
                            'New LP - End of learnpathItem::get_prevent_reinit() - Returning false',
855
                            0
856
                        );
857
                    }
858
859
                    return false;
860
                } else {
861
                    $row = Database::fetch_array($res);
862
                    $this->prevent_reinit = $row['prevent_reinit'];
863
                }
864
            } else {
865
                // Prevent reinit is always 1 by default - see learnpath.class.php
866
                $this->prevent_reinit = 1;
867
            }
868
        }
869
        if (self::DEBUG > 2) {
870
            error_log('End of learnpathItem::get_prevent_reinit() - Returned '.$this->prevent_reinit);
871
        }
872
873
        return $this->prevent_reinit;
874
    }
875
876
    /**
877
     * Returns 1 if seriousgame_mode is activated, 0 otherwise.
878
     *
879
     * @return int (0 or 1)
880
     *
881
     * @deprecated seriousgame_mode seems not to be used
882
     *
883
     * @author ndiechburg <[email protected]>
884
     */
885
    public function get_seriousgame_mode()
886
    {
887
        if (!isset($this->seriousgame_mode)) {
888
            if (!empty($this->lp_id)) {
889
                $table = Database::get_course_table(TABLE_LP_MAIN);
890
                $sql = "SELECT seriousgame_mode
891
                        FROM $table
892
                        WHERE iid = ".$this->lp_id;
893
                $res = Database::query($sql);
894
                if (Database::num_rows($res) < 1) {
895
                    $this->error = 'Could not find parent learnpath in learnpath table';
896
897
                    return false;
898
                } else {
899
                    $row = Database::fetch_array($res);
900
                    $this->seriousgame_mode = isset($row['seriousgame_mode']) ? $row['seriousgame_mode'] : 0;
901
                }
902
            } else {
903
                $this->seriousgame_mode = 0; //SeriousGame mode is always off by default
904
            }
905
        }
906
907
        return $this->seriousgame_mode;
908
    }
909
910
    /**
911
     * Gets the item's reference column.
912
     *
913
     * @return string The item's reference field (generally used for SCORM identifiers)
914
     */
915
    public function get_ref()
916
    {
917
        return $this->ref;
918
    }
919
920
    /**
921
     * Gets the list of included resources as a list of absolute or relative
922
     * paths of resources included in the current item. This allows for a
923
     * better SCORM export. The list will generally include pictures, flash
924
     * objects, java applets, or any other stuff included in the source of the
925
     * current item. The current item is expected to be an HTML file. If it
926
     * is not, then the function will return and empty list.
927
     *
928
     * @param string $type        (one of the Chamilo tools) - optional (otherwise takes the current item's type)
929
     * @param string $abs_path    absolute file path - optional (otherwise takes the current item's path)
930
     * @param int    $recursivity level of recursivity we're in
931
     *
932
     * @return array List of file paths.
933
     *               An additional field containing 'local' or 'remote' helps determine if
934
     *               the file should be copied into the zip or just linked
935
     */
936
    public function get_resources_from_source(
937
        $type = null,
938
        $abs_path = null,
939
        $recursivity = 1
940
    ) {
941
        $max = 5;
942
        if ($recursivity > $max) {
943
            return [];
944
        }
945
946
        $type = empty($type) ? $this->get_type() : $type;
947
948
        if (!isset($abs_path)) {
949
            $path = $this->get_file_path();
950
            $abs_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.$path;
951
        }
952
953
        $files_list = [];
954
        switch ($type) {
955
            case TOOL_DOCUMENT:
956
            case TOOL_QUIZ:
957
            case 'sco':
958
                // Get the document and, if HTML, open it.
959
                if (!is_file($abs_path)) {
960
                    // The file could not be found.
961
                    return false;
962
                }
963
964
                // for now, read the whole file in one go (that's gonna be
965
                // a problem when the file is too big).
966
                $info = pathinfo($abs_path);
967
                $ext = $info['extension'];
968
969
                switch (strtolower($ext)) {
970
                    case 'html':
971
                    case 'htm':
972
                    case 'shtml':
973
                    case 'css':
974
                        $wantedAttributes = [
975
                            'src',
976
                            'url',
977
                            '@import',
978
                            'href',
979
                            'value',
980
                        ];
981
982
                        // Parse it for included resources.
983
                        $fileContent = file_get_contents($abs_path);
984
                        // Get an array of attributes from the HTML source.
985
                        $attributes = DocumentManager::parse_HTML_attributes(
986
                            $fileContent,
987
                            $wantedAttributes
988
                        );
989
990
                        // Look at 'src' attributes in this file
991
                        foreach ($wantedAttributes as $attr) {
992
                            if (isset($attributes[$attr])) {
993
                                // Find which kind of path these are (local or remote).
994
                                $sources = $attributes[$attr];
995
996
                                foreach ($sources as $source) {
997
                                    // Skip what is obviously not a resource.
998
                                    if (strpos($source, "+this.")) {
999
                                        continue;
1000
                                    } // javascript code - will still work unaltered.
1001
                                    if (false === strpos($source, '.')) {
1002
                                        continue;
1003
                                    } // No dot, should not be an external file anyway.
1004
                                    if (strpos($source, 'mailto:')) {
1005
                                        continue;
1006
                                    } // mailto link.
1007
                                    if (strpos($source, ';') &&
1008
                                        !strpos($source, '&amp;')
1009
                                    ) {
1010
                                        continue;
1011
                                    } // Avoid code - that should help.
1012
1013
                                    if ('value' == $attr) {
1014
                                        if (strpos($source, 'mp3file')) {
1015
                                            $files_list[] = [
1016
                                                substr(
1017
                                                    $source,
1018
                                                    0,
1019
                                                    strpos(
1020
                                                        $source,
1021
                                                        '.swf'
1022
                                                    ) + 4
1023
                                                ),
1024
                                                'local',
1025
                                                'abs',
1026
                                            ];
1027
                                            $mp3file = substr(
1028
                                                $source,
1029
                                                strpos(
1030
                                                    $source,
1031
                                                    'mp3file='
1032
                                                ) + 8
1033
                                            );
1034
                                            if ('/' == substr($mp3file, 0, 1)) {
1035
                                                $files_list[] = [
1036
                                                    $mp3file,
1037
                                                    'local',
1038
                                                    'abs',
1039
                                                ];
1040
                                            } else {
1041
                                                $files_list[] = [
1042
                                                    $mp3file,
1043
                                                    'local',
1044
                                                    'rel',
1045
                                                ];
1046
                                            }
1047
                                        } elseif (0 === strpos($source, 'flv=')) {
1048
                                            $source = substr($source, 4);
1049
                                            if (strpos($source, '&') > 0) {
1050
                                                $source = substr(
1051
                                                    $source,
1052
                                                    0,
1053
                                                    strpos($source, '&')
1054
                                                );
1055
                                            }
1056
                                            if (strpos($source, '://') > 0) {
1057
                                                if (false !== strpos($source, api_get_path(WEB_PATH))) {
1058
                                                    // We found the current portal url.
1059
                                                    $files_list[] = [
1060
                                                        $source,
1061
                                                        'local',
1062
                                                        'url',
1063
                                                    ];
1064
                                                } else {
1065
                                                    // We didn't find any trace of current portal.
1066
                                                    $files_list[] = [
1067
                                                        $source,
1068
                                                        'remote',
1069
                                                        'url',
1070
                                                    ];
1071
                                                }
1072
                                            } else {
1073
                                                $files_list[] = [
1074
                                                    $source,
1075
                                                    'local',
1076
                                                    'abs',
1077
                                                ];
1078
                                            }
1079
                                            continue; // Skipping anything else to avoid two entries
1080
                                            //(while the others can have sub-files in their url, flv's can't).
1081
                                        }
1082
                                    }
1083
1084
                                    if (strpos($source, '://') > 0) {
1085
                                        // Cut at '?' in a URL with params.
1086
                                        if (strpos($source, '?') > 0) {
1087
                                            $second_part = substr(
1088
                                                $source,
1089
                                                strpos($source, '?')
1090
                                            );
1091
                                            if (strpos($second_part, '://') > 0) {
1092
                                                // If the second part of the url contains a url too,
1093
                                                // treat the second one before cutting.
1094
                                                $pos1 = strpos(
1095
                                                    $second_part,
1096
                                                    '='
1097
                                                );
1098
                                                $pos2 = strpos(
1099
                                                    $second_part,
1100
                                                    '&'
1101
                                                );
1102
                                                $second_part = substr(
1103
                                                    $second_part,
1104
                                                    $pos1 + 1,
1105
                                                    $pos2 - ($pos1 + 1)
1106
                                                );
1107
                                                if (false !== strpos($second_part, api_get_path(WEB_PATH))) {
1108
                                                    // We found the current portal url.
1109
                                                    $files_list[] = [
1110
                                                        $second_part,
1111
                                                        'local',
1112
                                                        'url',
1113
                                                    ];
1114
                                                    $in_files_list[] = self::get_resources_from_source(
1115
                                                        TOOL_DOCUMENT,
1116
                                                        $second_part,
1117
                                                        $recursivity + 1
1118
                                                    );
1119
                                                    if (count($in_files_list) > 0) {
1120
                                                        $files_list = array_merge(
1121
                                                            $files_list,
1122
                                                            $in_files_list
1123
                                                        );
1124
                                                    }
1125
                                                } else {
1126
                                                    // We didn't find any trace of current portal.
1127
                                                    $files_list[] = [
1128
                                                        $second_part,
1129
                                                        'remote',
1130
                                                        'url',
1131
                                                    ];
1132
                                                }
1133
                                            } elseif (strpos($second_part, '=') > 0) {
1134
                                                if ('/' === substr($second_part, 0, 1)) {
1135
                                                    // Link starts with a /,
1136
                                                    // making it absolute (relative to DocumentRoot).
1137
                                                    $files_list[] = [
1138
                                                        $second_part,
1139
                                                        'local',
1140
                                                        'abs',
1141
                                                    ];
1142
                                                    $in_files_list[] = self::get_resources_from_source(
1143
                                                        TOOL_DOCUMENT,
1144
                                                        $second_part,
1145
                                                        $recursivity + 1
1146
                                                    );
1147
                                                    if (count($in_files_list) > 0) {
1148
                                                        $files_list = array_merge(
1149
                                                            $files_list,
1150
                                                            $in_files_list
1151
                                                        );
1152
                                                    }
1153
                                                } elseif (0 === strstr($second_part, '..')) {
1154
                                                    // Link is relative but going back in the hierarchy.
1155
                                                    $files_list[] = [
1156
                                                        $second_part,
1157
                                                        'local',
1158
                                                        'rel',
1159
                                                    ];
1160
                                                    $dir = dirname(
1161
                                                        $abs_path
1162
                                                    );
1163
                                                    $new_abs_path = realpath(
1164
                                                        $dir.'/'.$second_part
1165
                                                    );
1166
                                                    $in_files_list[] = self::get_resources_from_source(
1167
                                                        TOOL_DOCUMENT,
1168
                                                        $new_abs_path,
1169
                                                        $recursivity + 1
1170
                                                    );
1171
                                                    if (count($in_files_list) > 0) {
1172
                                                        $files_list = array_merge(
1173
                                                            $files_list,
1174
                                                            $in_files_list
1175
                                                        );
1176
                                                    }
1177
                                                } else {
1178
                                                    // No starting '/', making it relative to current document's path.
1179
                                                    if ('./' == substr($second_part, 0, 2)) {
1180
                                                        $second_part = substr(
1181
                                                            $second_part,
1182
                                                            2
1183
                                                        );
1184
                                                    }
1185
                                                    $files_list[] = [
1186
                                                        $second_part,
1187
                                                        'local',
1188
                                                        'rel',
1189
                                                    ];
1190
                                                    $dir = dirname(
1191
                                                        $abs_path
1192
                                                    );
1193
                                                    $new_abs_path = realpath(
1194
                                                        $dir.'/'.$second_part
1195
                                                    );
1196
                                                    $in_files_list[] = self::get_resources_from_source(
1197
                                                        TOOL_DOCUMENT,
1198
                                                        $new_abs_path,
1199
                                                        $recursivity + 1
1200
                                                    );
1201
                                                    if (count($in_files_list) > 0) {
1202
                                                        $files_list = array_merge(
1203
                                                            $files_list,
1204
                                                            $in_files_list
1205
                                                        );
1206
                                                    }
1207
                                                }
1208
                                            }
1209
                                            // Leave that second part behind now.
1210
                                            $source = substr(
1211
                                                $source,
1212
                                                0,
1213
                                                strpos($source, '?')
1214
                                            );
1215
                                            if (strpos($source, '://') > 0) {
1216
                                                if (false !== strpos($source, api_get_path(WEB_PATH))) {
1217
                                                    // We found the current portal url.
1218
                                                    $files_list[] = [
1219
                                                        $source,
1220
                                                        'local',
1221
                                                        'url',
1222
                                                    ];
1223
                                                    $in_files_list[] = self::get_resources_from_source(
1224
                                                        TOOL_DOCUMENT,
1225
                                                        $source,
1226
                                                        $recursivity + 1
1227
                                                    );
1228
                                                    if (count($in_files_list) > 0) {
1229
                                                        $files_list = array_merge(
1230
                                                            $files_list,
1231
                                                            $in_files_list
1232
                                                        );
1233
                                                    }
1234
                                                } else {
1235
                                                    // We didn't find any trace of current portal.
1236
                                                    $files_list[] = [
1237
                                                        $source,
1238
                                                        'remote',
1239
                                                        'url',
1240
                                                    ];
1241
                                                }
1242
                                            } else {
1243
                                                // No protocol found, make link local.
1244
                                                if ('/' === substr($source, 0, 1)) {
1245
                                                    // Link starts with a /, making it absolute (relative to DocumentRoot).
1246
                                                    $files_list[] = [
1247
                                                        $source,
1248
                                                        'local',
1249
                                                        'abs',
1250
                                                    ];
1251
                                                    $in_files_list[] = self::get_resources_from_source(
1252
                                                        TOOL_DOCUMENT,
1253
                                                        $source,
1254
                                                        $recursivity + 1
1255
                                                    );
1256
                                                    if (count($in_files_list) > 0) {
1257
                                                        $files_list = array_merge(
1258
                                                            $files_list,
1259
                                                            $in_files_list
1260
                                                        );
1261
                                                    }
1262
                                                } elseif (0 === strstr($source, '..')) {
1263
                                                    // Link is relative but going back in the hierarchy.
1264
                                                    $files_list[] = [
1265
                                                        $source,
1266
                                                        'local',
1267
                                                        'rel',
1268
                                                    ];
1269
                                                    $dir = dirname(
1270
                                                        $abs_path
1271
                                                    );
1272
                                                    $new_abs_path = realpath(
1273
                                                        $dir.'/'.$source
1274
                                                    );
1275
                                                    $in_files_list[] = self::get_resources_from_source(
1276
                                                        TOOL_DOCUMENT,
1277
                                                        $new_abs_path,
1278
                                                        $recursivity + 1
1279
                                                    );
1280
                                                    if (count($in_files_list) > 0) {
1281
                                                        $files_list = array_merge(
1282
                                                            $files_list,
1283
                                                            $in_files_list
1284
                                                        );
1285
                                                    }
1286
                                                } else {
1287
                                                    // No starting '/', making it relative to current document's path.
1288
                                                    if ('./' == substr($source, 0, 2)) {
1289
                                                        $source = substr(
1290
                                                            $source,
1291
                                                            2
1292
                                                        );
1293
                                                    }
1294
                                                    $files_list[] = [
1295
                                                        $source,
1296
                                                        'local',
1297
                                                        'rel',
1298
                                                    ];
1299
                                                    $dir = dirname(
1300
                                                        $abs_path
1301
                                                    );
1302
                                                    $new_abs_path = realpath(
1303
                                                        $dir.'/'.$source
1304
                                                    );
1305
                                                    $in_files_list[] = self::get_resources_from_source(
1306
                                                        TOOL_DOCUMENT,
1307
                                                        $new_abs_path,
1308
                                                        $recursivity + 1
1309
                                                    );
1310
                                                    if (count($in_files_list) > 0) {
1311
                                                        $files_list = array_merge(
1312
                                                            $files_list,
1313
                                                            $in_files_list
1314
                                                        );
1315
                                                    }
1316
                                                }
1317
                                            }
1318
                                        }
1319
1320
                                        // Found some protocol there.
1321
                                        if (false !== strpos($source, api_get_path(WEB_PATH))) {
1322
                                            // We found the current portal url.
1323
                                            $files_list[] = [
1324
                                                $source,
1325
                                                'local',
1326
                                                'url',
1327
                                            ];
1328
                                            $in_files_list[] = self::get_resources_from_source(
1329
                                                TOOL_DOCUMENT,
1330
                                                $source,
1331
                                                $recursivity + 1
1332
                                            );
1333
                                            if (count($in_files_list) > 0) {
1334
                                                $files_list = array_merge(
1335
                                                    $files_list,
1336
                                                    $in_files_list
1337
                                                );
1338
                                            }
1339
                                        } else {
1340
                                            // We didn't find any trace of current portal.
1341
                                            $files_list[] = [
1342
                                                $source,
1343
                                                'remote',
1344
                                                'url',
1345
                                            ];
1346
                                        }
1347
                                    } else {
1348
                                        // No protocol found, make link local.
1349
                                        if ('/' === substr($source, 0, 1)) {
1350
                                            // Link starts with a /, making it absolute (relative to DocumentRoot).
1351
                                            $files_list[] = [
1352
                                                $source,
1353
                                                'local',
1354
                                                'abs',
1355
                                            ];
1356
                                            $in_files_list[] = self::get_resources_from_source(
1357
                                                TOOL_DOCUMENT,
1358
                                                $source,
1359
                                                $recursivity + 1
1360
                                            );
1361
                                            if (count($in_files_list) > 0) {
1362
                                                $files_list = array_merge(
1363
                                                    $files_list,
1364
                                                    $in_files_list
1365
                                                );
1366
                                            }
1367
                                        } elseif (0 === strstr($source, '..')) {
1368
                                            // Link is relative but going back in the hierarchy.
1369
                                            $files_list[] = [
1370
                                                $source,
1371
                                                'local',
1372
                                                'rel',
1373
                                            ];
1374
                                            $dir = dirname($abs_path);
1375
                                            $new_abs_path = realpath(
1376
                                                $dir.'/'.$source
1377
                                            );
1378
                                            $in_files_list[] = self::get_resources_from_source(
1379
                                                TOOL_DOCUMENT,
1380
                                                $new_abs_path,
1381
                                                $recursivity + 1
1382
                                            );
1383
                                            if (count($in_files_list) > 0) {
1384
                                                $files_list = array_merge(
1385
                                                    $files_list,
1386
                                                    $in_files_list
1387
                                                );
1388
                                            }
1389
                                        } else {
1390
                                            // No starting '/', making it relative to current document's path.
1391
                                            if (strpos($source, 'width=') ||
1392
                                                strpos($source, 'autostart=')
1393
                                            ) {
1394
                                                continue;
1395
                                            }
1396
1397
                                            if ('./' == substr($source, 0, 2)) {
1398
                                                $source = substr(
1399
                                                    $source,
1400
                                                    2
1401
                                                );
1402
                                            }
1403
                                            $files_list[] = [
1404
                                                $source,
1405
                                                'local',
1406
                                                'rel',
1407
                                            ];
1408
                                            $dir = dirname($abs_path);
1409
                                            $new_abs_path = realpath(
1410
                                                $dir.'/'.$source
1411
                                            );
1412
                                            $in_files_list[] = self::get_resources_from_source(
1413
                                                TOOL_DOCUMENT,
1414
                                                $new_abs_path,
1415
                                                $recursivity + 1
1416
                                            );
1417
                                            if (count($in_files_list) > 0) {
1418
                                                $files_list = array_merge(
1419
                                                    $files_list,
1420
                                                    $in_files_list
1421
                                                );
1422
                                            }
1423
                                        }
1424
                                    }
1425
                                }
1426
                            }
1427
                        }
1428
                        break;
1429
                    default:
1430
                        break;
1431
                }
1432
1433
                break;
1434
            default: // Ignore.
1435
                break;
1436
        }
1437
1438
        $checked_files_list = [];
1439
        $checked_array_list = [];
1440
        foreach ($files_list as $idx => $file) {
1441
            if (!empty($file[0])) {
1442
                if (!in_array($file[0], $checked_files_list)) {
1443
                    $checked_files_list[] = $files_list[$idx][0];
1444
                    $checked_array_list[] = $files_list[$idx];
1445
                }
1446
            }
1447
        }
1448
1449
        return $checked_array_list;
1450
    }
1451
1452
    /**
1453
     * Gets the score.
1454
     *
1455
     * @return float The current score or 0 if no score set yet
1456
     */
1457
    public function get_score()
1458
    {
1459
        $res = 0;
1460
        if (!empty($this->current_score)) {
1461
            $res = $this->current_score;
1462
        }
1463
1464
        return $res;
1465
    }
1466
1467
    /**
1468
     * Gets the item status.
1469
     *
1470
     * @param bool $check_db     Do or don't check into the database for the
1471
     *                           latest value. Optional. Default is true
1472
     * @param bool $update_local Do or don't update the local attribute
1473
     *                           value with what's been found in DB
1474
     *
1475
     * @return string Current status or 'Not attempted' if no status set yet
1476
     */
1477
    public function get_status($check_db = true, $update_local = false)
1478
    {
1479
        $courseId = $this->courseId;
1480
        $debug = self::DEBUG;
1481
        if ($debug) {
1482
            error_log('learnpathItem::get_status() on item '.$this->db_id);
1483
        }
1484
        if ($check_db) {
1485
            if ($debug) {
1486
                error_log('learnpathItem::get_status(): checking db');
1487
            }
1488
            if (!empty($this->db_item_view_id) && !empty($courseId)) {
1489
                $table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1490
                $sql = "SELECT status FROM $table
1491
                        WHERE
1492
                            c_id = $courseId AND
1493
                            iid = '".$this->db_item_view_id."' AND
1494
                            view_count = '".$this->get_attempt_id()."'";
1495
                $res = Database::query($sql);
1496
                if (1 == Database::num_rows($res)) {
1497
                    $row = Database::fetch_array($res);
1498
                    if ($update_local) {
1499
                        $this->set_status($row['status']);
1500
                    }
1501
1502
                    return $row['status'];
1503
                }
1504
            }
1505
        } else {
1506
            if (!empty($this->status)) {
1507
                return $this->status;
1508
            }
1509
        }
1510
1511
        return $this->possible_status[0];
1512
    }
1513
1514
    /**
1515
     * Gets the suspend data.
1516
     */
1517
    public function get_suspend_data()
1518
    {
1519
        // TODO: Improve cleaning of breaklines ... it works but is it really
1520
        // a beautiful way to do it ?
1521
        if (!empty($this->current_data)) {
1522
            return str_replace(
1523
                ["\r", "\n", "'"],
1524
                ['\r', '\n', "\\'"],
1525
                $this->current_data
1526
            );
1527
        }
1528
1529
        return '';
1530
    }
1531
1532
    /**
1533
     * @param string $origin
1534
     * @param string $time
1535
     *
1536
     * @return string
1537
     */
1538
    public static function getScormTimeFromParameter(
1539
        $origin = 'php',
1540
        $time = null
1541
    ) {
1542
        $h = get_lang('h');
1543
        if (!isset($time)) {
1544
            if ('js' == $origin) {
1545
                return '00 : 00: 00';
1546
            }
1547
1548
            return '00 '.$h.' 00 \' 00"';
1549
        }
1550
1551
        return api_format_time($time, $origin);
1552
    }
1553
1554
    /**
1555
     * Gets the total time spent on this item view so far.
1556
     *
1557
     * @param string   $origin     Origin of the request. If coming from PHP,
1558
     *                             send formatted as xxhxx'xx", otherwise use scorm format 00:00:00
1559
     * @param int|null $given_time Given time is a default time to return formatted
1560
     * @param bool     $query_db   Whether to get the value from db or from memory
1561
     *
1562
     * @return string A string with the time in SCORM format
1563
     */
1564
    public function get_scorm_time(
1565
        $origin = 'php',
1566
        $given_time = null,
1567
        $query_db = false
1568
    ) {
1569
        $time = null;
1570
        $courseId = $this->courseId;
1571
        if (empty($courseId)) {
1572
            $courseId = api_get_course_int_id();
1573
        }
1574
1575
        $courseId = (int) $courseId;
1576
        if (!isset($given_time)) {
1577
            if (self::DEBUG > 2) {
1578
                error_log(
1579
                    'learnpathItem::get_scorm_time(): given time empty, current_start_time = '.$this->current_start_time,
1580
                    0
1581
                );
1582
            }
1583
            if (true === $query_db) {
1584
                $table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1585
                $sql = "SELECT start_time, total_time
1586
                        FROM $table
1587
                        WHERE
1588
                            c_id = $courseId AND
1589
                            iid = '".$this->db_item_view_id."' AND
1590
                            view_count = '".$this->get_attempt_id()."'";
1591
                $res = Database::query($sql);
1592
                $row = Database::fetch_array($res);
1593
                $start = $row['start_time'];
1594
                $stop = $start + $row['total_time'];
1595
            } else {
1596
                $start = $this->current_start_time;
1597
                $stop = $this->current_stop_time;
1598
            }
1599
            if (!empty($start)) {
1600
                if (!empty($stop)) {
1601
                    $time = $stop - $start;
1602
                } else {
1603
                    $time = time() - $start;
1604
                }
1605
            }
1606
        } else {
1607
            $time = $given_time;
1608
        }
1609
        if (self::DEBUG > 2) {
1610
            error_log(
1611
                'learnpathItem::get_scorm_time(): intermediate = '.$time,
1612
                0
1613
            );
1614
        }
1615
        $time = api_format_time($time, $origin);
1616
1617
        return $time;
1618
    }
1619
1620
    /**
1621
     * Get the extra terms (tags) that identify this item.
1622
     *
1623
     * @return mixed
1624
     */
1625
    public function get_terms()
1626
    {
1627
        $table = Database::get_course_table(TABLE_LP_ITEM);
1628
        $sql = "SELECT * FROM $table
1629
                WHERE iid = ".intval($this->db_id);
1630
        $res = Database::query($sql);
1631
        $row = Database::fetch_array($res);
1632
1633
        return $row['terms'];
1634
    }
1635
1636
    /**
1637
     * Returns the item's title.
1638
     *
1639
     * @return string Title
1640
     */
1641
    public function get_title()
1642
    {
1643
        if (empty($this->title)) {
1644
            return '';
1645
        }
1646
1647
        return $this->title;
1648
    }
1649
1650
    /**
1651
     * Returns the total time used to see that item.
1652
     *
1653
     * @return int Total time
1654
     */
1655
    public function get_total_time()
1656
    {
1657
        $debug = self::DEBUG;
1658
        if ($debug) {
1659
            error_log(
1660
                'learnpathItem::get_total_time() for item '.$this->db_id.
1661
                ' - Initially, current_start_time = '.$this->current_start_time.
1662
                ' and current_stop_time = '.$this->current_stop_time,
1663
                0
1664
            );
1665
        }
1666
        if (0 == $this->current_start_time) {
1667
            // Shouldn't be necessary thanks to the open() method.
1668
            if ($debug) {
1669
                error_log(
1670
                    'learnpathItem::get_total_time() - Current start time was empty',
1671
                    0
1672
                );
1673
            }
1674
            $this->current_start_time = time();
1675
        }
1676
1677
        if (time() < $this->current_stop_time ||
1678
            0 == $this->current_stop_time
1679
        ) {
1680
            if ($debug) {
1681
                error_log(
1682
                    'learnpathItem::get_total_time() - Current stop time was '
1683
                    .'greater than the current time or was empty',
1684
                    0
1685
                );
1686
            }
1687
            // If this case occurs, then we risk to write huge time data in db.
1688
            // In theory, stop time should be *always* updated here, but it
1689
            // might be used in some unknown goal.
1690
            $this->current_stop_time = time();
1691
        }
1692
1693
        $time = $this->current_stop_time - $this->current_start_time;
1694
1695
        if ($time < 0) {
1696
            if ($debug) {
1697
                error_log(
1698
                    'learnpathItem::get_total_time() - Time smaller than 0. Returning 0',
1699
                    0
1700
                );
1701
            }
1702
1703
            return 0;
1704
        } else {
1705
            $time = $this->fixAbusiveTime($time);
1706
            if ($debug) {
1707
                error_log(
1708
                    'Current start time = '.$this->current_start_time.', current stop time = '.
1709
                    $this->current_stop_time.' Returning '.$time."-----------\n"
1710
                );
1711
            }
1712
1713
            return $time;
1714
        }
1715
    }
1716
1717
    /**
1718
     * Sometimes time recorded for a learning path item is superior to the maximum allowed duration of the session.
1719
     * In this case, this session resets the time for that particular learning path item to 5 minutes
1720
     * (something more realistic, that is also used when leaving the portal without closing one's session).
1721
     *
1722
     * @param int $time
1723
     *
1724
     * @return int
1725
     */
1726
    public function fixAbusiveTime($time)
1727
    {
1728
        // Code based from Event::courseLogout
1729
        $sessionLifetime = api_get_configuration_value('session_lifetime');
1730
        // If session life time too big use 1 hour
1731
        if (empty($sessionLifetime) || $sessionLifetime > 86400) {
1732
            $sessionLifetime = 3600;
1733
        }
1734
1735
        if (!Tracking::minimumTimeAvailable(api_get_session_id(), api_get_course_int_id())) {
1736
            $fixedAddedMinute = 5 * 60; // Add only 5 minutes
1737
            if ($time > $sessionLifetime) {
1738
                error_log("fixAbusiveTime: Total time is too big: $time replaced with: $fixedAddedMinute");
1739
                error_log("item_id : ".$this->db_id." lp_item_view.iid: ".$this->db_item_view_id);
1740
                $time = $fixedAddedMinute;
1741
            }
1742
1743
            return $time;
1744
        } else {
1745
            // Calulate minimum and accumulated time
1746
            $user_id = api_get_user_id();
1747
            $myLP = learnpath::getLpFromSession(api_get_course_id(), $this->lp_id, $user_id);
1748
            $timeLp = $myLP->getAccumulateWorkTime();
1749
            $timeTotalCourse = $myLP->getAccumulateWorkTimeTotalCourse();
1750
            /*
1751
            $timeLp = $_SESSION['oLP']->getAccumulateWorkTime();
1752
            $timeTotalCourse = $_SESSION['oLP']->getAccumulateWorkTimeTotalCourse();
1753
            */
1754
            // Minimum connection percentage
1755
            $perc = 100;
1756
            // Time from the course
1757
            $tc = $timeTotalCourse;
1758
            /*if (!empty($sessionId) && $sessionId != 0) {
1759
                $sql = "SELECT hours, perc FROM plugin_licences_course_session WHERE session_id = $sessionId";
1760
                $res = Database::query($sql);
1761
                if (Database::num_rows($res) > 0) {
1762
                    $aux = Database::fetch_assoc($res);
1763
                    $perc = $aux['perc'];
1764
                    $tc = $aux['hours'] * 60;
1765
                }
1766
            }*/
1767
            // Percentage of the learning paths
1768
            $pl = 0;
1769
            if (!empty($timeTotalCourse)) {
1770
                $pl = $timeLp / $timeTotalCourse;
1771
            }
1772
1773
            // Minimum time for each learning path
1774
            $accumulateWorkTime = ($pl * $tc * $perc / 100);
1775
            $time_seg = intval($accumulateWorkTime * 60);
1776
1777
            if ($time_seg < $sessionLifetime) {
1778
                $sessionLifetime = $time_seg;
1779
            }
1780
1781
            if ($time > $sessionLifetime) {
1782
                $fixedAddedMinute = $time_seg + mt_rand(0, 300);
1783
                if (self::DEBUG > 2) {
1784
                    error_log("Total time is too big: $time replaced with: $fixedAddedMinute");
1785
                }
1786
                $time = $fixedAddedMinute;
1787
            }
1788
1789
            return $time;
1790
        }
1791
    }
1792
1793
    /**
1794
     * Gets the item type.
1795
     *
1796
     * @return string The item type (can be doc, dir, sco, asset)
1797
     */
1798
    public function get_type()
1799
    {
1800
        $res = 'asset';
1801
        if (!empty($this->type)) {
1802
            $res = $this->type;
1803
        }
1804
1805
        return $res;
1806
    }
1807
1808
    /**
1809
     * Gets the view count for this item.
1810
     *
1811
     * @return int Number of attempts or 0
1812
     */
1813
    public function get_view_count()
1814
    {
1815
        if (!empty($this->attempt_id)) {
1816
            return $this->attempt_id;
1817
        }
1818
1819
        return 0;
1820
    }
1821
1822
    /**
1823
     * Tells if an item is done ('completed','passed','succeeded') or not.
1824
     *
1825
     * @return bool True if the item is done ('completed','passed','succeeded'),
1826
     *              false otherwise
1827
     */
1828
    public function is_done()
1829
    {
1830
        $completedStatusList = [
1831
            'completed',
1832
            'passed',
1833
            'succeeded',
1834
            'failed',
1835
        ];
1836
1837
        if ($this->status_is($completedStatusList)) {
1838
            return true;
1839
        }
1840
1841
        return false;
1842
    }
1843
1844
    /**
1845
     * Tells if a restart is allowed (take it from $this->prevent_reinit and $this->status).
1846
     *
1847
     * @return int -1 if retaking the sco another time for credit is not allowed,
1848
     *             0 if it is not allowed but the item has to be finished
1849
     *             1 if it is allowed. Defaults to 1
1850
     */
1851
    public function isRestartAllowed()
1852
    {
1853
        $restart = 1;
1854
        $mystatus = $this->get_status(true);
1855
        if ($this->get_prevent_reinit() > 0) {
1856
            // If prevent_reinit == 1 (or more)
1857
            // If status is not attempted or incomplete, authorize retaking (of the same) anyway. Otherwise:
1858
            if ($mystatus != $this->possible_status[0] && $mystatus != $this->possible_status[1]) {
1859
                $restart = -1;
1860
            } else { //status incompleted or not attempted
1861
                $restart = 0;
1862
            }
1863
        } else {
1864
            if ($mystatus == $this->possible_status[0] || $mystatus == $this->possible_status[1]) {
1865
                $restart = -1;
1866
            }
1867
        }
1868
1869
        return $restart;
1870
    }
1871
1872
    /**
1873
     * Opens/launches the item. Initialises runtime values.
1874
     *
1875
     * @param bool $allow_new_attempt
1876
     *
1877
     * @return bool true on success, false on failure
1878
     */
1879
    public function open($allow_new_attempt = false)
1880
    {
1881
        if (0 == $this->prevent_reinit) {
1882
            $this->current_score = 0;
1883
            $this->current_start_time = time();
1884
            // In this case, as we are opening the item, what is important to us
1885
            // is the database status, in order to know if this item has already
1886
            // been used in the past (rather than just loaded and modified by
1887
            // some javascript but not written in the database).
1888
            // If the database status is different from 'not attempted', we can
1889
            // consider this item has already been used, and as such we can
1890
            // open a new attempt. Otherwise, we'll just reuse the current
1891
            // attempt, which is generally created the first time the item is
1892
            // loaded (for example as part of the table of contents).
1893
            $stat = $this->get_status(true);
1894
            if ($allow_new_attempt && isset($stat) && ($stat != $this->possible_status[0])) {
1895
                $this->attempt_id = $this->attempt_id + 1; // Open a new attempt.
1896
            }
1897
            $this->status = $this->possible_status[1];
1898
        } else {
1899
            /*if ($this->current_start_time == 0) {
1900
                // Small exception for start time, to avoid amazing values.
1901
                $this->current_start_time = time();
1902
            }*/
1903
            // If we don't init start time here, the time is sometimes calculated from the last start time.
1904
            $this->current_start_time = time();
1905
        }
1906
    }
1907
1908
    /**
1909
     * Outputs the item contents.
1910
     *
1911
     * @return string HTML file (displayable in an <iframe>) or empty string if no path defined
1912
     */
1913
    public function output()
1914
    {
1915
        if (!empty($this->path) and is_file($this->path)) {
1916
            $output = '';
1917
            $output .= file_get_contents($this->path);
1918
1919
            return $output;
1920
        }
1921
1922
        return '';
1923
    }
1924
1925
    /**
1926
     * Parses the prerequisites string with the AICC logic language.
1927
     *
1928
     * @param string $prereqs_string The prerequisites string as it figures in imsmanifest.xml
1929
     * @param array  $items          Array of items in the current learnpath object.
1930
     *                               Although we're in the learnpathItem object, it's necessary to have
1931
     *                               a list of all items to be able to check the current item's prerequisites
1932
     * @param array  $refs_list      list of references
1933
     *                               (the "ref" column in the lp_item table) that are strings used in the
1934
     *                               expression of prerequisites
1935
     * @param int    $user_id        The user ID. In some cases like Chamilo quizzes,
1936
     *                               it's necessary to have the user ID to query other tables (like the results of quizzes)
1937
     *
1938
     * @return bool True if the list of prerequisites given is entirely satisfied, false otherwise
1939
     */
1940
    public function parse_prereq($prereqs_string, $items, $refs_list, $user_id)
1941
    {
1942
        $debug = self::DEBUG;
1943
        if ($debug > 0) {
1944
            error_log(
1945
                'learnpathItem::parse_prereq() for learnpath '.$this->lp_id.' with string '.$prereqs_string,
1946
                0
1947
            );
1948
        }
1949
1950
        $courseId = $this->courseId;
1951
        $sessionId = api_get_session_id();
1952
1953
        // Deal with &, |, ~, =, <>, {}, ,, X*, () in reverse order.
1954
        $this->prereq_alert = '';
1955
1956
        // First parse all parenthesis by using a sequential loop
1957
        //  (looking for less-inclusives first).
1958
        if ('_true_' == $prereqs_string) {
1959
            return true;
1960
        }
1961
1962
        if ('_false_' == $prereqs_string) {
1963
            if (empty($this->prereq_alert)) {
1964
                $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
1965
            }
1966
1967
            return false;
1968
        }
1969
1970
        while (false !== strpos($prereqs_string, '(')) {
1971
            // Remove any () set and replace with its value.
1972
            $matches = [];
1973
            $res = preg_match_all(
1974
                '/(\(([^\(\)]*)\))/',
1975
                $prereqs_string,
1976
                $matches
1977
            );
1978
            if ($res) {
1979
                foreach ($matches[2] as $id => $match) {
1980
                    $str_res = $this->parse_prereq(
1981
                        $match,
1982
                        $items,
1983
                        $refs_list,
1984
                        $user_id
1985
                    );
1986
                    if ($str_res) {
1987
                        $prereqs_string = str_replace(
1988
                            $matches[1][$id],
1989
                            '_true_',
1990
                            $prereqs_string
1991
                        );
1992
                    } else {
1993
                        $prereqs_string = str_replace(
1994
                            $matches[1][$id],
1995
                            '_false_',
1996
                            $prereqs_string
1997
                        );
1998
                    }
1999
                }
2000
            }
2001
        }
2002
2003
        // Parenthesis removed, now look for ORs as it is the lesser-priority
2004
        //  binary operator (= always uses one text operand).
2005
        if (false === strpos($prereqs_string, '|')) {
2006
            if ($debug) {
2007
                error_log('New LP - Didnt find any OR, looking for AND', 0);
2008
            }
2009
            if (false !== strpos($prereqs_string, '&')) {
2010
                $list = explode('&', $prereqs_string);
2011
                if (count($list) > 1) {
2012
                    $andstatus = true;
2013
                    foreach ($list as $condition) {
2014
                        $andstatus = $andstatus && $this->parse_prereq(
2015
                            $condition,
2016
                            $items,
2017
                            $refs_list,
2018
                            $user_id
2019
                        );
2020
2021
                        if (!$andstatus) {
2022
                            if ($debug) {
2023
                                error_log(
2024
                                    'New LP - One condition in AND was false, short-circuit',
2025
                                    0
2026
                                );
2027
                            }
2028
                            break;
2029
                        }
2030
                    }
2031
2032
                    if (empty($this->prereq_alert) && !$andstatus) {
2033
                        $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2034
                    }
2035
2036
                    return $andstatus;
2037
                } else {
2038
                    if (isset($items[$refs_list[$list[0]]])) {
2039
                        $status = $items[$refs_list[$list[0]]]->get_status(true);
2040
                        $returnstatus = ($status == $this->possible_status[2]) || ($status == $this->possible_status[3]);
2041
                        if (empty($this->prereq_alert) && !$returnstatus) {
2042
                            $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2043
                        }
2044
2045
                        return $returnstatus;
2046
                    }
2047
                    $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2048
2049
                    return false;
2050
                }
2051
            } else {
2052
                // No ORs found, now look for ANDs.
2053
                if ($debug) {
2054
                    error_log('New LP - Didnt find any AND, looking for =', 0);
2055
                }
2056
2057
                if (false !== strpos($prereqs_string, '=')) {
2058
                    if ($debug) {
2059
                        error_log('New LP - Found =, looking into it', 0);
2060
                    }
2061
                    // We assume '=' signs only appear when there's nothing else around.
2062
                    $params = explode('=', $prereqs_string);
2063
                    if (2 == count($params)) {
2064
                        // Right number of operands.
2065
                        if (isset($items[$refs_list[$params[0]]])) {
2066
                            $status = $items[$refs_list[$params[0]]]->get_status(true);
2067
                            $returnstatus = $status == $params[1];
2068
                            if (empty($this->prereq_alert) && !$returnstatus) {
2069
                                $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2070
                            }
2071
2072
                            return $returnstatus;
2073
                        }
2074
                        $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2075
2076
                        return false;
2077
                    }
2078
                } else {
2079
                    // No ANDs found, look for <>
2080
                    if ($debug) {
2081
                        error_log(
2082
                            'New LP - Didnt find any =, looking for <>',
2083
                            0
2084
                        );
2085
                    }
2086
2087
                    if (false !== strpos($prereqs_string, '<>')) {
2088
                        if ($debug) {
2089
                            error_log('New LP - Found <>, looking into it', 0);
2090
                        }
2091
                        // We assume '<>' signs only appear when there's nothing else around.
2092
                        $params = explode('<>', $prereqs_string);
2093
                        if (2 == count($params)) {
2094
                            // Right number of operands.
2095
                            if (isset($items[$refs_list[$params[0]]])) {
2096
                                $status = $items[$refs_list[$params[0]]]->get_status(true);
2097
                                $returnstatus = $status != $params[1];
2098
                                if (empty($this->prereq_alert) && !$returnstatus) {
2099
                                    $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2100
                                }
2101
2102
                                return $returnstatus;
2103
                            }
2104
                            $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2105
2106
                            return false;
2107
                        }
2108
                    } else {
2109
                        // No <> found, look for ~ (unary)
2110
                        if ($debug) {
2111
                            error_log(
2112
                                'New LP - Didnt find any =, looking for ~',
2113
                                0
2114
                            );
2115
                        }
2116
                        // Only remains: ~ and X*{}
2117
                        if (false !== strpos($prereqs_string, '~')) {
2118
                            // Found NOT.
2119
                            if ($debug) {
2120
                                error_log(
2121
                                    'New LP - Found ~, looking into it',
2122
                                    0
2123
                                );
2124
                            }
2125
                            $list = [];
2126
                            $myres = preg_match(
2127
                                '/~([^(\d+\*)\{]*)/',
2128
                                $prereqs_string,
2129
                                $list
2130
                            );
2131
                            if ($myres) {
2132
                                $returnstatus = !$this->parse_prereq(
2133
                                    $list[1],
2134
                                    $items,
2135
                                    $refs_list,
2136
                                    $user_id
2137
                                );
2138
                                if (empty($this->prereq_alert) && !$returnstatus) {
2139
                                    $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2140
                                }
2141
2142
                                return $returnstatus;
2143
                            } else {
2144
                                // Strange...
2145
                                if ($debug) {
2146
                                    error_log(
2147
                                        'New LP - Found ~ but strange string: '.$prereqs_string,
2148
                                        0
2149
                                    );
2150
                                }
2151
                            }
2152
                        } else {
2153
                            // Finally, look for sets/groups
2154
                            if ($debug) {
2155
                                error_log(
2156
                                    'New LP - Didnt find any ~, looking for groups',
2157
                                    0
2158
                                );
2159
                            }
2160
                            // Only groups here.
2161
                            $groups = [];
2162
                            $groups_there = preg_match_all(
2163
                                '/((\d+\*)?\{([^\}]+)\}+)/',
2164
                                $prereqs_string,
2165
                                $groups
2166
                            );
2167
2168
                            if ($groups_there) {
2169
                                foreach ($groups[1] as $gr) {
2170
                                    // Only take the results that correspond to
2171
                                    //  the big brackets-enclosed condition.
2172
                                    if ($debug) {
2173
                                        error_log(
2174
                                            'New LP - Dealing with group '.$gr,
2175
                                            0
2176
                                        );
2177
                                    }
2178
                                    $multi = [];
2179
                                    $mycond = false;
2180
                                    if (preg_match(
2181
                                        '/(\d+)\*\{([^\}]+)\}/',
2182
                                        $gr,
2183
                                        $multi
2184
                                    )
2185
                                    ) {
2186
                                        if ($debug) {
2187
                                            error_log(
2188
                                                'New LP - Found multiplier '.$multi[0],
2189
                                                0
2190
                                            );
2191
                                        }
2192
                                        $count = $multi[1];
2193
                                        $list = explode(',', $multi[2]);
2194
                                        $mytrue = 0;
2195
                                        foreach ($list as $cond) {
2196
                                            if (isset($items[$refs_list[$cond]])) {
2197
                                                $status = $items[$refs_list[$cond]]->get_status(true);
2198
                                                if ($status == $this->possible_status[2] ||
2199
                                                    $status == $this->possible_status[3]
2200
                                                ) {
2201
                                                    $mytrue++;
2202
                                                    if ($debug) {
2203
                                                        error_log(
2204
                                                            'New LP - Found true item, counting.. ('.($mytrue).')',
2205
                                                            0
2206
                                                        );
2207
                                                    }
2208
                                                }
2209
                                            } else {
2210
                                                if ($debug) {
2211
                                                    error_log(
2212
                                                        'New LP - item '.$cond.' does not exist in items list',
2213
                                                        0
2214
                                                    );
2215
                                                }
2216
                                            }
2217
                                        }
2218
                                        if ($mytrue >= $count) {
2219
                                            if ($debug) {
2220
                                                error_log(
2221
                                                    'New LP - Got enough true results, return true',
2222
                                                    0
2223
                                                );
2224
                                            }
2225
                                            $mycond = true;
2226
                                        } else {
2227
                                            if ($debug) {
2228
                                                error_log(
2229
                                                    'New LP - Not enough true results',
2230
                                                    0
2231
                                                );
2232
                                            }
2233
                                        }
2234
                                    } else {
2235
                                        if ($debug) {
2236
                                            error_log(
2237
                                                'New LP - No multiplier',
2238
                                                0
2239
                                            );
2240
                                        }
2241
                                        $list = explode(',', $gr);
2242
                                        $mycond = true;
2243
                                        foreach ($list as $cond) {
2244
                                            if (isset($items[$refs_list[$cond]])) {
2245
                                                $status = $items[$refs_list[$cond]]->get_status(true);
2246
                                                if ($status == $this->possible_status[2] ||
2247
                                                    $status == $this->possible_status[3]
2248
                                                ) {
2249
                                                    $mycond = true;
2250
                                                    if ($debug) {
2251
                                                        error_log(
2252
                                                            'New LP - Found true item',
2253
                                                            0
2254
                                                        );
2255
                                                    }
2256
                                                } else {
2257
                                                    if ($debug) {
2258
                                                        error_log(
2259
                                                            'New LP - '.
2260
                                                            ' Found false item, the set is not true, return false',
2261
                                                            0
2262
                                                        );
2263
                                                    }
2264
                                                    $mycond = false;
2265
                                                    break;
2266
                                                }
2267
                                            } else {
2268
                                                if ($debug) {
2269
                                                    error_log(
2270
                                                        'New LP - item '.$cond.' does not exist in items list',
2271
                                                        0
2272
                                                    );
2273
                                                }
2274
                                                if ($debug) {
2275
                                                    error_log(
2276
                                                        'New LP - Found false item, the set is not true, return false',
2277
                                                        0
2278
                                                    );
2279
                                                }
2280
                                                $mycond = false;
2281
                                                break;
2282
                                            }
2283
                                        }
2284
                                    }
2285
                                    if (!$mycond && empty($this->prereq_alert)) {
2286
                                        $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2287
                                    }
2288
2289
                                    return $mycond;
2290
                                }
2291
                            } else {
2292
                                // Nothing found there either. Now return the
2293
                                // value of the corresponding resource completion status.
2294
                                if (isset($refs_list[$prereqs_string]) &&
2295
                                    isset($items[$refs_list[$prereqs_string]])
2296
                                ) {
2297
                                    /** @var learnpathItem $itemToCheck */
2298
                                    $itemToCheck = $items[$refs_list[$prereqs_string]];
2299
2300
                                    if ('quiz' === $itemToCheck->type) {
2301
                                        // 1. Checking the status in current items.
2302
                                        $status = $itemToCheck->get_status(true);
2303
                                        $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2304
2305
                                        if (!$returnstatus) {
2306
                                            $explanation = sprintf(
2307
                                                get_lang('Item %s blocks this step'),
2308
                                                $itemToCheck->get_title()
2309
                                            );
2310
                                            $this->prereq_alert = $explanation;
2311
                                        }
2312
2313
                                        // For one and first attempt.
2314
                                        if (1 == $this->prevent_reinit) {
2315
                                            // 2. If is completed we check the results in the DB of the quiz.
2316
                                            if ($returnstatus) {
2317
                                                $sql = 'SELECT score, max_score
2318
                                                        FROM '.Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES).'
2319
                                                        WHERE
2320
                                                            exe_exo_id = '.$items[$refs_list[$prereqs_string]]->path.' AND
2321
                                                            exe_user_id = '.$user_id.' AND
2322
                                                            orig_lp_id = '.$this->lp_id.' AND
2323
                                                            orig_lp_item_id = '.$prereqs_string.' AND
2324
                                                            status <> "incomplete" AND
2325
                                                            c_id = '.$courseId.'
2326
                                                        ORDER BY exe_date DESC
2327
                                                        LIMIT 0, 1';
2328
                                                $rs_quiz = Database::query($sql);
2329
                                                if ($quiz = Database::fetch_array($rs_quiz)) {
2330
                                                    /** @var learnpathItem $myItemToCheck */
2331
                                                    $myItemToCheck = $items[$refs_list[$this->get_id()]];
2332
                                                    $minScore = $myItemToCheck->getPrerequisiteMinScore();
2333
                                                    $maxScore = $myItemToCheck->getPrerequisiteMaxScore();
2334
2335
                                                    if (isset($minScore) && isset($minScore)) {
2336
                                                        // Taking min/max prerequisites values see BT#5776
2337
                                                        if ($quiz['score'] >= $minScore &&
2338
                                                            $quiz['score'] <= $maxScore
2339
                                                        ) {
2340
                                                            $returnstatus = true;
2341
                                                        } else {
2342
                                                            $explanation = sprintf(
2343
                                                                get_lang('Your result at %s blocks this step'),
2344
                                                                $itemToCheck->get_title()
2345
                                                            );
2346
                                                            $this->prereq_alert = $explanation;
2347
                                                            $returnstatus = false;
2348
                                                        }
2349
                                                    } else {
2350
                                                        // Classic way
2351
                                                        if ($quiz['score'] >=
2352
                                                            $items[$refs_list[$prereqs_string]]->get_mastery_score()
2353
                                                        ) {
2354
                                                            $returnstatus = true;
2355
                                                        } else {
2356
                                                            $explanation = sprintf(
2357
                                                                get_lang('Your result at %s blocks this step'),
2358
                                                                $itemToCheck->get_title()
2359
                                                            );
2360
                                                            $this->prereq_alert = $explanation;
2361
                                                            $returnstatus = false;
2362
                                                        }
2363
                                                    }
2364
                                                } else {
2365
                                                    $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2366
                                                    $returnstatus = false;
2367
                                                }
2368
                                            }
2369
                                        } else {
2370
                                            // 3. For multiple attempts we check that there are minimum 1 item completed
2371
                                            // Checking in the database.
2372
                                            $sql = 'SELECT score, max_score
2373
                                                    FROM '.Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES).'
2374
                                                    WHERE
2375
                                                        c_id = '.$courseId.' AND
2376
                                                        exe_exo_id = '.$items[$refs_list[$prereqs_string]]->path.' AND
2377
                                                        exe_user_id = '.$user_id.' AND
2378
                                                        orig_lp_id = '.$this->lp_id.' AND
2379
                                                        orig_lp_item_id = '.$prereqs_string;
2380
2381
                                            $rs_quiz = Database::query($sql);
2382
                                            if (Database::num_rows($rs_quiz) > 0) {
2383
                                                while ($quiz = Database::fetch_array($rs_quiz)) {
2384
                                                    /** @var learnpathItem $myItemToCheck */
2385
                                                    $myItemToCheck = $items[$refs_list[$this->get_id()]];
2386
                                                    $minScore = $myItemToCheck->getPrerequisiteMinScore();
2387
                                                    $maxScore = $myItemToCheck->getPrerequisiteMaxScore();
2388
2389
                                                    if (empty($minScore)) {
2390
                                                        // Try with mastery_score
2391
                                                        $masteryScoreAsMin = $myItemToCheck->get_mastery_score();
2392
                                                        if (!empty($masteryScoreAsMin)) {
2393
                                                            $minScore = $masteryScoreAsMin;
2394
                                                        }
2395
                                                    }
2396
2397
                                                    if (isset($minScore) && isset($minScore)) {
2398
                                                        // Taking min/max prerequisites values see BT#5776
2399
                                                        if ($quiz['score'] >= $minScore && $quiz['score'] <= $maxScore) {
2400
                                                            $returnstatus = true;
2401
                                                            break;
2402
                                                        } else {
2403
                                                            $explanation = sprintf(
2404
                                                                get_lang('Your result at %s blocks this step'),
2405
                                                                $itemToCheck->get_title()
2406
                                                            );
2407
                                                            $this->prereq_alert = $explanation;
2408
                                                            $returnstatus = false;
2409
                                                        }
2410
                                                    } else {
2411
                                                        if ($quiz['score'] >=
2412
                                                            $items[$refs_list[$prereqs_string]]->get_mastery_score()
2413
                                                        ) {
2414
                                                            $returnstatus = true;
2415
                                                            break;
2416
                                                        } else {
2417
                                                            $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2418
                                                            $returnstatus = false;
2419
                                                        }
2420
                                                    }
2421
                                                }
2422
                                            } else {
2423
                                                $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2424
                                                $returnstatus = false;
2425
                                            }
2426
                                        }
2427
2428
                                        if (false === $returnstatus) {
2429
                                            // Check results from another sessions.
2430
                                            $checkOtherSessions = api_get_configuration_value('validate_lp_prerequisite_from_other_session');
2431
                                            if ($checkOtherSessions) {
2432
                                                $returnstatus = $this->getStatusFromOtherSessions(
2433
                                                    $user_id,
2434
                                                    $prereqs_string,
2435
                                                    $refs_list
2436
                                                );
2437
                                            }
2438
                                        }
2439
2440
                                        return $returnstatus;
2441
                                    } elseif ('student_publication' === $itemToCheck->type) {
2442
                                        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
2443
                                        $workId = $items[$refs_list[$prereqs_string]]->path;
2444
                                        $count = get_work_count_by_student($user_id, $workId);
2445
                                        if ($count >= 1) {
2446
                                            $returnstatus = true;
2447
                                        } else {
2448
                                            $returnstatus = false;
2449
                                            $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2450
                                        }
2451
2452
                                        return $returnstatus;
2453
                                    } else {
2454
                                        $status = $itemToCheck->get_status(true);
2455
                                        if (self::DEBUG) {
2456
                                            error_log('Status:'.$status);
2457
                                        }
2458
                                        $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2459
2460
                                        // Check results from another sessions.
2461
                                        $checkOtherSessions = api_get_configuration_value('validate_lp_prerequisite_from_other_session');
2462
                                        if ($checkOtherSessions && !$returnstatus) {
2463
                                            $returnstatus = $this->getStatusFromOtherSessions(
2464
                                                $user_id,
2465
                                                $prereqs_string,
2466
                                                $refs_list
2467
                                            );
2468
                                        }
2469
2470
                                        if (!$returnstatus) {
2471
                                            $explanation = sprintf(
2472
                                                get_lang('Item %s blocks this step'),
2473
                                                $itemToCheck->get_title()
2474
                                            );
2475
                                            $this->prereq_alert = $explanation;
2476
                                        }
2477
2478
                                        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2479
                                        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
2480
2481
                                        if ($returnstatus && 1 == $this->prevent_reinit) {
2482
                                            $sql = "SELECT iid FROM $lp_view
2483
                                                    WHERE
2484
                                                        c_id = $courseId AND
2485
                                                        user_id = $user_id  AND
2486
                                                        lp_id = $this->lp_id AND
2487
                                                        session_id = $sessionId
2488
                                                    LIMIT 0, 1";
2489
                                            $rs_lp = Database::query($sql);
2490
                                            if (Database::num_rows($rs_lp)) {
2491
                                                $lp_id = Database::fetch_row($rs_lp);
2492
                                                $my_lp_id = $lp_id[0];
2493
2494
                                                $sql = "SELECT status FROM $lp_item_view
2495
                                                        WHERE
2496
                                                            c_id = $courseId AND
2497
                                                            lp_view_id = $my_lp_id AND
2498
                                                            lp_item_id = $refs_list[$prereqs_string]
2499
                                                        LIMIT 0, 1";
2500
                                                $rs_lp = Database::query($sql);
2501
                                                $status_array = Database::fetch_row($rs_lp);
2502
                                                $status = $status_array[0];
2503
2504
                                                $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2505
                                                if (!$returnstatus && empty($this->prereq_alert)) {
2506
                                                    $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2507
                                                }
2508
                                            }
2509
2510
                                            if ($checkOtherSessions && false === $returnstatus) {
2511
                                                $returnstatus = $returnstatus = $this->getStatusFromOtherSessions(
2512
                                                    $user_id,
2513
                                                    $prereqs_string,
2514
                                                    $refs_list
2515
                                                );
2516
                                            }
2517
                                        }
2518
2519
                                        return $returnstatus;
2520
                                    }
2521
                                }
2522
                            }
2523
                        }
2524
                    }
2525
                }
2526
            }
2527
        } else {
2528
            $list = explode("\|", $prereqs_string);
2529
            if (count($list) > 1) {
2530
                if (self::DEBUG > 1) {
2531
                    error_log('New LP - Found OR, looking into it', 0);
2532
                }
2533
                $orstatus = false;
2534
                foreach ($list as $condition) {
2535
                    if (self::DEBUG) {
2536
                        error_log(
2537
                            'New LP - Found OR, adding it ('.$condition.')',
2538
                            0
2539
                        );
2540
                    }
2541
                    $orstatus = $orstatus || $this->parse_prereq(
2542
                        $condition,
2543
                        $items,
2544
                        $refs_list,
2545
                        $user_id
2546
                    );
2547
                    if ($orstatus) {
2548
                        // Shortcircuit OR.
2549
                        if (self::DEBUG > 1) {
2550
                            error_log(
2551
                                'New LP - One condition in OR was true, short-circuit',
2552
                                0
2553
                            );
2554
                        }
2555
                        break;
2556
                    }
2557
                }
2558
                if (!$orstatus && empty($this->prereq_alert)) {
2559
                    $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2560
                }
2561
2562
                return $orstatus;
2563
            } else {
2564
                if (self::DEBUG > 1) {
2565
                    error_log(
2566
                        'New LP - OR was found but only one elem present !?',
2567
                        0
2568
                    );
2569
                }
2570
                if (isset($items[$refs_list[$list[0]]])) {
2571
                    $status = $items[$refs_list[$list[0]]]->get_status(true);
2572
                    $returnstatus = 'completed' == $status || 'passed' == $status;
2573
                    if (!$returnstatus && empty($this->prereq_alert)) {
2574
                        $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2575
                    }
2576
2577
                    return $returnstatus;
2578
                }
2579
            }
2580
        }
2581
        if (empty($this->prereq_alert)) {
2582
            $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2583
        }
2584
2585
        if (self::DEBUG > 1) {
2586
            error_log(
2587
                'New LP - End of parse_prereq. Error code is now '.$this->prereq_alert,
2588
                0
2589
            );
2590
        }
2591
2592
        return false;
2593
    }
2594
2595
    /**
2596
     * Reinits all local values as the learnpath is restarted.
2597
     *
2598
     * @return bool True on success, false otherwise
2599
     */
2600
    public function restart()
2601
    {
2602
        if (self::DEBUG > 0) {
2603
            error_log('learnpathItem::restart()', 0);
2604
        }
2605
        $seriousGame = $this->get_seriousgame_mode();
2606
        //For serious game  : We reuse same attempt_id
2607
        if (1 == $seriousGame && 'sco' == $this->type) {
2608
            // If this is a sco, Chamilo can't update the time without an
2609
            //  explicit scorm call
2610
            $this->current_start_time = 0;
2611
            $this->current_stop_time = 0; //Those 0 value have this effect
2612
            $this->last_scorm_session_time = 0;
2613
            $this->save();
2614
2615
            return true;
2616
        }
2617
2618
        $this->save();
2619
2620
        $allowed = $this->isRestartAllowed();
2621
        if (-1 === $allowed) {
2622
            // Nothing allowed, do nothing.
2623
        } elseif (1 === $allowed) {
2624
            // Restart as new attempt is allowed, record a new attempt.
2625
            $this->attempt_id = $this->attempt_id + 1; // Simply reuse the previous attempt_id.
2626
            $this->current_score = 0;
2627
            $this->current_start_time = 0;
2628
            $this->current_stop_time = 0;
2629
            $this->current_data = '';
2630
            $this->status = $this->possible_status[0];
2631
            $this->interactions_count = 0;
2632
            $this->interactions = [];
2633
            $this->objectives_count = 0;
2634
            $this->objectives = [];
2635
            $this->lesson_location = '';
2636
            if (TOOL_QUIZ != $this->type) {
2637
                $this->write_to_db();
2638
            }
2639
        } else {
2640
            // Restart current element is allowed (because it's not finished yet),
2641
            // reinit current.
2642
            //$this->current_score = 0;
2643
            $this->current_start_time = 0;
2644
            $this->current_stop_time = 0;
2645
            $this->interactions_count = $this->get_interactions_count(true);
2646
        }
2647
2648
        return true;
2649
    }
2650
2651
    /**
2652
     * Saves data in the database.
2653
     *
2654
     * @param bool $from_outside     Save from URL params (1) or from object attributes (0)
2655
     * @param bool $prereqs_complete The results of a check on prerequisites for this item.
2656
     *                               True if prerequisites are completed, false otherwise. Defaults to false. Only used if not sco or au
2657
     *
2658
     * @return bool True on success, false on failure
2659
     */
2660
    public function save($from_outside = true, $prereqs_complete = false)
2661
    {
2662
        $debug = self::DEBUG;
2663
        if ($debug) {
2664
            error_log('learnpathItem::save()', 0);
2665
        }
2666
        // First check if parameters passed via GET can be saved here
2667
        // in case it's a SCORM, we should get:
2668
        if ('sco' == $this->type || 'au' == $this->type) {
2669
            $status = $this->get_status(true);
2670
            if (1 == $this->prevent_reinit &&
2671
                $status != $this->possible_status[0] && // not attempted
2672
                $status != $this->possible_status[1]    //incomplete
2673
            ) {
2674
                if ($debug) {
2675
                    error_log(
2676
                        'learnpathItem::save() - save reinit blocked by setting',
2677
                        0
2678
                    );
2679
                }
2680
                // Do nothing because the status has already been set. Don't allow it to change.
2681
                // TODO: Check there isn't a special circumstance where this should be saved.
2682
            } else {
2683
                if ($debug) {
2684
                    error_log(
2685
                        'learnpathItem::save() - SCORM save request received',
2686
                        0
2687
                    );
2688
                }
2689
2690
                if ($from_outside) {
2691
                    if ($debug) {
2692
                        error_log(
2693
                            'learnpathItem::save() - Getting item data from outside',
2694
                            0
2695
                        );
2696
                    }
2697
                    foreach ($_GET as $param => $value) {
2698
                        switch ($param) {
2699
                            case 'score':
2700
                                $this->set_score($value);
2701
                                if ($debug) {
2702
                                    error_log(
2703
                                        'learnpathItem::save() - setting score to '.$value,
2704
                                        0
2705
                                    );
2706
                                }
2707
                                break;
2708
                            case 'max':
2709
                                $this->set_max_score($value);
2710
                                if ($debug) {
2711
                                    error_log(
2712
                                        'learnpathItem::save() - setting view_max_score to '.$value,
2713
                                        0
2714
                                    );
2715
                                }
2716
                                break;
2717
                            case 'min':
2718
                                $this->min_score = $value;
2719
                                if ($debug) {
2720
                                    error_log(
2721
                                        'learnpathItem::save() - setting min_score to '.$value,
2722
                                        0
2723
                                    );
2724
                                }
2725
                                break;
2726
                            case 'lesson_status':
2727
                                if (!empty($value)) {
2728
                                    $this->set_status($value);
2729
                                    if ($debug) {
2730
                                        error_log(
2731
                                            'learnpathItem::save() - setting status to '.$value,
2732
                                            0
2733
                                        );
2734
                                    }
2735
                                }
2736
                                break;
2737
                            case 'time':
2738
                                $this->set_time($value);
2739
                                if ($debug) {
2740
                                    error_log(
2741
                                        'learnpathItem::save() - setting time to '.$value,
2742
                                        0
2743
                                    );
2744
                                }
2745
                                break;
2746
                            case 'suspend_data':
2747
                                $this->current_data = $value;
2748
                                if ($debug) {
2749
                                    error_log(
2750
                                        'learnpathItem::save() - setting suspend_data to '.$value,
2751
                                        0
2752
                                    );
2753
                                }
2754
                                break;
2755
                            case 'lesson_location':
2756
                                $this->set_lesson_location($value);
2757
                                if ($debug) {
2758
                                    error_log(
2759
                                        'learnpathItem::save() - setting lesson_location to '.$value,
2760
                                        0
2761
                                    );
2762
                                }
2763
                                break;
2764
                            case 'core_exit':
2765
                                $this->set_core_exit($value);
2766
                                if ($debug) {
2767
                                    error_log(
2768
                                        'learnpathItem::save() - setting core_exit to '.$value,
2769
                                        0
2770
                                    );
2771
                                }
2772
                                break;
2773
                            case 'interactions':
2774
                                break;
2775
                            case 'objectives':
2776
                                break;
2777
                            default:
2778
                                // Ignore.
2779
                                break;
2780
                        }
2781
                    }
2782
                } else {
2783
                    if ($debug) {
2784
                        error_log(
2785
                            'learnpathItem::save() - Using inside item status',
2786
                            0
2787
                        );
2788
                    }
2789
                    // Do nothing, just let the local attributes be used.
2790
                }
2791
            }
2792
        } else {
2793
            // If not SCO, such messages should not be expected.
2794
            $type = strtolower($this->type);
2795
            if ($debug) {
2796
                error_log("type: $type");
2797
            }
2798
2799
            if (!WhispeakAuthPlugin::isAllowedToSaveLpItem($this->iId)) {
2800
                return false;
2801
            }
2802
2803
            switch ($type) {
2804
                case 'asset':
2805
                    if ($prereqs_complete) {
2806
                        $this->set_status($this->possible_status[2]);
2807
                    }
2808
                    break;
2809
                case TOOL_HOTPOTATOES:
2810
                    break;
2811
                case TOOL_QUIZ:
2812
                    return false;
2813
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
2814
                default:
2815
                    // For now, everything that is not sco and not asset is set to
2816
                    // completed when saved.
2817
                    if ($prereqs_complete) {
2818
                        $this->set_status($this->possible_status[2]);
2819
                    }
2820
                    break;
2821
            }
2822
        }
2823
2824
        if ($debug) {
2825
            error_log('End of learnpathItem::save() - Calling write_to_db() now');
2826
        }
2827
2828
        return $this->write_to_db();
2829
    }
2830
2831
    /**
2832
     * Sets the number of attempt_id to a given value.
2833
     *
2834
     * @param int $num The given value to set attempt_id to
2835
     *
2836
     * @return bool TRUE on success, FALSE otherwise
2837
     */
2838
    public function set_attempt_id($num)
2839
    {
2840
        if ($num == strval(intval($num)) && $num >= 0) {
2841
            $this->attempt_id = $num;
2842
2843
            return true;
2844
        }
2845
2846
        return false;
2847
    }
2848
2849
    /**
2850
     * Sets the core_exit value to the one given.
2851
     *
2852
     * @return bool $value  True (always)
2853
     */
2854
    public function set_core_exit($value)
2855
    {
2856
        switch ($value) {
2857
            case '':
2858
                $this->core_exit = '';
2859
                break;
2860
            case 'suspend':
2861
                $this->core_exit = 'suspend';
2862
                break;
2863
            default:
2864
                $this->core_exit = 'none';
2865
                break;
2866
        }
2867
2868
        return true;
2869
    }
2870
2871
    /**
2872
     * Sets the item's description.
2873
     *
2874
     * @param string $string Description
2875
     */
2876
    public function set_description($string = '')
2877
    {
2878
        if (!empty($string)) {
2879
            $this->description = $string;
2880
        }
2881
    }
2882
2883
    /**
2884
     * Sets the lesson_location value.
2885
     *
2886
     * @param string $location lesson_location as provided by the SCO
2887
     *
2888
     * @return bool True on success, false otherwise
2889
     */
2890
    public function set_lesson_location($location)
2891
    {
2892
        if (isset($location)) {
2893
            $this->lesson_location = $location;
2894
2895
            return true;
2896
        }
2897
2898
        return false;
2899
    }
2900
2901
    /**
2902
     * Sets the item's depth level in the LP tree (0 is at root).
2903
     *
2904
     * @param int $int Level
2905
     */
2906
    public function set_level($int = 0)
2907
    {
2908
        $this->level = (int) $int;
2909
    }
2910
2911
    /**
2912
     * Sets the lp_view id this item view is registered to.
2913
     *
2914
     * @param int $lp_view_id lp_view DB ID
2915
     * @param int $courseId
2916
     *
2917
     * @return bool
2918
     *
2919
     * @todo //todo insert into lp_item_view if lp_view not exists
2920
     */
2921
    public function set_lp_view($lp_view_id, $courseId = null)
2922
    {
2923
        $lp_view_id = (int) $lp_view_id;
2924
        $courseId = (int) $courseId;
2925
2926
        if (empty($courseId)) {
2927
            $courseId = api_get_course_int_id();
2928
        }
2929
2930
        $lpItemId = $this->get_id();
2931
2932
        if (empty($lpItemId)) {
2933
            return false;
2934
        }
2935
2936
        if (empty($lp_view_id)) {
2937
            return false;
2938
        }
2939
2940
        if (self::DEBUG > 0) {
2941
            error_log('learnpathItem::set_lp_view('.$lp_view_id.')', 0);
2942
        }
2943
2944
        $this->view_id = $lp_view_id;
2945
2946
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2947
        // Get the lp_item_view with the highest view_count.
2948
        $sql = "SELECT * FROM $item_view_table
2949
                WHERE
2950
                    c_id = $courseId AND
2951
                    lp_item_id = $lpItemId AND
2952
                    lp_view_id = $lp_view_id
2953
                ORDER BY view_count DESC";
2954
2955
        if (self::DEBUG > 2) {
2956
            error_log(
2957
                'learnpathItem::set_lp_view() - Querying lp_item_view: '.$sql,
2958
                0
2959
            );
2960
        }
2961
        $res = Database::query($sql);
2962
        if (Database::num_rows($res) > 0) {
2963
            $row = Database::fetch_array($res);
2964
            $this->db_item_view_id = $row['iid'];
2965
            $this->attempt_id = $row['view_count'];
2966
            $this->current_score = $row['score'];
2967
            $this->current_data = $row['suspend_data'];
2968
            $this->view_max_score = $row['max_score'];
2969
            $this->status = $row['status'];
2970
            $this->current_start_time = $row['start_time'];
2971
            $this->current_stop_time = $this->current_start_time + $row['total_time'];
2972
            $this->lesson_location = $row['lesson_location'];
2973
            $this->core_exit = $row['core_exit'];
2974
2975
            if (self::DEBUG > 2) {
2976
                error_log(
2977
                    'learnpathItem::set_lp_view() - Updated item object with database values',
2978
                    0
2979
                );
2980
            }
2981
2982
            // Now get the number of interactions for this little guy.
2983
            $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2984
            $sql = "SELECT * FROM $table
2985
                    WHERE
2986
                        c_id = $courseId AND
2987
                        lp_iv_id = '".$this->db_item_view_id."'";
2988
2989
            $res = Database::query($sql);
2990
            if (false !== $res) {
2991
                $this->interactions_count = Database::num_rows($res);
2992
            } else {
2993
                $this->interactions_count = 0;
2994
            }
2995
            // Now get the number of objectives for this little guy.
2996
            $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2997
            $sql = "SELECT * FROM $table
2998
                    WHERE
2999
                        c_id = $courseId AND
3000
                        lp_iv_id = '".$this->db_item_view_id."'";
3001
3002
            $this->objectives_count = 0;
3003
            $res = Database::query($sql);
3004
            if (false !== $res) {
3005
                $this->objectives_count = Database::num_rows($res);
3006
            }
3007
        }
3008
3009
        return true;
3010
    }
3011
3012
    /**
3013
     * Sets the path.
3014
     *
3015
     * @param string $string Path
3016
     */
3017
    public function set_path($string = '')
3018
    {
3019
        if (!empty($string)) {
3020
            $this->path = $string;
3021
        }
3022
    }
3023
3024
    /**
3025
     * Sets the prevent_reinit attribute.
3026
     * This is based on the LP value and is set at creation time for
3027
     * each learnpathItem. It is a (bad?) way of avoiding
3028
     * a reference to the LP when saving an item.
3029
     *
3030
     * @param int 1 for "prevent", 0 for "don't prevent"
0 ignored issues
show
Documentation Bug introduced by
The doc comment 1 at position 0 could not be parsed: Unknown type name '1' at position 0 in 1.
Loading history...
3031
     * saving freshened values (new "not attempted" status etc)
3032
     */
3033
    public function set_prevent_reinit($prevent)
3034
    {
3035
        $this->prevent_reinit = 0;
3036
        if ($prevent) {
3037
            $this->prevent_reinit = 1;
3038
        }
3039
    }
3040
3041
    /**
3042
     * Sets the score value. If the mastery_score is set and the score reaches
3043
     * it, then set the status to 'passed'.
3044
     *
3045
     * @param float $score Score
3046
     *
3047
     * @return bool True on success, false otherwise
3048
     */
3049
    public function set_score($score)
3050
    {
3051
        $debug = self::DEBUG;
3052
        if ($debug > 0) {
3053
            error_log('learnpathItem::set_score('.$score.')', 0);
3054
        }
3055
        if (($this->max_score <= 0 || $score <= $this->max_score) && ($score >= $this->min_score)) {
3056
            $this->current_score = $score;
3057
            $masteryScore = $this->get_mastery_score();
3058
            $current_status = $this->get_status(false);
3059
3060
            // Fixes bug when SCORM doesn't send a mastery score even if they sent a score!
3061
            if (-1 == $masteryScore) {
3062
                $masteryScore = $this->max_score;
3063
            }
3064
3065
            if ($debug > 0) {
3066
                error_log('get_mastery_score: '.$masteryScore);
3067
                error_log('current_status: '.$current_status);
3068
                error_log('current score : '.$this->current_score);
3069
            }
3070
3071
            // If mastery_score is set AND the current score reaches the mastery
3072
            //  score AND the current status is different from 'completed', then
3073
            //  set it to 'passed'.
3074
            /*
3075
            if ($master != -1 && $this->current_score >= $master && $current_status != $this->possible_status[2]) {
3076
                if ($debug > 0) error_log('Status changed to: '.$this->possible_status[3]);
3077
                $this->set_status($this->possible_status[3]); //passed
3078
            } elseif ($master != -1 && $this->current_score < $master) {
3079
                if ($debug > 0) error_log('Status changed to: '.$this->possible_status[4]);
3080
                $this->set_status($this->possible_status[4]); //failed
3081
            }*/
3082
            return true;
3083
        }
3084
3085
        return false;
3086
    }
3087
3088
    /**
3089
     * Sets the maximum score for this item.
3090
     *
3091
     * @param int $score Maximum score - must be a decimal or an empty string
3092
     *
3093
     * @return bool True on success, false on error
3094
     */
3095
    public function set_max_score($score)
3096
    {
3097
        if (is_int($score) || '' == $score) {
3098
            $this->view_max_score = $score;
3099
3100
            return true;
3101
        }
3102
3103
        return false;
3104
    }
3105
3106
    /**
3107
     * Sets the status for this item.
3108
     *
3109
     * @param string $status Status - must be one of the values defined in $this->possible_status
3110
     *                       (this affects the status setting)
3111
     *
3112
     * @return bool True on success, false on error
3113
     */
3114
    public function set_status($status)
3115
    {
3116
        if (self::DEBUG > 0) {
3117
            error_log('learnpathItem::set_status('.$status.')', 0);
3118
        }
3119
3120
        $found = false;
3121
        foreach ($this->possible_status as $possible) {
3122
            if (preg_match('/^'.$possible.'$/i', $status)) {
3123
                $found = true;
3124
            }
3125
        }
3126
3127
        if ($found) {
3128
            $this->status = $status;
3129
            if (self::DEBUG) {
3130
                error_log(
3131
                    'learnpathItem::set_status() - '.
3132
                        'Updated object status of item '.$this->db_id.
3133
                    ' to '.$this->status
3134
                );
3135
            }
3136
3137
            return true;
3138
        }
3139
3140
        $this->status = $this->possible_status[0];
3141
3142
        return false;
3143
    }
3144
3145
    /**
3146
     * Set the (indexing) terms for this learnpath item.
3147
     *
3148
     * @param string $terms Terms, as a comma-split list
3149
     *
3150
     * @return bool Always return true
3151
     */
3152
    public function set_terms($terms)
3153
    {
3154
        global $charset;
3155
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
3156
        $a_terms = preg_split('/,/', $terms);
3157
        $i_terms = preg_split('/,/', $this->get_terms());
3158
        foreach ($i_terms as $term) {
3159
            if (!in_array($term, $a_terms)) {
3160
                array_push($a_terms, $term);
3161
            }
3162
        }
3163
        $new_terms = $a_terms;
3164
        $new_terms_string = implode(',', $new_terms);
3165
3166
        // TODO: Validate csv string.
3167
        $terms = Database::escape_string(api_htmlentities($new_terms_string, ENT_QUOTES, $charset));
3168
        $sql = "UPDATE $lp_item
3169
                SET terms = '$terms'
3170
                WHERE iid=".$this->get_id();
3171
        Database::query($sql);
3172
        // Save it to search engine.
3173
        if ('true' == api_get_setting('search_enabled')) {
3174
            $di = new ChamiloIndexer();
3175
            $di->update_terms($this->get_search_did(), $new_terms, 'T');
3176
        }
3177
3178
        return true;
3179
    }
3180
3181
    /**
3182
     * Get the document ID from inside the text index database.
3183
     *
3184
     * @return int Search index database document ID
3185
     */
3186
    public function get_search_did()
3187
    {
3188
        return $this->search_did;
3189
    }
3190
3191
    /**
3192
     * Sets the item viewing time in a usable form, given that SCORM packages
3193
     * often give it as 00:00:00.0000.
3194
     *
3195
     * @param    string    Time as given by SCORM
3196
     * @param string $format
3197
     */
3198
    public function set_time($scorm_time, $format = 'scorm')
3199
    {
3200
        $debug = self::DEBUG;
3201
        if ($debug) {
3202
            error_log("learnpathItem::set_time($scorm_time, $format)");
3203
            error_log("this->type: ".$this->type);
3204
            error_log("this->current_start_time: ".$this->current_start_time);
3205
        }
3206
3207
        if ('0' == $scorm_time &&
3208
            'sco' != $this->type &&
3209
            0 != $this->current_start_time
3210
        ) {
3211
            $myTime = time() - $this->current_start_time;
3212
            if ($myTime > 0) {
3213
                $this->update_time($myTime);
3214
                if ($debug) {
3215
                    error_log('found asset - set time to '.$myTime);
3216
                }
3217
            } else {
3218
                if ($debug) {
3219
                    error_log('Time not set');
3220
                }
3221
            }
3222
        } else {
3223
            switch ($format) {
3224
                case 'scorm':
3225
                    $res = [];
3226
                    if (preg_match(
3227
                        '/^(\d{1,4}):(\d{2}):(\d{2})(\.\d{1,4})?/',
3228
                        $scorm_time,
3229
                        $res
3230
                    )
3231
                    ) {
3232
                        $hour = $res[1];
3233
                        $min = $res[2];
3234
                        $sec = $res[3];
3235
                        // Getting total number of seconds spent.
3236
                        $totalSec = $hour * 3600 + $min * 60 + $sec;
3237
                        if ($debug) {
3238
                            error_log("totalSec : $totalSec");
3239
                            error_log("Now calling to scorm_update_time()");
3240
                        }
3241
                        $this->scorm_update_time($totalSec);
3242
                    }
3243
                    break;
3244
                case 'int':
3245
                    if ($debug) {
3246
                        error_log("scorm_time = $scorm_time");
3247
                        error_log("Now calling to scorm_update_time()");
3248
                    }
3249
                    $this->scorm_update_time($scorm_time);
3250
                    break;
3251
            }
3252
        }
3253
    }
3254
3255
    /**
3256
     * Sets the item's title.
3257
     *
3258
     * @param string $string Title
3259
     */
3260
    public function set_title($string = '')
3261
    {
3262
        if (!empty($string)) {
3263
            $this->title = $string;
3264
        }
3265
    }
3266
3267
    /**
3268
     * Sets the item's type.
3269
     *
3270
     * @param string $string Type
3271
     */
3272
    public function set_type($string = '')
3273
    {
3274
        if (!empty($string)) {
3275
            $this->type = $string;
3276
        }
3277
    }
3278
3279
    /**
3280
     * Checks if the current status is part of the list of status given.
3281
     *
3282
     * @param array $list An array of status to check for.
3283
     *                    If the current status is one of the strings, return true
3284
     *
3285
     * @return bool True if the status was one of the given strings,
3286
     *              false otherwise
3287
     */
3288
    public function status_is($list = [])
3289
    {
3290
        if (self::DEBUG > 1) {
3291
            error_log(
3292
                'learnpathItem::status_is('.print_r(
3293
                    $list,
3294
                    true
3295
                ).') on item '.$this->db_id,
3296
                0
3297
            );
3298
        }
3299
        $currentStatus = $this->get_status(true);
3300
        if (empty($currentStatus)) {
3301
            return false;
3302
        }
3303
        $found = false;
3304
        foreach ($list as $status) {
3305
            if (preg_match('/^'.$status.'$/i', $currentStatus)) {
3306
                if (self::DEBUG > 2) {
3307
                    error_log(
3308
                        'New LP - learnpathItem::status_is() - Found status '.
3309
                            $status.' corresponding to current status',
3310
                        0
3311
                    );
3312
                }
3313
                $found = true;
3314
3315
                return $found;
3316
            }
3317
        }
3318
        if (self::DEBUG > 2) {
3319
            error_log(
3320
                'New LP - learnpathItem::status_is() - Status '.
3321
                    $currentStatus.' did not match request',
3322
                0
3323
            );
3324
        }
3325
3326
        return $found;
3327
    }
3328
3329
    /**
3330
     * Updates the time info according to the given session_time.
3331
     *
3332
     * @param int $totalSec Time in seconds
3333
     */
3334
    public function update_time($totalSec = 0)
3335
    {
3336
        if (self::DEBUG > 0) {
3337
            error_log('learnpathItem::update_time('.$totalSec.')');
3338
        }
3339
        if ($totalSec >= 0) {
3340
            // Getting start time from finish time. The only problem in the calculation is it might be
3341
            // modified by the scripts processing time.
3342
            $now = time();
3343
            $start = $now - $totalSec;
3344
            $this->current_start_time = $start;
3345
            $this->current_stop_time = $now;
3346
        }
3347
    }
3348
3349
    /**
3350
     * Special scorm update time function. This function will update time
3351
     * directly into db for scorm objects.
3352
     *
3353
     * @param int $total_sec Total number of seconds
3354
     */
3355
    public function scorm_update_time($total_sec = 0)
3356
    {
3357
        $debug = self::DEBUG;
3358
        if ($debug) {
3359
            error_log('learnpathItem::scorm_update_time()');
3360
            error_log("total_sec: $total_sec");
3361
        }
3362
3363
        // Step 1 : get actual total time stored in db
3364
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3365
        $courseId = $this->courseId;
3366
3367
        $sql = 'SELECT total_time, status
3368
                FROM '.$item_view_table.'
3369
                WHERE
3370
                    c_id = '.$courseId.' AND
3371
                    lp_item_id = "'.$this->db_id.'" AND
3372
                    lp_view_id = "'.$this->view_id.'" AND
3373
                    view_count = "'.$this->get_attempt_id().'"';
3374
        $result = Database::query($sql);
3375
        $row = Database::fetch_array($result);
3376
3377
        if (!isset($row['total_time'])) {
3378
            $total_time = 0;
3379
        } else {
3380
            $total_time = $row['total_time'];
3381
        }
3382
        if ($debug) {
3383
            error_log("Original total_time: $total_time");
3384
        }
3385
3386
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3387
        $lp_id = (int) $this->lp_id;
3388
        $sql = "SELECT * FROM $lp_table WHERE iid = $lp_id";
3389
        $res = Database::query($sql);
3390
        $accumulateScormTime = 'false';
3391
        if (Database::num_rows($res) > 0) {
3392
            $row = Database::fetch_assoc($res);
3393
            $accumulateScormTime = $row['accumulate_scorm_time'];
3394
        }
3395
3396
        // Step 2.1 : if normal mode total_time = total_time + total_sec
3397
        if ('sco' == $this->type && 0 != $accumulateScormTime) {
3398
            if ($debug) {
3399
                error_log("accumulateScormTime is on. total_time modified: $total_time + $total_sec");
3400
            }
3401
            $total_time += $total_sec;
3402
        } else {
3403
            // Step 2.2 : if not cumulative mode total_time = total_time - last_update + total_sec
3404
            $total_sec = $this->fixAbusiveTime($total_sec);
3405
            if ($debug) {
3406
                error_log("after fix abusive: $total_sec");
3407
                error_log("total_time: $total_time");
3408
                error_log("this->last_scorm_session_time: ".$this->last_scorm_session_time);
3409
            }
3410
3411
            $total_time = $total_time - $this->last_scorm_session_time + $total_sec;
3412
            $this->last_scorm_session_time = $total_sec;
3413
3414
            if ($total_time < 0) {
3415
                $total_time = $total_sec;
3416
            }
3417
        }
3418
3419
        if ($debug) {
3420
            error_log("accumulate_scorm_time: $accumulateScormTime");
3421
            error_log("total_time modified: $total_time");
3422
        }
3423
3424
        // Step 3 update db only if status != completed, passed, browsed or seriousgamemode not activated
3425
        // @todo complete
3426
        $case_completed = [
3427
            'completed',
3428
            'passed',
3429
            'browsed',
3430
            'failed',
3431
        ];
3432
3433
        if (1 != $this->seriousgame_mode ||
3434
            !in_array($row['status'], $case_completed)
3435
        ) {
3436
            $sql = "UPDATE $item_view_table
3437
                      SET total_time = '$total_time'
3438
                    WHERE
3439
                        c_id = $courseId AND
3440
                        lp_item_id = {$this->db_id} AND
3441
                        lp_view_id = {$this->view_id} AND
3442
                        view_count = {$this->get_attempt_id()}";
3443
            if ($debug) {
3444
                error_log('-------------total_time updated ------------------------');
3445
                error_log($sql);
3446
                error_log('-------------------------------------');
3447
            }
3448
            Database::query($sql);
3449
        }
3450
    }
3451
3452
    /**
3453
     * Write objectives to DB. This method is separate from write_to_db() because otherwise
3454
     * objectives are lost as a side effect to AJAX and session concurrent access.
3455
     *
3456
     * @return bool True or false on error
3457
     */
3458
    public function write_objectives_to_db()
3459
    {
3460
        if (self::DEBUG > 0) {
3461
            error_log('learnpathItem::write_objectives_to_db()', 0);
3462
        }
3463
        if (api_is_invitee()) {
3464
            // If the user is an invitee, we don't write anything to DB
3465
            return true;
3466
        }
3467
        $courseId = api_get_course_int_id();
3468
        if (is_array($this->objectives) && count($this->objectives) > 0) {
3469
            // Save objectives.
3470
            $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3471
            $sql = "SELECT iid
3472
                    FROM $tbl
3473
                    WHERE
3474
                        c_id = $courseId AND
3475
                        lp_item_id = ".$this->db_id." AND
3476
                        lp_view_id = ".$this->view_id." AND
3477
                        view_count = ".$this->attempt_id;
3478
            $res = Database::query($sql);
3479
            if (Database::num_rows($res) > 0) {
3480
                $row = Database::fetch_array($res);
3481
                $lp_iv_id = $row[0];
3482
                if (self::DEBUG > 2) {
3483
                    error_log(
3484
                        'learnpathItem::write_to_db() - Got item_view_id '.
3485
                            $lp_iv_id.', now checking objectives ',
3486
                        0
3487
                    );
3488
                }
3489
                foreach ($this->objectives as $index => $objective) {
3490
                    $iva_table = Database::get_course_table(
3491
                        TABLE_LP_IV_OBJECTIVE
3492
                    );
3493
                    $iva_sql = "SELECT iid FROM $iva_table
3494
                                WHERE
3495
                                    c_id = $courseId AND
3496
                                    lp_iv_id = $lp_iv_id AND
3497
                                    objective_id = '".Database::escape_string($objective[0])."'";
3498
                    $iva_res = Database::query($iva_sql);
3499
                    // id(0), type(1), time(2), weighting(3),
3500
                    // correct_responses(4), student_response(5),
3501
                    // result(6), latency(7)
3502
                    if (Database::num_rows($iva_res) > 0) {
3503
                        // Update (or don't).
3504
                        $iva_row = Database::fetch_array($iva_res);
3505
                        $iva_id = $iva_row[0];
3506
                        $ivau_sql = "UPDATE $iva_table ".
3507
                            "SET objective_id = '".Database::escape_string($objective[0])."',".
3508
                            "status = '".Database::escape_string($objective[1])."',".
3509
                            "score_raw = '".Database::escape_string($objective[2])."',".
3510
                            "score_min = '".Database::escape_string($objective[4])."',".
3511
                            "score_max = '".Database::escape_string($objective[3])."' ".
3512
                            "WHERE c_id = $courseId AND iid = $iva_id";
3513
                        Database::query($ivau_sql);
3514
                    } else {
3515
                        // Insert new one.
3516
                        $params = [
3517
                            'c_id' => $courseId,
3518
                            'lp_iv_id' => $lp_iv_id,
3519
                            'order_id' => $index,
3520
                            'objective_id' => $objective[0],
3521
                            'status' => $objective[1],
3522
                            'score_raw' => $objective[2],
3523
                            'score_min' => $objective[4],
3524
                            'score_max' => $objective[3],
3525
                        ];
3526
3527
                        $insertId = Database::insert($iva_table, $params);
3528
                        if ($insertId) {
3529
                            $sql = "UPDATE $iva_table SET id = iid
3530
                                    WHERE iid = $insertId";
3531
                            Database::query($sql);
3532
                        }
3533
                    }
3534
                }
3535
            }
3536
        }
3537
    }
3538
3539
    /**
3540
     * Writes the current data to the database.
3541
     *
3542
     * @return bool Query result
3543
     */
3544
    public function write_to_db()
3545
    {
3546
        $debug = self::DEBUG;
3547
        if ($debug) {
3548
            error_log('learnpathItem::write_to_db()', 0);
3549
        }
3550
3551
        // Check the session visibility.
3552
        if (!api_is_allowed_to_session_edit()) {
3553
            if ($debug) {
3554
                error_log('return false api_is_allowed_to_session_edit');
3555
            }
3556
3557
            return false;
3558
        }
3559
        if (api_is_invitee()) {
3560
            // If the user is an invitee, we don't write anything to DB
3561
            return true;
3562
        }
3563
3564
        $courseId = api_get_course_int_id();
3565
        $mode = $this->get_lesson_mode();
3566
        $credit = $this->get_credit();
3567
        $total_time = ' ';
3568
        $my_status = ' ';
3569
3570
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3571
        $sql = 'SELECT status, total_time
3572
                FROM '.$item_view_table.'
3573
                WHERE
3574
                    c_id = '.$courseId.' AND
3575
                    lp_item_id="'.$this->db_id.'" AND
3576
                    lp_view_id="'.$this->view_id.'" AND
3577
                    view_count="'.$this->get_attempt_id().'" ';
3578
        $rs_verified = Database::query($sql);
3579
        $row_verified = Database::fetch_array($rs_verified);
3580
3581
        $my_case_completed = [
3582
            'completed',
3583
            'passed',
3584
            'browsed',
3585
            'failed',
3586
        ];
3587
3588
        $oldTotalTime = $row_verified['total_time'];
3589
        $this->oldTotalTime = $oldTotalTime;
3590
3591
        $save = true;
3592
        if (isset($row_verified) && isset($row_verified['status'])) {
3593
            if (in_array($row_verified['status'], $my_case_completed)) {
3594
                $save = false;
3595
            }
3596
        }
3597
3598
        if (((false === $save && 'sco' == $this->type) ||
3599
           ('sco' == $this->type && ('no-credit' == $credit || 'review' == $mode || 'browse' == $mode))) &&
3600
           (1 != $this->seriousgame_mode && 'sco' == $this->type)
3601
        ) {
3602
            if ($debug) {
3603
                error_log(
3604
                    "This info shouldn't be saved as the credit or lesson mode info prevent it"
3605
                );
3606
                error_log(
3607
                    'learnpathItem::write_to_db() - credit('.$credit.') or'.
3608
                    ' lesson_mode('.$mode.') prevent recording!',
3609
                    0
3610
                );
3611
            }
3612
        } else {
3613
            // Check the row exists.
3614
            $inserted = false;
3615
            // This a special case for multiple attempts and Chamilo exercises.
3616
            if ('quiz' == $this->type &&
3617
                0 == $this->get_prevent_reinit() &&
3618
                'completed' == $this->get_status()
3619
            ) {
3620
                // We force the item to be restarted.
3621
                $this->restart();
3622
                $params = [
3623
                    "c_id" => $courseId,
3624
                    "total_time" => $this->get_total_time(),
3625
                    "start_time" => $this->current_start_time,
3626
                    "score" => $this->get_score(),
3627
                    "status" => $this->get_status(false),
3628
                    "max_score" => $this->get_max(),
3629
                    "lp_item_id" => $this->db_id,
3630
                    "lp_view_id" => $this->view_id,
3631
                    "view_count" => $this->get_attempt_id(),
3632
                    "suspend_data" => $this->current_data,
3633
                    //"max_time_allowed" => ,
3634
                    "lesson_location" => $this->lesson_location,
3635
                ];
3636
                if ($debug) {
3637
                    error_log(
3638
                        'learnpathItem::write_to_db() - Inserting into item_view forced: '.print_r($params, 1),
3639
                        0
3640
                    );
3641
                }
3642
                $this->db_item_view_id = Database::insert($item_view_table, $params);
3643
                if ($this->db_item_view_id) {
3644
                    $sql = "UPDATE $item_view_table SET id = iid
3645
                            WHERE iid = ".$this->db_item_view_id;
3646
                    Database::query($sql);
3647
                    $inserted = true;
3648
                }
3649
            }
3650
3651
            $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3652
            $sql = "SELECT * FROM $item_view_table
3653
                    WHERE
3654
                        c_id = $courseId AND
3655
                        lp_item_id = ".$this->db_id." AND
3656
                        lp_view_id = ".$this->view_id." AND
3657
                        view_count = ".$this->get_attempt_id();
3658
            if ($debug) {
3659
                error_log('learnpathItem::write_to_db() - Querying item_view: '.$sql);
3660
            }
3661
3662
            $check_res = Database::query($sql);
3663
            // Depending on what we want (really), we'll update or insert a new row
3664
            // now save into DB.
3665
            if (!$inserted && Database::num_rows($check_res) < 1) {
3666
                $params = [
3667
                    "c_id" => $courseId,
3668
                    "total_time" => $this->get_total_time(),
3669
                    "start_time" => $this->current_start_time,
3670
                    "score" => $this->get_score(),
3671
                    "status" => $this->get_status(false),
3672
                    "max_score" => $this->get_max(),
3673
                    "lp_item_id" => $this->db_id,
3674
                    "lp_view_id" => $this->view_id,
3675
                    "view_count" => $this->get_attempt_id(),
3676
                    "suspend_data" => $this->current_data,
3677
                    //"max_time_allowed" => ,$this->get_max_time_allowed()
3678
                    "lesson_location" => $this->lesson_location,
3679
                ];
3680
3681
                if ($debug) {
3682
                    error_log(
3683
                        'learnpathItem::write_to_db() - Inserting into item_view forced: '.print_r($params, 1),
3684
                        0
3685
                    );
3686
                }
3687
3688
                $this->db_item_view_id = Database::insert($item_view_table, $params);
3689
                if ($this->db_item_view_id) {
3690
                    $sql = "UPDATE $item_view_table SET id = iid
3691
                            WHERE iid = ".$this->db_item_view_id;
3692
                    Database::query($sql);
3693
                }
3694
            } else {
3695
                if ('hotpotatoes' === $this->type) {
3696
                    $params = [
3697
                        'total_time' => $this->get_total_time(),
3698
                        'start_time' => $this->get_current_start_time(),
3699
                        'score' => $this->get_score(),
3700
                        'status' => $this->get_status(false),
3701
                        'max_score' => $this->get_max(),
3702
                        'suspend_data' => $this->current_data,
3703
                        'lesson_location' => $this->lesson_location,
3704
                    ];
3705
                    $where = [
3706
                        'c_id = ? AND lp_item_id = ? AND lp_view_id = ? AND view_count = ?' => [
3707
                            $courseId,
3708
                            $this->db_id,
3709
                            $this->view_id,
3710
                            $this->get_attempt_id(),
3711
                        ],
3712
                    ];
3713
                    Database::update($item_view_table, $params, $where);
3714
                } else {
3715
                    // For all other content types...
3716
                    if ('quiz' === $this->type) {
3717
                        if ($debug) {
3718
                            error_log("item is quiz:");
3719
                        }
3720
                        $my_status = ' ';
3721
                        $total_time = ' ';
3722
                        if (!empty($_REQUEST['exeId'])) {
3723
                            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3724
                            $exeId = (int) $_REQUEST['exeId'];
3725
                            $sql = "SELECT exe_duration
3726
                                    FROM $table
3727
                                    WHERE exe_id = $exeId";
3728
                            $res = Database::query($sql);
3729
                            $exeRow = Database::fetch_array($res);
3730
                            $duration = $exeRow['exe_duration'];
3731
                            $total_time = " total_time = ".$duration.", ";
3732
                            if ($debug) {
3733
                                error_log("quiz: $total_time");
3734
                            }
3735
                        }
3736
                    } else {
3737
                        $my_type_lp = learnpath::get_type_static($this->lp_id);
3738
                        // This is a array containing values finished
3739
                        $case_completed = [
3740
                            'completed',
3741
                            'passed',
3742
                            'browsed',
3743
                            'failed',
3744
                        ];
3745
3746
                        // Is not multiple attempts
3747
                        if (1 == $this->seriousgame_mode && 'sco' == $this->type) {
3748
                            $total_time = " total_time = total_time +".$this->get_total_time().", ";
3749
                            $my_status = " status = '".$this->get_status(false)."' ,";
3750
                            if ($debug) {
3751
                                error_log("seriousgame_mode time changed: $total_time");
3752
                            }
3753
                        } elseif (1 == $this->get_prevent_reinit()) {
3754
                            // Process of status verified into data base.
3755
                            $sql = 'SELECT status FROM '.$item_view_table.'
3756
                                    WHERE
3757
                                        c_id = '.$courseId.' AND
3758
                                        lp_item_id="'.$this->db_id.'" AND
3759
                                        lp_view_id="'.$this->view_id.'" AND
3760
                                        view_count="'.$this->get_attempt_id().'"
3761
                                    ';
3762
                            $rs_verified = Database::query($sql);
3763
                            $row_verified = Database::fetch_array($rs_verified);
3764
3765
                            // Get type lp: 1=lp dokeos and  2=scorm.
3766
                            // If not is completed or passed or browsed and learning path is scorm.
3767
                            if (!in_array($this->get_status(false), $case_completed) &&
3768
                                2 == $my_type_lp
3769
                            ) {
3770
                                $total_time = " total_time = total_time +".$this->get_total_time().", ";
3771
                                $my_status = " status = '".$this->get_status(false)."' ,";
3772
                                if ($debug) {
3773
                                    error_log("get_prevent_reinit = 1 time changed: $total_time");
3774
                                }
3775
                            } else {
3776
                                // Verified into database.
3777
                                if (!in_array($row_verified['status'], $case_completed) &&
3778
                                    2 == $my_type_lp
3779
                                ) {
3780
                                    $total_time = " total_time = total_time +".$this->get_total_time().", ";
3781
                                    $my_status = " status = '".$this->get_status(false)."' ,";
3782
                                    if ($debug) {
3783
                                        error_log("total_time time changed case 1: $total_time");
3784
                                    }
3785
                                } elseif (in_array($row_verified['status'], $case_completed) &&
3786
                                    2 == $my_type_lp && 'sco' != $this->type
3787
                                ) {
3788
                                    $total_time = " total_time = total_time +".$this->get_total_time().", ";
3789
                                    $my_status = " status = '".$this->get_status(false)."' ,";
3790
                                    if ($debug) {
3791
                                        error_log("total_time time changed case 2: $total_time");
3792
                                    }
3793
                                } else {
3794
                                    if ((3 == $my_type_lp && 'au' == $this->type) ||
3795
                                        (1 == $my_type_lp && 'dir' != $this->type)) {
3796
                                        // Is AICC or Chamilo LP
3797
                                        $total_time = " total_time = total_time + ".$this->get_total_time().", ";
3798
                                        $my_status = " status = '".$this->get_status(false)."' ,";
3799
                                        if ($debug) {
3800
                                            error_log("total_time time changed case 3: $total_time");
3801
                                        }
3802
                                    }
3803
                                }
3804
                            }
3805
                        } else {
3806
                            // Multiple attempts are allowed.
3807
                            if (in_array($this->get_status(false), $case_completed) && 2 == $my_type_lp) {
3808
                                // Reset zero new attempt ?
3809
                                $my_status = " status = '".$this->get_status(false)."' ,";
3810
                                if ($debug) {
3811
                                    error_log("total_time time changed Multiple attempt case 1: $total_time");
3812
                                }
3813
                            } elseif (!in_array($this->get_status(false), $case_completed) && 2 == $my_type_lp) {
3814
                                $total_time = " total_time = ".$this->get_total_time().", ";
3815
                                $my_status = " status = '".$this->get_status(false)."' ,";
3816
                                if ($debug) {
3817
                                    error_log("total_time time changed Multiple attempt case 2: $total_time");
3818
                                }
3819
                            } else {
3820
                                // It is chamilo LP.
3821
                                $total_time = " total_time = total_time +".$this->get_total_time().", ";
3822
                                $my_status = " status = '".$this->get_status(false)."' ,";
3823
                                if ($debug) {
3824
                                    error_log("total_time time changed Multiple attempt case 3: $total_time");
3825
                                }
3826
                            }
3827
3828
                            // This code line fixes the problem of wrong status.
3829
                            if (2 == $my_type_lp) {
3830
                                // Verify current status in multiples attempts.
3831
                                $sql = 'SELECT status FROM '.$item_view_table.'
3832
                                        WHERE
3833
                                            c_id = '.$courseId.' AND
3834
                                            lp_item_id="'.$this->db_id.'" AND
3835
                                            lp_view_id="'.$this->view_id.'" AND
3836
                                            view_count="'.$this->get_attempt_id().'" ';
3837
                                $rs_status = Database::query($sql);
3838
                                $current_status = Database::result($rs_status, 0, 'status');
3839
                                if (in_array($current_status, $case_completed)) {
3840
                                    $my_status = '';
3841
                                    $total_time = '';
3842
                                } else {
3843
                                    $total_time = " total_time = total_time + ".$this->get_total_time().", ";
3844
                                }
3845
3846
                                if ($debug) {
3847
                                    error_log("total_time time my_type_lp: $total_time");
3848
                                }
3849
                            }
3850
                        }
3851
                    }
3852
3853
                    if ('sco' === $this->type) {
3854
                        //IF scorm scorm_update_time has already updated total_time in db
3855
                        //" . //start_time = ".$this->get_current_start_time().", " . //scorm_init_time does it
3856
                        ////" max_time_allowed = '".$this->get_max_time_allowed()."'," .
3857
                        $sql = "UPDATE $item_view_table SET
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
                    } else {
3869
                        //" max_time_allowed = '".$this->get_max_time_allowed()."'," .
3870
                        $sql = "UPDATE $item_view_table SET
3871
                                    $total_time
3872
                                    start_time = ".$this->get_current_start_time().",
3873
                                    score = ".$this->get_score().",
3874
                                    $my_status
3875
                                    max_score = '".$this->get_max()."',
3876
                                    suspend_data = '".Database::escape_string($this->current_data)."',
3877
                                    lesson_location = '".$this->lesson_location."'
3878
                                WHERE
3879
                                    c_id = $courseId AND
3880
                                    lp_item_id = ".$this->db_id." AND
3881
                                    lp_view_id = ".$this->view_id." AND
3882
                                    view_count = ".$this->get_attempt_id();
3883
                    }
3884
                    $this->current_start_time = time();
3885
                }
3886
                if ($debug) {
3887
                    error_log('-------------------------------------------');
3888
                    error_log('learnpathItem::write_to_db() - Updating item_view:');
3889
                    error_log($sql);
3890
                    error_log('-------------------------------------------');
3891
                }
3892
                Database::query($sql);
3893
            }
3894
3895
            if (is_array($this->interactions) &&
3896
                count($this->interactions) > 0
3897
            ) {
3898
                // Save interactions.
3899
                $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3900
                $sql = "SELECT iid FROM $tbl
3901
                        WHERE
3902
                            c_id = $courseId AND
3903
                            lp_item_id = ".$this->db_id." AND
3904
                            lp_view_id = ".$this->view_id." AND
3905
                            view_count = ".$this->get_attempt_id();
3906
                $res = Database::query($sql);
3907
                if (Database::num_rows($res) > 0) {
3908
                    $row = Database::fetch_array($res);
3909
                    $lp_iv_id = $row[0];
3910
                    if ($debug) {
3911
                        error_log(
3912
                            'learnpathItem::write_to_db() - Got item_view_id '.
3913
                            $lp_iv_id.', now checking interactions ',
3914
                            0
3915
                        );
3916
                    }
3917
                    foreach ($this->interactions as $index => $interaction) {
3918
                        $correct_resp = '';
3919
                        if (is_array($interaction[4]) && !empty($interaction[4][0])) {
3920
                            foreach ($interaction[4] as $resp) {
3921
                                $correct_resp .= $resp.',';
3922
                            }
3923
                            $correct_resp = substr(
3924
                                $correct_resp,
3925
                                0,
3926
                                strlen($correct_resp) - 1
3927
                            );
3928
                        }
3929
                        $iva_table = Database::get_course_table(
3930
                            TABLE_LP_IV_INTERACTION
3931
                        );
3932
3933
                        //also check for the interaction ID as it must be unique for this SCO view
3934
                        $iva_sql = "SELECT iid FROM $iva_table
3935
                                    WHERE
3936
                                        c_id = $courseId AND
3937
                                        lp_iv_id = $lp_iv_id AND
3938
                                        (
3939
                                            order_id = $index OR
3940
                                            interaction_id = '".Database::escape_string($interaction[0])."'
3941
                                        )
3942
                                    ";
3943
                        $iva_res = Database::query($iva_sql);
3944
3945
                        $interaction[0] = isset($interaction[0]) ? $interaction[0] : '';
3946
                        $interaction[1] = isset($interaction[1]) ? $interaction[1] : '';
3947
                        $interaction[2] = isset($interaction[2]) ? $interaction[2] : '';
3948
                        $interaction[3] = isset($interaction[3]) ? $interaction[3] : '';
3949
                        $interaction[4] = isset($interaction[4]) ? $interaction[4] : '';
3950
                        $interaction[5] = isset($interaction[5]) ? $interaction[5] : '';
3951
                        $interaction[6] = isset($interaction[6]) ? $interaction[6] : '';
3952
                        $interaction[7] = isset($interaction[7]) ? $interaction[7] : '';
3953
3954
                        // id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7)
3955
                        if (Database::num_rows($iva_res) > 0) {
3956
                            // Update (or don't).
3957
                            $iva_row = Database::fetch_array($iva_res);
3958
                            $iva_id = $iva_row[0];
3959
                            // Insert new one.
3960
                            $params = [
3961
                                'interaction_id' => $interaction[0],
3962
                                'interaction_type' => $interaction[1],
3963
                                'weighting' => $interaction[3],
3964
                                'completion_time' => $interaction[2],
3965
                                'correct_responses' => $correct_resp,
3966
                                'student_response' => $interaction[5],
3967
                                'result' => $interaction[6],
3968
                                'latency' => $interaction[7],
3969
                            ];
3970
                            Database::update(
3971
                                $iva_table,
3972
                                $params,
3973
                                [
3974
                                    'c_id = ? AND iid = ?' => [
3975
                                        $courseId,
3976
                                        $iva_id,
3977
                                    ],
3978
                                ]
3979
                            );
3980
                        } else {
3981
                            // Insert new one.
3982
                            $params = [
3983
                                'c_id' => $courseId,
3984
                                'order_id' => $index,
3985
                                'lp_iv_id' => $lp_iv_id,
3986
                                'interaction_id' => $interaction[0],
3987
                                'interaction_type' => $interaction[1],
3988
                                'weighting' => $interaction[3],
3989
                                'completion_time' => $interaction[2],
3990
                                'correct_responses' => $correct_resp,
3991
                                'student_response' => $interaction[5],
3992
                                'result' => $interaction[6],
3993
                                'latency' => $interaction[7],
3994
                            ];
3995
3996
                            $insertId = Database::insert($iva_table, $params);
3997
                            if ($insertId) {
3998
                                $sql = "UPDATE $iva_table SET id = iid
3999
                                        WHERE iid = $insertId";
4000
                                Database::query($sql);
4001
                            }
4002
                        }
4003
                    }
4004
                }
4005
            }
4006
        }
4007
4008
        if ($debug) {
4009
            error_log('End of learnpathItem::write_to_db()', 0);
4010
        }
4011
4012
        return true;
4013
    }
4014
4015
    /**
4016
     * Adds an audio file attached to the current item (store on disk and in db).
4017
     *
4018
     * @return bool|string|null
4019
     */
4020
    public function addAudio()
4021
    {
4022
        $course_info = api_get_course_info();
4023
        $filepath = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document/';
4024
4025
        if (!is_dir($filepath.'audio')) {
4026
            mkdir(
4027
                $filepath.'audio',
4028
                api_get_permissions_for_new_directories()
4029
            );
4030
            DocumentManager::addDocument(
4031
                $course_info,
4032
                '/audio',
4033
                'folder',
4034
                0,
4035
                'audio'
4036
            );
4037
        }
4038
4039
        $key = 'file';
4040
        if (!isset($_FILES[$key]['name']) || !isset($_FILES[$key]['tmp_name'])) {
4041
            return false;
4042
        }
4043
        $result = DocumentManager::upload_document(
4044
            $_FILES,
4045
            '/audio',
4046
            null,
4047
            null,
4048
            0,
4049
            'rename',
4050
            false,
4051
            false
4052
        );
4053
        $file_path = null;
4054
4055
        if ($result) {
4056
            $file_path = basename($result['path']);
4057
4058
            // Store the mp3 file in the lp_item table.
4059
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4060
            $sql = "UPDATE $tbl_lp_item SET
4061
                        audio = '".Database::escape_string($file_path)."'
4062
                    WHERE iid = ".intval($this->db_id);
4063
            Database::query($sql);
4064
        }
4065
4066
        return $file_path;
4067
    }
4068
4069
    /**
4070
     * Removes the relation between the current item and an audio file. The file
4071
     * is only removed from the lp_item table, but remains in the document table
4072
     * and directory.
4073
     *
4074
     * @return bool
4075
     */
4076
    public function removeAudio()
4077
    {
4078
        $courseInfo = api_get_course_info();
4079
4080
        if (empty($this->db_id) || empty($courseInfo)) {
4081
            return false;
4082
        }
4083
4084
        $table = Database::get_course_table(TABLE_LP_ITEM);
4085
        $sql = "UPDATE $table SET
4086
                audio = ''
4087
                WHERE iid = ".$this->db_id;
4088
        Database::query($sql);
4089
    }
4090
4091
    /**
4092
     * Adds an audio file to the current item, using a file already in documents.
4093
     *
4094
     * @param int $documentId
4095
     *
4096
     * @return string
4097
     */
4098
    public function add_audio_from_documents($documentId)
4099
    {
4100
        $courseInfo = api_get_course_info();
4101
        $documentData = DocumentManager::get_document_data_by_id($documentId, $courseInfo['code']);
4102
4103
        $path = '';
4104
        if (!empty($documentData)) {
4105
            $path = $documentData['path'];
4106
            // Store the mp3 file in the lp_item table.
4107
            $table = Database::get_course_table(TABLE_LP_ITEM);
4108
            $sql = "UPDATE $table SET
4109
                        audio = '".Database::escape_string($path)."'
4110
                    WHERE iid = ".$this->db_id;
4111
            Database::query($sql);
4112
        }
4113
4114
        return $path;
4115
    }
4116
4117
    /**
4118
     * Transform the SCORM status to a string that can be translated by Chamilo
4119
     * in different user languages.
4120
     *
4121
     * @param $status
4122
     * @param bool   $decorate
4123
     * @param string $type     classic|simple
4124
     *
4125
     * @return array|string
4126
     */
4127
    public static function humanize_status($status, $decorate = true, $type = 'classic')
4128
    {
4129
        $statusList = [
4130
            'completed' => 'ScormCompstatus',
4131
            'incomplete' => 'ScormIncomplete',
4132
            'failed' => 'ScormFailed',
4133
            'passed' => 'ScormPassed',
4134
            'browsed' => 'ScormBrowsed',
4135
            'not attempted' => 'ScormNotAttempted',
4136
        ];
4137
4138
        $myLessonStatus = get_lang($statusList[$status]);
4139
4140
        switch ($status) {
4141
            case 'completed':
4142
            case 'browsed':
4143
                $classStatus = 'info';
4144
                break;
4145
            case 'incomplete':
4146
                $classStatus = 'warning';
4147
                break;
4148
            case 'passed':
4149
                $classStatus = 'success';
4150
                break;
4151
            case 'failed':
4152
                $classStatus = 'important';
4153
                break;
4154
            default:
4155
                $classStatus = 'default';
4156
                break;
4157
        }
4158
4159
        if ('simple' === $type) {
4160
            if (in_array($status, ['failed', 'passed', 'browsed'])) {
4161
                $myLessonStatus = get_lang('Incomplete');
4162
4163
                $classStatus = 'warning';
4164
            }
4165
        }
4166
4167
        if ($decorate) {
4168
            return Display::label($myLessonStatus, $classStatus);
4169
        }
4170
4171
        return $myLessonStatus;
4172
    }
4173
4174
    /**
4175
     * @return float
4176
     */
4177
    public function getPrerequisiteMaxScore()
4178
    {
4179
        return $this->prerequisiteMaxScore;
4180
    }
4181
4182
    /**
4183
     * @param float $prerequisiteMaxScore
4184
     */
4185
    public function setPrerequisiteMaxScore($prerequisiteMaxScore)
4186
    {
4187
        $this->prerequisiteMaxScore = $prerequisiteMaxScore;
4188
    }
4189
4190
    /**
4191
     * @return float
4192
     */
4193
    public function getPrerequisiteMinScore()
4194
    {
4195
        return $this->prerequisiteMinScore;
4196
    }
4197
4198
    /**
4199
     * @param float $prerequisiteMinScore
4200
     */
4201
    public function setPrerequisiteMinScore($prerequisiteMinScore)
4202
    {
4203
        $this->prerequisiteMinScore = $prerequisiteMinScore;
4204
    }
4205
4206
    /**
4207
     * Check if this LP item has a created thread in the basis course from the forum of its LP.
4208
     *
4209
     * @param int $lpCourseId The course ID
4210
     *
4211
     * @return bool
4212
     */
4213
    public function lpItemHasThread($lpCourseId)
4214
    {
4215
        $forumThreadTable = Database::get_course_table(TABLE_FORUM_THREAD);
4216
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
4217
4218
        $fakeFrom = "
4219
            $forumThreadTable ft
4220
            INNER JOIN $itemProperty ip
4221
            ON (ft.thread_id = ip.ref AND ft.c_id = ip.c_id)
4222
        ";
4223
4224
        $resultData = Database::select(
4225
            'COUNT(ft.iid) AS qty',
4226
            $fakeFrom,
4227
            [
4228
                'where' => [
4229
                    'ip.visibility != ? AND ' => 2,
4230
                    'ip.tool = ? AND ' => TOOL_FORUM_THREAD,
4231
                    'ft.c_id = ? AND ' => intval($lpCourseId),
4232
                    '(ft.lp_item_id = ? OR (ft.thread_title = ? AND ft.lp_item_id = ?))' => [
4233
                        intval($this->db_id),
4234
                        "{$this->title} - {$this->db_id}",
4235
                        intval($this->db_id),
4236
                    ],
4237
                ],
4238
            ],
4239
            'first'
4240
        );
4241
4242
        if ($resultData['qty'] > 0) {
4243
            return true;
4244
        }
4245
4246
        return false;
4247
    }
4248
4249
    /**
4250
     * Get the forum thread info.
4251
     *
4252
     * @param int $courseId  The course ID from the learning path
4253
     * @param int $sessionId Optional. The session ID from the learning path
4254
     *
4255
     * @return \Chamilo\CourseBundle\Entity\CForumThread
4256
     */
4257
    public function getForumThread($courseId, $sessionId = 0)
4258
    {
4259
        $repo = Container::getForumThreadRepository();
4260
        $qb = $repo->getResourcesByCourse(api_get_course_entity($courseId), api_get_session_entity($sessionId));
4261
        $qb->andWhere('resource.threadTitle = :title')->setParameter('title', "{$this->title} - {$this->db_id}");
4262
4263
        return $qb->getQuery()->getFirstResult();
4264
4265
        $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...
4266
        $lpCourseId = (int) $lpCourseId;
4267
4268
        $forumThreadTable = Database::get_course_table(TABLE_FORUM_THREAD);
4269
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
4270
4271
        $fakeFrom = "$forumThreadTable ft INNER JOIN $itemProperty ip ";
4272
4273
        if (0 == $lpSessionId) {
4274
            $fakeFrom .= "
4275
                ON (
4276
                    ft.thread_id = ip.ref AND ft.c_id = ip.c_id AND (
4277
                        ft.session_id = ip.session_id OR ip.session_id IS NULL
4278
                    )
4279
                )
4280
            ";
4281
        } else {
4282
            $fakeFrom .= "
4283
                ON (
4284
                    ft.thread_id = ip.ref AND ft.c_id = ip.c_id AND ft.session_id = ip.session_id
4285
                )
4286
            ";
4287
        }
4288
4289
        $resultData = Database::select(
4290
            'ft.*',
4291
            $fakeFrom,
4292
            [
4293
                'where' => [
4294
                    'ip.visibility != ? AND ' => 2,
4295
                    'ip.tool = ? AND ' => TOOL_FORUM_THREAD,
4296
                    'ft.session_id = ? AND ' => $lpSessionId,
4297
                    'ft.c_id = ? AND ' => $lpCourseId,
4298
                    '(ft.lp_item_id = ? OR (ft.thread_title = ? AND ft.lp_item_id = ?))' => [
4299
                        intval($this->db_id),
4300
                        "{$this->title} - {$this->db_id}",
4301
                        intval($this->db_id),
4302
                    ],
4303
                ],
4304
            ],
4305
            'first'
4306
        );
4307
4308
        if (empty($resultData)) {
4309
            return false;
4310
        }
4311
4312
        return $resultData;
4313
    }
4314
4315
    /**
4316
     * Create a forum thread for this learning path item.
4317
     *
4318
     * @param int $currentForumId The forum ID to add the new thread
4319
     *
4320
     * @return int The forum thread if was created. Otherwise return false
4321
     */
4322
    public function createForumThread($currentForumId)
4323
    {
4324
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
4325
4326
        $currentForumId = (int) $currentForumId;
4327
4328
        $em = Database::getManager();
4329
        $threadRepo = $em->getRepository('ChamiloCourseBundle:CForumThread');
4330
        $forumThread = $threadRepo->findOneBy([
4331
            'threadTitle' => "{$this->title} - {$this->db_id}",
4332
            'forumId' => $currentForumId,
4333
        ]);
4334
4335
        if (!$forumThread) {
4336
            $repo = Container::getForumRepository();
4337
            $forumInfo = $repo->find($currentForumId);
4338
            store_thread(
4339
                $forumInfo,
4340
                [
4341
                    'forum_id' => $currentForumId,
4342
                    'thread_id' => 0,
4343
                    'gradebook' => 0,
4344
                    'post_title' => "{$this->name} - {$this->db_id}",
4345
                    'post_text' => $this->description,
4346
                    'category_id' => 1,
4347
                    'numeric_calification' => 0,
4348
                    'calification_notebook_title' => 0,
4349
                    'weight_calification' => 0.00,
4350
                    'thread_peer_qualify' => 0,
4351
                    'lp_item_id' => $this->db_id,
4352
                ],
4353
                [],
4354
                false
4355
            );
4356
4357
            return;
4358
        }
4359
4360
        $forumThread->setLpItemId($this->db_id);
4361
4362
        $em->persist($forumThread);
4363
        $em->flush();
4364
    }
4365
4366
    /**
4367
     * Allow dissociate a forum to this LP item.
4368
     *
4369
     * @param int $threadIid The thread id
4370
     *
4371
     * @return bool
4372
     */
4373
    public function dissociateForumThread($threadIid)
4374
    {
4375
        $threadIid = (int) $threadIid;
4376
        $em = Database::getManager();
4377
4378
        $forumThread = $em->find('ChamiloCourseBundle:CForumThread', $threadIid);
4379
4380
        if (!$forumThread) {
4381
            return false;
4382
        }
4383
4384
        $forumThread->setThreadTitle("{$this->get_title()} - {$this->db_id}");
4385
        $forumThread->setLpItemId(0);
4386
4387
        $em->persist($forumThread);
4388
        $em->flush();
4389
4390
        return true;
4391
    }
4392
4393
    /**
4394
     * @return int
4395
     */
4396
    public function getLastScormSessionTime()
4397
    {
4398
        return $this->last_scorm_session_time;
4399
    }
4400
4401
    /**
4402
     * @return int
4403
     */
4404
    public function getIid()
4405
    {
4406
        return $this->iId;
4407
    }
4408
4409
    /**
4410
     * @param int    $user_id
4411
     * @param string $prereqs_string
4412
     * @param array  $refs_list
4413
     *
4414
     * @return bool
4415
     */
4416
    public function getStatusFromOtherSessions($user_id, $prereqs_string, $refs_list)
4417
    {
4418
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
4419
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
4420
        $courseId = api_get_course_int_id();
4421
        $user_id = (int) $user_id;
4422
4423
        // Check results from another sessions:
4424
        $checkOtherSessions = api_get_configuration_value('validate_lp_prerequisite_from_other_session');
4425
        if ($checkOtherSessions) {
4426
            // Check items
4427
            $sql = "SELECT iid FROM $lp_view
4428
                    WHERE
4429
                        c_id = $courseId AND
4430
                        user_id = $user_id  AND
4431
                        lp_id = $this->lp_id AND
4432
                        session_id <> 0
4433
                    ";
4434
            $result = Database::query($sql);
4435
            $resultFromOtherSessions = false;
4436
            while ($row = Database::fetch_array($result)) {
4437
                $lpIid = $row['iid'];
4438
                $sql = "SELECT status FROM $lp_item_view
4439
                        WHERE
4440
                            c_id = $courseId AND
4441
                            lp_view_id = $lpIid AND
4442
                            lp_item_id = $refs_list[$prereqs_string]
4443
                        LIMIT 1";
4444
                $resultRow = Database::query($sql);
4445
                if (Database::num_rows($resultRow)) {
4446
                    $statusResult = Database::fetch_array($resultRow);
4447
                    $status = $statusResult['status'];
4448
                    $checked = $status == $this->possible_status[2] || $status == $this->possible_status[3];
4449
                    if ($checked) {
4450
                        $resultFromOtherSessions = true;
4451
                        break;
4452
                    }
4453
                }
4454
            }
4455
4456
            return $resultFromOtherSessions;
4457
        }
4458
    }
4459
}
4460