Passed
Push — master ( 784f8c...891151 )
by Julito
11:16
created

learnpathItem::fixAbusiveTime()   B

Complexity

Conditions 9
Paths 28

Size

Total Lines 64
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 30
nc 28
nop 1
dl 0
loc 64
rs 8.0555
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

    return false;
}

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

Loading history...
4311
        $lpCourseId = (int) $lpCourseId;
4312
4313
        $forumThreadTable = Database::get_course_table(TABLE_FORUM_THREAD);
4314
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
4315
4316
        $fakeFrom = "$forumThreadTable ft INNER JOIN $itemProperty ip ";
4317
4318
        if (0 == $lpSessionId) {
4319
            $fakeFrom .= "
4320
                ON (
4321
                    ft.thread_id = ip.ref AND ft.c_id = ip.c_id AND (
4322
                        ft.session_id = ip.session_id OR ip.session_id IS NULL
4323
                    )
4324
                )
4325
            ";
4326
        } else {
4327
            $fakeFrom .= "
4328
                ON (
4329
                    ft.thread_id = ip.ref AND ft.c_id = ip.c_id AND ft.session_id = ip.session_id
4330
                )
4331
            ";
4332
        }
4333
4334
        $resultData = Database::select(
4335
            'ft.*',
4336
            $fakeFrom,
4337
            [
4338
                'where' => [
4339
                    'ip.visibility != ? AND ' => 2,
4340
                    'ip.tool = ? AND ' => TOOL_FORUM_THREAD,
4341
                    'ft.session_id = ? AND ' => $lpSessionId,
4342
                    'ft.c_id = ? AND ' => $lpCourseId,
4343
                    '(ft.lp_item_id = ? OR (ft.thread_title = ? AND ft.lp_item_id = ?))' => [
4344
                        intval($this->db_id),
4345
                        "{$this->title} - {$this->db_id}",
4346
                        intval($this->db_id),
4347
                    ],
4348
                ],
4349
            ],
4350
            'first'
4351
        );
4352
4353
        if (empty($resultData)) {
4354
            return false;
4355
        }
4356
4357
        return $resultData;
4358
    }
4359
4360
    /**
4361
     * Create a forum thread for this learning path item.
4362
     *
4363
     * @return int The forum thread if was created. Otherwise return false
4364
     */
4365
    public function createForumThread(CForumForum $forum)
4366
    {
4367
        $em = Database::getManager();
4368
        $threadRepo = Container::getForumThreadRepository();
4369
        $forumThread = $threadRepo->findOneBy([
4370
            'threadTitle' => "{$this->title} - {$this->db_id}",
4371
            'forum' => $forum,
4372
        ]);
4373
4374
        if (null === $forumThread) {
4375
            saveThread(
4376
                $forum,
4377
                [
4378
                    'thread_id' => 0,
4379
                    'gradebook' => 0,
4380
                    'post_title' => "{$this->name} - {$this->db_id}",
4381
                    'post_text' => $this->description,
4382
                    'category_id' => 1,
4383
                    'numeric_calification' => 0,
4384
                    'calification_notebook_title' => 0,
4385
                    'weight_calification' => 0.00,
4386
                    'thread_peer_qualify' => 0,
4387
                    'lp_item_id' => $this->db_id,
4388
                ],
4389
                [],
4390
                false
4391
            );
4392
4393
            return;
4394
        }
4395
4396
        $forumThread->setLpItemId($this->db_id);
4397
4398
        $em->persist($forumThread);
4399
        $em->flush();
4400
    }
4401
4402
    /**
4403
     * Allow dissociate a forum to this LP item.
4404
     *
4405
     * @param int $threadIid The thread id
4406
     *
4407
     * @return bool
4408
     */
4409
    public function dissociateForumThread($threadIid)
4410
    {
4411
        $threadIid = (int) $threadIid;
4412
4413
        $repo = Container::getForumThreadRepository();
4414
        $forumThread = $repo->find($threadIid);
4415
4416
        if (!$forumThread) {
4417
            return false;
4418
        }
4419
4420
        $forumThread->setThreadTitle("{$this->get_title()} - {$this->db_id}");
4421
        $forumThread->setLpItemId(0);
4422
        $repo->update($forumThread);
4423
4424
        return true;
4425
    }
4426
4427
    /**
4428
     * @return int
4429
     */
4430
    public function getLastScormSessionTime()
4431
    {
4432
        return $this->last_scorm_session_time;
4433
    }
4434
4435
    /**
4436
     * @return int
4437
     */
4438
    public function getIid()
4439
    {
4440
        return $this->iId;
4441
    }
4442
4443
    /**
4444
     * @param int    $user_id
4445
     * @param string $prereqs_string
4446
     * @param array  $refs_list
4447
     *
4448
     * @return bool
4449
     */
4450
    public function getStatusFromOtherSessions($user_id, $prereqs_string, $refs_list)
4451
    {
4452
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
4453
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
4454
        $courseId = api_get_course_int_id();
4455
        $user_id = (int) $user_id;
4456
4457
        // Check results from another sessions:
4458
        $checkOtherSessions = api_get_configuration_value('validate_lp_prerequisite_from_other_session');
4459
        if ($checkOtherSessions) {
4460
            // Check items
4461
            $sql = "SELECT iid FROM $lp_view
4462
                    WHERE
4463
                        c_id = $courseId AND
4464
                        user_id = $user_id  AND
4465
                        lp_id = $this->lp_id AND
4466
                        session_id <> 0
4467
                    ";
4468
            $result = Database::query($sql);
4469
            $resultFromOtherSessions = false;
4470
            while ($row = Database::fetch_array($result)) {
4471
                $lpIid = $row['iid'];
4472
                $sql = "SELECT status FROM $lp_item_view
4473
                        WHERE
4474
                            c_id = $courseId AND
4475
                            lp_view_id = $lpIid AND
4476
                            lp_item_id = $refs_list[$prereqs_string]
4477
                        LIMIT 1";
4478
                $resultRow = Database::query($sql);
4479
                if (Database::num_rows($resultRow)) {
4480
                    $statusResult = Database::fetch_array($resultRow);
4481
                    $status = $statusResult['status'];
4482
                    $checked = $status == $this->possible_status[2] || $status == $this->possible_status[3];
4483
                    if ($checked) {
4484
                        $resultFromOtherSessions = true;
4485
                        break;
4486
                    }
4487
                }
4488
            }
4489
4490
            return $resultFromOtherSessions;
4491
        }
4492
    }
4493
}
4494