Passed
Push — master ( 15378b...a2dd4f )
by Julito
09:24
created

learnpathItem::getStatusFromOtherSessions()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 41
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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