learnpathItem   F
last analyzed

Complexity

Total Complexity 615

Size/Duplication

Total Lines 4249
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 615
eloc 2186
c 1
b 0
f 0
dl 0
loc 4249
rs 0.8

83 Methods

Rating   Name   Duplication   Size   Complexity  
A get_parent() 0 7 2
A get_max_time_allowed() 0 7 2
A get_prereq_string() 0 7 2
A get_path() 0 7 2
A get_min() 0 7 2
A get_id() 0 7 2
A load_interactions() 0 28 3
A add_interaction() 0 6 2
A delete() 0 28 4
A set_level() 0 3 1
A get_core_exit() 0 3 1
A get_objectives_count() 0 8 2
A setPrerequisiteMinScore() 0 3 1
A add_objective() 0 9 3
A get_max() 0 18 6
A getScormTimeFromParameter() 0 14 3
B write_objectives_to_db() 0 73 10
B fixAbusiveTime() 0 64 9
A get_attempt_id() 0 8 2
A get_lesson_location() 0 11 2
A addAudio() 0 67 4
A set_lesson_location() 0 9 2
A get_view_count() 0 7 2
B __construct() 0 78 7
C get_file_path() 0 42 13
A fixAudio() 0 24 5
B close() 0 35 8
F get_resources_from_source() 0 514 61
A get_launch_data() 0 11 2
A update_time() 0 12 3
A getPrerequisiteMinScore() 0 3 1
A get_description() 0 7 2
A get_lesson_mode() 0 12 4
A get_interactions_js_array() 0 22 4
A set_path() 0 4 2
B set_status() 0 32 7
F save() 0 165 39
A setPrerequisiteMaxScore() 0 3 1
B humanize_status() 0 45 9
A output() 0 10 3
A get_title() 0 7 2
A get_mastery_score() 0 7 2
B get_prevent_reinit() 0 36 7
A set_title() 0 4 2
A is_done() 0 14 2
B set_score() 0 37 7
C set_time() 0 53 13
A get_suspend_data() 0 13 2
A removeAudio() 0 13 3
B status_is() 0 39 7
A set_core_exit() 0 15 3
A get_search_did() 0 3 1
A getIid() 0 3 1
A open() 0 26 5
A get_level() 0 7 2
B get_credit() 0 35 7
A get_children() 0 10 3
A set_description() 0 4 2
A get_seriousgame_mode() 0 23 5
B get_scorm_time() 0 53 8
A set_attempt_id() 0 9 3
A set_prevent_reinit() 0 5 2
B set_lp_view() 0 77 9
B getStatusFromOtherSessions() 0 40 6
A getPrerequisiteMaxScore() 0 3 1
A get_score() 0 8 2
A isRestartAllowed() 0 19 6
A getLastScormSessionTime() 0 3 1
A get_type() 0 8 2
A get_terms() 0 9 1
F scorm_update_time() 0 91 14
A get_ref() 0 3 1
B get_total_time() 0 59 10
A add_audio_from_documents() 0 17 2
A set_max_score() 0 9 3
A set_terms() 0 27 4
B restart() 0 49 7
A set_type() 0 4 2
F write_to_db() 0 449 74
B get_status() 0 33 8
A get_current_start_time() 0 7 2
F parse_prereq() 0 651 125
B get_interactions_count() 0 35 6

How to fix   Complexity   

Complex Class

Complex classes like learnpathItem often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use learnpathItem, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CourseBundle\Entity\CLpItem;
6
7
/**
8
 * Class learnpathItem
9
 * lp_item defines items belonging to a learnpath. Each item has a name,
10
 * a score, a use time and additional information that enables tracking a user's
11
 * progress in a learning path.
12
 *
13
 * @author  Yannick Warnier <[email protected]>
14
 */
15
class learnpathItem
16
{
17
    public const DEBUG = 0; // Logging parameter.
18
    public $iId;
19
    public $attempt_id; // Also called "objectives" SCORM-wise.
20
    public $audio; // The path to an audio file (stored in document/audio/).
21
    public $children = []; // Contains the ids of children items.
22
    public $condition; // If this item has a special condition embedded.
23
    public $current_score;
24
    public $current_start_time;
25
    public $current_stop_time;
26
    public $current_data = '';
27
    public $db_id;
28
    public $db_item_view_id = '';
29
    public $description = '';
30
    public $file;
31
    /**
32
     * At the moment, interactions are just an array of arrays with a structure
33
     * of 8 text fields: id(0), type(1), time(2), weighting(3),
34
     * correct_responses(4), student_response(5), result(6), latency(7).
35
     */
36
    public $interactions = [];
37
    public $interactions_count = 0;
38
    public $objectives = [];
39
    public $objectives_count = 0;
40
    public $launch_data = '';
41
    public $lesson_location = '';
42
    public $level = 0;
43
    public $core_exit = '';
44
    public $lp_id;
45
    public $max_score;
46
    public $mastery_score;
47
    public $min_score;
48
    public $max_time_allowed = '';
49
    public $name;
50
    public $next;
51
    public $parent;
52
    public $path; // In some cases the exo_id = exercise_id in courseDb exercices table.
53
    public $possible_status = [
54
        'not attempted',
55
        'incomplete',
56
        'completed',
57
        'passed',
58
        'failed',
59
        'browsed',
60
    ];
61
    public $prereq_string = '';
62
    public $prereq_alert = '';
63
    public $prereqs = [];
64
    public $previous;
65
    public $prevent_reinit = 1; // 0 =  multiple attempts   1 = one attempt
66
    public $seriousgame_mode;
67
    public $ref;
68
    public $save_on_close = true;
69
    public $search_did = null;
70
    public $status;
71
    public $title;
72
    /**
73
     * Type attribute can contain one of
74
     * link|student_publication|dir|quiz|document|forum|thread.
75
     */
76
    public $type;
77
    public $view_id;
78
    public $oldTotalTime;
79
    public $view_max_score;
80
    public $courseInfo;
81
    public $courseId;
82
    //var used if absolute session time mode is used
83
    private $last_scorm_session_time = 0;
84
    private $prerequisiteMaxScore;
85
    private $prerequisiteMinScore;
86
87
    public $display_order;
88
    public $js_lib;
89
    public $author;
90
    public $hide_toc_frame;
91
    public $max_ordered_items;
92
93
    /**
94
     * Prepares the learning path item for later launch.
95
     * Don't forget to use set_lp_view() if applicable after creating the item.
96
     * Setting an lp_view will finalise the item_view data collection.
97
     *
98
     * @param int                $id           Learning path item ID
99
     * @param CLpItem|array|null $item_content Contents of the item
100
     */
101
    public function __construct($id, $item_content = null)
102
    {
103
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
104
        $id = (int) $id;
105
        $this->courseId = api_get_course_int_id();
106
107
        if (!empty($id)) {
108
            $sql = "SELECT * FROM $items_table
109
                    WHERE iid = $id";
110
            $res = Database::query($sql);
111
            if (Database::num_rows($res) < 1) {
112
                $this->error = 'Could not find given learnpath item in learnpath_item table';
113
            }
114
            $row = Database::fetch_array($res);
115
        } else {
116
            if ($item_content instanceof CLpItem) {
117
                $row = [];
118
                $row['lp_id'] = $item_content->getLp()->getIid();
119
                $row['iid'] = $item_content->getIid();
120
                $row['max_score'] = $item_content->getMaxScore();
121
                $row['min_score'] = $item_content->getMinScore();
122
                $row['title'] = $item_content->getTitle();
123
                $row['item_type'] = $item_content->getItemType();
124
                $row['ref'] = $item_content->getRef();
125
                $row['description'] = $item_content->getDescription();
126
                $row['path'] = $item_content->getPath();
127
                $row['mastery_score'] = $item_content->getMasteryScore();
128
                $row['parent_item_id'] = $item_content->getParentItemId();
129
                $row['next_item_id'] = $item_content->getNextItemId();
130
                $row['previous_item_id'] = $item_content->getPreviousItemId();
131
                $row['display_order'] = $item_content->getDisplayOrder();
132
                $row['prerequisite'] = $item_content->getPrerequisite();
133
                $row['max_time_allowed'] = $item_content->getMaxTimeAllowed();
134
                $row['prerequisite_max_score'] = $item_content->getPrerequisiteMaxScore();
135
                $row['prerequisite_min_score'] = $item_content->getPrerequisiteMinScore();
136
                $row['audio'] = $item_content->getAudio();
137
                $row['launch_data'] = $item_content->getLaunchData();
138
            }
139
        }
140
141
        $this->iId = $row['iid'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $row does not seem to be defined for all execution paths leading up to this point.
Loading history...
142
        $this->lp_id = $row['lp_id'];
143
        $this->max_score = $row['max_score'];
144
        $this->min_score = $row['min_score'];
145
        $this->name = $row['title'];
146
        $this->type = $row['item_type'];
147
        $this->ref = $row['ref'];
148
        $this->title = $row['title'];
149
        $this->description = $row['description'];
150
        $this->path = $row['path'];
151
        $this->mastery_score = $row['mastery_score'];
152
        $this->parent = $row['parent_item_id'];
153
        $this->next = $row['next_item_id'];
154
        $this->previous = $row['previous_item_id'];
155
        $this->display_order = $row['display_order'];
156
        $this->prereq_string = $row['prerequisite'];
157
        $this->max_time_allowed = $row['max_time_allowed'];
158
        $this->setPrerequisiteMaxScore($row['prerequisite_max_score']);
159
        $this->setPrerequisiteMinScore($row['prerequisite_min_score']);
160
        $this->oldTotalTime = 0;
161
        $this->view_max_score = 0;
162
        $this->seriousgame_mode = 0;
163
        //$this->audio = self::fixAudio($row['audio']);
164
        $this->audio = $row['audio'];
165
        $this->launch_data = $row['launch_data'];
166
        $this->save_on_close = true;
167
        $this->db_id = $row['iid'];
168
169
        // Load children list
170
        if (!empty($this->lp_id)) {
171
            $sql = "SELECT iid FROM $items_table
172
                    WHERE
173
                        lp_id = ".$this->lp_id." AND
174
                        parent_item_id = $id";
175
            $res = Database::query($sql);
176
            if (Database::num_rows($res) > 0) {
177
                while ($row = Database::fetch_assoc($res)) {
178
                    $this->children[] = $row['iid'];
179
                }
180
            }
181
182
/*
183
	    // Xapian full text search does not work
184
	    // and if the option is activated it generates an error
185
	    // So I comment this part of the code to avoid unnecesary errors
186
            // Get search_did.
187
            if ('true' === api_get_setting('search_enabled')) {
188
                $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
189
                $sql = 'SELECT *
190
                        FROM %s
191
                        WHERE
192
                            course_code=\'%s\' AND
193
                            tool_id=\'%s\' AND
194
                            ref_id_high_level=%s AND
195
                            ref_id_second_level=%d
196
                        LIMIT 1';
197
                // TODO: Verify if it's possible to assume the actual course instead
198
                // of getting it from db.
199
                $sql = sprintf(
200
                    $sql,
201
                    $tbl_se_ref,
202
                    api_get_course_id(),
203
                    TOOL_LEARNPATH,
204
                    $this->lp_id,
205
                    $id
206
                );
207
                $res = Database::query($sql);
208
                if (Database::num_rows($res) > 0) {
209
                    $se_ref = Database::fetch_array($res);
210
                    $this->search_did = (int) $se_ref['search_did'];
211
                }
212
            }
213
*/
214
        }
215
    }
216
217
    public static function fixAudio($audio)
218
    {
219
        $courseInfo = api_get_course_info();
220
        // Do not check in DB as we expect the call to come from the
221
        if (empty($audio) || empty($courseInfo)) {
222
            return '';
223
        }
224
        // learnpath class which should be aware of any fake.
225
        // Old structure
226
        $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
0 ignored issues
show
Bug introduced by
The constant SYS_COURSE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
227
        if (file_exists($file)) {
228
            $audio = '/audio/'.$audio;
229
            $audio = str_replace('//', '/', $audio);
230
231
            return $audio;
232
        }
233
234
        $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
235
236
        if (file_exists($file)) {
237
            return $audio;
238
        }
239
240
        return '';
241
    }
242
243
    /**
244
     * Adds an interaction to the current item.
245
     *
246
     * @param int   $index  Index (order ID) of the interaction inside this item
247
     * @param array $params Array of parameters:
248
     *                      id(0), type(1), time(2), weighting(3), correct_responses(4),
249
     *                      student_response(5), result(6), latency(7)
250
     */
251
    public function add_interaction($index, $params)
252
    {
253
        $this->interactions[$index] = $params;
254
        // Take the current maximum index to generate the interactions_count.
255
        if (($index + 1) > $this->interactions_count) {
256
            $this->interactions_count = $index + 1;
257
        }
258
    }
259
260
    /**
261
     * Adds an objective to the current item.
262
     *
263
     * @param    array    Array of parameters:
264
     * id(0), status(1), score_raw(2), score_max(3), score_min(4)
265
     */
266
    public function add_objective($index, $params)
267
    {
268
        if (empty($params[0])) {
269
            return null;
270
        }
271
        $this->objectives[$index] = $params;
272
        // Take the current maximum index to generate the objectives_count.
273
        if ((count($this->objectives) + 1) > $this->objectives_count) {
274
            $this->objectives_count = (count($this->objectives) + 1);
275
        }
276
    }
277
278
    /**
279
     * Closes/stops the item viewing. Finalises runtime values.
280
     * If required, save to DB.
281
     *
282
     * @param bool $prerequisitesCheck Needed to check if asset can be set as completed or not
283
     *
284
     * @return bool True on success, false otherwise
285
     */
286
    public function close()
287
    {
288
        $debug = self::DEBUG;
289
        $this->current_stop_time = time();
290
        $type = $this->get_type();
291
        if ($debug) {
292
            error_log('Start - learnpathItem:close');
293
            error_log("Type: ".$type);
294
            error_log("get_id: ".$this->get_id());
295
        }
296
        if ('sco' !== $type) {
297
            if (TOOL_QUIZ == $type || TOOL_HOTPOTATOES == $type) {
298
                $this->get_status(
299
                    true,
300
                    true
301
                ); // Update status (second option forces the update).
302
            } else {
303
                /*$this->status = $this->possible_status[2];
304
                if (self::DEBUG) {
305
                    error_log("STATUS changed to: ".$this->status);
306
                }*/
307
            }
308
        }
309
        if ($this->save_on_close) {
310
            if ($debug) {
311
                error_log('save_on_close');
312
            }
313
            $this->save();
314
        }
315
316
        if ($debug) {
317
            error_log('End - learnpathItem:close');
318
        }
319
320
        return true;
321
    }
322
323
    /**
324
     * Deletes all traces of this item in the database.
325
     *
326
     * @return bool true. Doesn't check for errors yet.
327
     */
328
    public function delete()
329
    {
330
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
331
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
332
333
        $sql = "DELETE FROM $lp_item_view
334
                WHERE lp_item_id = ".$this->db_id;
335
        Database::query($sql);
336
337
        $sql = "SELECT * FROM $lp_item
338
                WHERE iid = ".$this->db_id;
339
        $res_sel = Database::query($sql);
340
        if (Database::num_rows($res_sel) < 1) {
341
            return false;
342
        }
343
344
        $sql = "DELETE FROM $lp_item
345
                WHERE iid = ".$this->db_id;
346
        Database::query($sql);
347
348
        if ('true' == api_get_setting('search_enabled')) {
349
            if (!is_null($this->search_did)) {
350
                $di = new ChamiloIndexer();
351
                $di->remove_document($this->search_did);
352
            }
353
        }
354
355
        return true;
356
    }
357
358
    /**
359
     * Gets the current attempt_id for this user on this item.
360
     *
361
     * @return int attempt_id for this item view by this user or 1 if none defined
362
     */
363
    public function get_attempt_id()
364
    {
365
        $res = 1;
366
        if (!empty($this->attempt_id)) {
367
            $res = (int) $this->attempt_id;
368
        }
369
370
        return $res;
371
    }
372
373
    /**
374
     * Gets a list of the item's children.
375
     *
376
     * @return array Array of children items IDs
377
     */
378
    public function get_children()
379
    {
380
        $list = [];
381
        foreach ($this->children as $child) {
382
            if (!empty($child)) {
383
                $list[] = $child;
384
            }
385
        }
386
387
        return $list;
388
    }
389
390
    /**
391
     * Gets the core_exit value from the database.
392
     */
393
    public function get_core_exit()
394
    {
395
        return $this->core_exit;
396
    }
397
398
    /**
399
     * Gets the credit information (rather scorm-stuff) based on current status
400
     * and reinit autorization. Credit tells the sco(content) if Chamilo will
401
     * record the data it is sent (credit) or not (no-credit).
402
     *
403
     * @return string 'credit' or 'no-credit'. Defaults to 'credit'
404
     *                Because if we don't know enough about this item, it's probably because
405
     *                it was never used before.
406
     */
407
    public function get_credit()
408
    {
409
        if (self::DEBUG > 1) {
410
            error_log('learnpathItem::get_credit()', 0);
411
        }
412
        $credit = 'credit';
413
        // Now check the value of prevent_reinit (if it's 0, return credit as
414
        // the default was).
415
        // If prevent_reinit == 1 (or more).
416
        if (0 != $this->get_prevent_reinit()) {
417
            // If status is not attempted or incomplete, credit anyway.
418
            // Otherwise:
419
            // Check the status in the database rather than in the object, as
420
            // checking in the object would always return "no-credit" when we
421
            // want to set it to completed.
422
            $status = $this->get_status(true);
423
            if (self::DEBUG > 2) {
424
                error_log(
425
                    'learnpathItem::get_credit() - get_prevent_reinit!=0 and '.
426
                    'status is '.$status,
427
                    0
428
                );
429
            }
430
            //0=not attempted - 1 = incomplete
431
            if ($status != $this->possible_status[0] &&
432
                $status != $this->possible_status[1]
433
            ) {
434
                $credit = 'no-credit';
435
            }
436
        }
437
        if (self::DEBUG > 1) {
438
            error_log("learnpathItem::get_credit() returns: $credit");
439
        }
440
441
        return $credit;
442
    }
443
444
    /**
445
     * Gets the current start time property.
446
     *
447
     * @return int Current start time, or current time if none
448
     */
449
    public function get_current_start_time()
450
    {
451
        if (empty($this->current_start_time)) {
452
            return time();
453
        }
454
455
        return $this->current_start_time;
456
    }
457
458
    /**
459
     * Gets the item's description.
460
     *
461
     * @return string Description
462
     */
463
    public function get_description()
464
    {
465
        if (empty($this->description)) {
466
            return '';
467
        }
468
469
        return $this->description;
470
    }
471
472
    /**
473
     * Gets the file path from the course's root directory, no matter what
474
     * tool it is from.
475
     *
476
     * @param string $path_to_scorm_dir
477
     *
478
     * @return string The file path, or an empty string if there is no file
479
     *                attached, or '-1' if the file must be replaced by an error page
480
     */
481
    public function get_file_path($path_to_scorm_dir = '')
482
    {
483
        $courseId = $this->courseId;
484
        $path = $this->get_path();
485
        $type = $this->get_type();
486
487
        if (empty($path)) {
488
            if ('dir' == $type) {
489
                return '';
490
            } else {
491
                return '-1';
492
            }
493
        } elseif ($path == strval(intval($path))) {
494
            // The path is numeric, so it is a reference to a Chamilo object.
495
            switch ($type) {
496
                case 'dir':
497
                    return '';
498
                case TOOL_DOCUMENT:
499
                case 'video':
500
                    $table_doc = Database::get_course_table(TABLE_DOCUMENT);
501
                    $sql = 'SELECT path
502
                            FROM '.$table_doc.'
503
                            WHERE iid = '.$path;
504
                    $res = Database::query($sql);
505
                    $row = Database::fetch_array($res);
506
                    $real_path = 'document'.$row['path'];
507
508
                    return $real_path;
509
                case TOOL_STUDENTPUBLICATION:
510
                case TOOL_QUIZ:
511
                case TOOL_FORUM:
512
                case TOOL_THREAD:
513
                case TOOL_LINK:
514
                default:
515
                    return '-1';
516
            }
517
        } else {
518
            if (!empty($path_to_scorm_dir)) {
519
                $path = $path_to_scorm_dir.$path;
520
            }
521
522
            return $path;
523
        }
524
    }
525
526
    /**
527
     * Gets the DB ID.
528
     *
529
     * @return int Database ID for the current item
530
     */
531
    public function get_id()
532
    {
533
        if (!empty($this->db_id)) {
534
            return $this->db_id;
535
        }
536
        // TODO: Check this return value is valid for children classes (SCORM?).
537
        return 0;
538
    }
539
540
    /**
541
     * Loads the interactions into the item object, from the database.
542
     * If object interactions exist, they will be overwritten by this function,
543
     * using the database elements only.
544
     */
545
    public function load_interactions()
546
    {
547
        $this->interactions = [];
548
        $courseId = $this->courseId;
549
        $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
550
        $sql = "SELECT id FROM $tbl
551
                WHERE
552
                    lp_item_id = ".$this->db_id." AND
553
                    lp_view_id = ".$this->view_id." AND
554
                    view_count = ".$this->get_view_count();
555
        $res = Database::query($sql);
556
        if (Database::num_rows($res) > 0) {
557
            $row = Database::fetch_array($res);
558
            $lp_iv_id = $row[0];
559
            $iva_table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
560
            $sql = "SELECT * FROM $iva_table
561
                    WHERE c_id = $courseId AND lp_iv_id = $lp_iv_id ";
562
            $res_sql = Database::query($sql);
563
            while ($row = Database::fetch_array($res_sql)) {
564
                $this->interactions[$row['interaction_id']] = [
565
                    $row['interaction_id'],
566
                    $row['interaction_type'],
567
                    $row['weighting'],
568
                    $row['completion_time'],
569
                    $row['correct_responses'],
570
                    $row['student_responses'],
571
                    $row['result'],
572
                    $row['latency'],
573
                ];
574
            }
575
        }
576
    }
577
578
    /**
579
     * Gets the current count of interactions recorded in the database.
580
     *
581
     * @param bool $checkdb Whether to count from database or not (defaults to no)
582
     *
583
     * @return int The current number of interactions recorder
584
     */
585
    public function get_interactions_count($checkdb = false)
586
    {
587
        $return = 0;
588
        if (api_is_invitee()) {
589
            // If the user is an invitee, we consider there's no interaction
590
            return 0;
591
        }
592
        if ($checkdb) {
593
            $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
594
            $sql = "SELECT iid FROM $tbl
595
                    WHERE
596
                        lp_item_id = ".$this->db_id." AND
597
                        lp_view_id = ".$this->view_id." AND
598
                        view_count = ".$this->get_attempt_id();
599
            $res = Database::query($sql);
600
            if (Database::num_rows($res) > 0) {
601
                $row = Database::fetch_array($res);
602
                $lp_iv_id = $row[0];
603
                $iva_table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
604
                $sql = "SELECT count(iid) as count
605
                        FROM $iva_table
606
                        WHERE lp_iv_id = $lp_iv_id ";
607
                $res_sql = Database::query($sql);
608
                if (Database::num_rows($res_sql) > 0) {
609
                    $row = Database::fetch_array($res_sql);
610
                    $return = (int) $row['count'];
611
                }
612
            }
613
        } else {
614
            if (!empty($this->interactions_count)) {
615
                $return = $this->interactions_count;
616
            }
617
        }
618
619
        return $return;
620
    }
621
622
    /**
623
     * Gets the JavaScript array content to fill the interactions array.
624
     *
625
     * @param bool $checkdb Whether to check directly into the database (default no)
626
     *
627
     * @return string An empty string if no interaction, a JS array definition otherwise
628
     */
629
    public function get_interactions_js_array($checkdb = false)
630
    {
631
        $return = '';
632
        if ($checkdb) {
633
            $this->load_interactions();
634
        }
635
        foreach ($this->interactions as $id => $in) {
636
            $return .= "[
637
                '$id',
638
                '".$in[1]."',
639
                '".$in[2]."',
640
                '".$in[3]."',
641
                '".$in[4]."',
642
                '".$in[5]."',
643
                '".$in[6]."',
644
                '".$in[7]."'],";
645
        }
646
        if (!empty($return)) {
647
            $return = substr($return, 0, -1);
648
        }
649
650
        return $return;
651
    }
652
653
    /**
654
     * Gets the current count of objectives recorded in the database.
655
     *
656
     * @return int The current number of objectives recorder
657
     */
658
    public function get_objectives_count()
659
    {
660
        $res = 0;
661
        if (!empty($this->objectives_count)) {
662
            $res = $this->objectives_count;
663
        }
664
665
        return $res;
666
    }
667
668
    /**
669
     * Gets the launch_data field found in imsmanifests (this is SCORM- or
670
     * AICC-related, really).
671
     *
672
     * @return string Launch data as found in imsmanifest and stored in
673
     *                Chamilo (read only). Defaults to ''.
674
     */
675
    public function get_launch_data()
676
    {
677
        if (!empty($this->launch_data)) {
678
            return str_replace(
679
                ["\r", "\n", "'"],
680
                ['\r', '\n', "\\'"],
681
                $this->launch_data
682
            );
683
        }
684
685
        return '';
686
    }
687
688
    /**
689
     * Gets the lesson location.
690
     *
691
     * @return string lesson location as recorded by the SCORM and AICC
692
     *                elements. Defaults to ''
693
     */
694
    public function get_lesson_location()
695
    {
696
        if (!empty($this->lesson_location)) {
697
            return str_replace(
698
                ["\r", "\n", "'"],
699
                ['\r', '\n', "\\'"],
700
                $this->lesson_location
701
            );
702
        }
703
704
        return '';
705
    }
706
707
    /**
708
     * Gets the lesson_mode (scorm feature, but might be used by aicc as well
709
     * as chamilo paths).
710
     *
711
     * The "browse" mode is not supported yet (because there is no such way of
712
     * seeing a sco in Chamilo)
713
     *
714
     * @return string 'browse','normal' or 'review'. Defaults to 'normal'
715
     */
716
    public function get_lesson_mode()
717
    {
718
        $mode = 'normal';
719
        if (0 != $this->get_prevent_reinit()) {
720
            // If prevent_reinit == 0
721
            $my_status = $this->get_status();
722
            if ($my_status != $this->possible_status[0] && $my_status != $this->possible_status[1]) {
723
                $mode = 'review';
724
            }
725
        }
726
727
        return $mode;
728
    }
729
730
    /**
731
     * Gets the depth level.
732
     *
733
     * @return int Level. Defaults to 0
734
     */
735
    public function get_level()
736
    {
737
        if (empty($this->level)) {
738
            return 0;
739
        }
740
741
        return $this->level;
742
    }
743
744
    /**
745
     * Gets the mastery score.
746
     */
747
    public function get_mastery_score()
748
    {
749
        if (isset($this->mastery_score)) {
750
            return $this->mastery_score;
751
        }
752
753
        return -1;
754
    }
755
756
    /**
757
     * Gets the maximum (score).
758
     *
759
     * @return int Maximum score. Defaults to 100 if nothing else is defined
760
     */
761
    public function get_max()
762
    {
763
        if ('sco' === $this->type) {
764
            if (!empty($this->view_max_score) && $this->view_max_score > 0) {
765
                return $this->view_max_score;
766
            } else {
767
                if (!empty($this->max_score)) {
768
                    return $this->max_score;
769
                }
770
771
                return 100;
772
            }
773
        } else {
774
            if (!empty($this->max_score)) {
775
                return $this->max_score;
776
            }
777
778
            return 100;
779
        }
780
    }
781
782
    /**
783
     * Gets the maximum time allowed for this user in this attempt on this item.
784
     *
785
     * @return string Time string in SCORM format
786
     *                (HH:MM:SS or HH:MM:SS.SS or HHHH:MM:SS.SS)
787
     */
788
    public function get_max_time_allowed()
789
    {
790
        if (!empty($this->max_time_allowed)) {
791
            return $this->max_time_allowed;
792
        }
793
794
        return '';
795
    }
796
797
    /**
798
     * Gets the minimum (score).
799
     *
800
     * @return int Minimum score. Defaults to 0
801
     */
802
    public function get_min()
803
    {
804
        if (!empty($this->min_score)) {
805
            return $this->min_score;
806
        }
807
808
        return 0;
809
    }
810
811
    /**
812
     * Gets the parent ID.
813
     *
814
     * @return int Parent ID. Defaults to null
815
     */
816
    public function get_parent()
817
    {
818
        if (!empty($this->parent)) {
819
            return $this->parent;
820
        }
821
        // TODO: Check this return value is valid for children classes (SCORM?).
822
        return null;
823
    }
824
825
    /**
826
     * Gets the path attribute.
827
     *
828
     * @return string Path. Defaults to ''
829
     */
830
    public function get_path()
831
    {
832
        if (empty($this->path)) {
833
            return '';
834
        }
835
836
        return $this->path;
837
    }
838
839
    /**
840
     * Gets the prerequisites string.
841
     *
842
     * @return string empty string or prerequisites string if defined
843
     */
844
    public function get_prereq_string()
845
    {
846
        if (!empty($this->prereq_string)) {
847
            return $this->prereq_string;
848
        }
849
850
        return '';
851
    }
852
853
    /**
854
     * Gets the prevent_reinit attribute value (and sets it if not set already).
855
     *
856
     * @return int 1 or 0 (defaults to 1)
857
     */
858
    public function get_prevent_reinit()
859
    {
860
        if (self::DEBUG > 2) {
861
            error_log('learnpathItem::get_prevent_reinit()', 0);
862
        }
863
        if (!isset($this->prevent_reinit)) {
864
            if (!empty($this->lp_id)) {
865
                $table = Database::get_course_table(TABLE_LP_MAIN);
866
                $sql = "SELECT prevent_reinit
867
                        FROM $table
868
                        WHERE iid = ".$this->lp_id;
869
                $res = Database::query($sql);
870
                if (Database::num_rows($res) < 1) {
871
                    $this->error = 'Could not find parent learnpath in lp table';
872
                    if (self::DEBUG > 2) {
873
                        error_log(
874
                            'LearnpathItem::get_prevent_reinit() - Returning false',
875
                            0
876
                        );
877
                    }
878
879
                    return false;
880
                } else {
881
                    $row = Database::fetch_array($res);
882
                    $this->prevent_reinit = $row['prevent_reinit'];
883
                }
884
            } else {
885
                // Prevent reinit is always 1 by default - see learnpath.class.php
886
                $this->prevent_reinit = 1;
887
            }
888
        }
889
        if (self::DEBUG > 2) {
890
            error_log('End of learnpathItem::get_prevent_reinit() - Returned '.$this->prevent_reinit);
891
        }
892
893
        return $this->prevent_reinit;
894
    }
895
896
    /**
897
     * Returns 1 if seriousgame_mode is activated, 0 otherwise.
898
     *
899
     * @return int (0 or 1)
900
     *
901
     * @deprecated seriousgame_mode seems not to be used
902
     *
903
     * @author ndiechburg <[email protected]>
904
     */
905
    public function get_seriousgame_mode()
906
    {
907
        if (!isset($this->seriousgame_mode)) {
908
            if (!empty($this->lp_id)) {
909
                $table = Database::get_course_table(TABLE_LP_MAIN);
910
                $sql = "SELECT seriousgame_mode
911
                        FROM $table
912
                        WHERE iid = ".$this->lp_id;
913
                $res = Database::query($sql);
914
                if (Database::num_rows($res) < 1) {
915
                    $this->error = 'Could not find parent learnpath in learnpath table';
916
917
                    return false;
918
                } else {
919
                    $row = Database::fetch_array($res);
920
                    $this->seriousgame_mode = isset($row['seriousgame_mode']) ? $row['seriousgame_mode'] : 0;
921
                }
922
            } else {
923
                $this->seriousgame_mode = 0; //SeriousGame mode is always off by default
924
            }
925
        }
926
927
        return $this->seriousgame_mode;
928
    }
929
930
    /**
931
     * Gets the item's reference column.
932
     *
933
     * @return string The item's reference field (generally used for SCORM identifiers)
934
     */
935
    public function get_ref()
936
    {
937
        return $this->ref;
938
    }
939
940
    /**
941
     * Gets the list of included resources as a list of absolute or relative
942
     * paths of resources included in the current item. This allows for a
943
     * better SCORM export. The list will generally include pictures, flash
944
     * objects, java applets, or any other stuff included in the source of the
945
     * current item. The current item is expected to be an HTML file. If it
946
     * is not, then the function will return and empty list.
947
     *
948
     * @param string $type        (one of the Chamilo tools) - optional (otherwise takes the current item's type)
949
     * @param string $abs_path    absolute file path - optional (otherwise takes the current item's path)
950
     * @param int    $recursivity level of recursivity we're in
951
     *
952
     * @return array List of file paths.
953
     *               An additional field containing 'local' or 'remote' helps determine if
954
     *               the file should be copied into the zip or just linked
955
     */
956
    public function get_resources_from_source(
957
        $type = null,
958
        $abs_path = null,
959
        $recursivity = 1
960
    ) {
961
        $max = 5;
962
        if ($recursivity > $max) {
963
            return [];
964
        }
965
966
        $type = empty($type) ? $this->get_type() : $type;
967
968
        if (!isset($abs_path)) {
969
            $path = $this->get_file_path();
970
            $abs_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.$path;
0 ignored issues
show
Bug introduced by
The constant SYS_COURSE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The function api_get_course_path was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

970
            $abs_path = api_get_path(SYS_COURSE_PATH)./** @scrutinizer ignore-call */ api_get_course_path().'/'.$path;
Loading history...
971
        }
972
973
        $files_list = [];
974
        switch ($type) {
975
            case TOOL_DOCUMENT:
976
            case TOOL_QUIZ:
977
            case 'sco':
978
                // Get the document and, if HTML, open it.
979
                if (!is_file($abs_path)) {
980
                    // The file could not be found.
981
                    return false;
982
                }
983
984
                // for now, read the whole file in one go (that's gonna be
985
                // a problem when the file is too big).
986
                $info = pathinfo($abs_path);
987
                $ext = $info['extension'];
988
989
                switch (strtolower($ext)) {
990
                    case 'html':
991
                    case 'htm':
992
                    case 'shtml':
993
                    case 'css':
994
                        $wantedAttributes = [
995
                            'src',
996
                            'url',
997
                            '@import',
998
                            'href',
999
                            'value',
1000
                        ];
1001
1002
                        // Parse it for included resources.
1003
                        $fileContent = file_get_contents($abs_path);
1004
                        // Get an array of attributes from the HTML source.
1005
                        $attributes = DocumentManager::parse_HTML_attributes(
1006
                            $fileContent,
1007
                            $wantedAttributes
1008
                        );
1009
1010
                        // Look at 'src' attributes in this file
1011
                        foreach ($wantedAttributes as $attr) {
1012
                            if (isset($attributes[$attr])) {
1013
                                // Find which kind of path these are (local or remote).
1014
                                $sources = $attributes[$attr];
1015
1016
                                foreach ($sources as $source) {
1017
                                    // Skip what is obviously not a resource.
1018
                                    if (strpos($source, "+this.")) {
1019
                                        continue;
1020
                                    } // javascript code - will still work unaltered.
1021
                                    if (false === strpos($source, '.')) {
1022
                                        continue;
1023
                                    } // No dot, should not be an external file anyway.
1024
                                    if (strpos($source, 'mailto:')) {
1025
                                        continue;
1026
                                    } // mailto link.
1027
                                    if (strpos($source, ';') &&
1028
                                        !strpos($source, '&amp;')
1029
                                    ) {
1030
                                        continue;
1031
                                    } // Avoid code - that should help.
1032
1033
                                    if ('value' == $attr) {
1034
                                        if (strpos($source, 'mp3file')) {
1035
                                            $files_list[] = [
1036
                                                substr(
1037
                                                    $source,
1038
                                                    0,
1039
                                                    strpos(
1040
                                                        $source,
1041
                                                        '.swf'
1042
                                                    ) + 4
1043
                                                ),
1044
                                                'local',
1045
                                                'abs',
1046
                                            ];
1047
                                            $mp3file = substr(
1048
                                                $source,
1049
                                                strpos(
1050
                                                    $source,
1051
                                                    'mp3file='
1052
                                                ) + 8
1053
                                            );
1054
                                            if ('/' == substr($mp3file, 0, 1)) {
1055
                                                $files_list[] = [
1056
                                                    $mp3file,
1057
                                                    'local',
1058
                                                    'abs',
1059
                                                ];
1060
                                            } else {
1061
                                                $files_list[] = [
1062
                                                    $mp3file,
1063
                                                    'local',
1064
                                                    'rel',
1065
                                                ];
1066
                                            }
1067
                                        } elseif (0 === strpos($source, 'flv=')) {
1068
                                            $source = substr($source, 4);
1069
                                            if (strpos($source, '&') > 0) {
1070
                                                $source = substr(
1071
                                                    $source,
1072
                                                    0,
1073
                                                    strpos($source, '&')
1074
                                                );
1075
                                            }
1076
                                            if (strpos($source, '://') > 0) {
1077
                                                if (false !== strpos($source, api_get_path(WEB_PATH))) {
1078
                                                    // We found the current portal url.
1079
                                                    $files_list[] = [
1080
                                                        $source,
1081
                                                        'local',
1082
                                                        'url',
1083
                                                    ];
1084
                                                } else {
1085
                                                    // We didn't find any trace of current portal.
1086
                                                    $files_list[] = [
1087
                                                        $source,
1088
                                                        'remote',
1089
                                                        'url',
1090
                                                    ];
1091
                                                }
1092
                                            } else {
1093
                                                $files_list[] = [
1094
                                                    $source,
1095
                                                    'local',
1096
                                                    'abs',
1097
                                                ];
1098
                                            }
1099
                                            continue; // Skipping anything else to avoid two entries
1100
                                            //(while the others can have sub-files in their url, flv's can't).
1101
                                        }
1102
                                    }
1103
1104
                                    if (strpos($source, '://') > 0) {
1105
                                        // Cut at '?' in a URL with params.
1106
                                        if (strpos($source, '?') > 0) {
1107
                                            $second_part = substr(
1108
                                                $source,
1109
                                                strpos($source, '?')
1110
                                            );
1111
                                            if (strpos($second_part, '://') > 0) {
1112
                                                // If the second part of the url contains a url too,
1113
                                                // treat the second one before cutting.
1114
                                                $pos1 = strpos(
1115
                                                    $second_part,
1116
                                                    '='
1117
                                                );
1118
                                                $pos2 = strpos(
1119
                                                    $second_part,
1120
                                                    '&'
1121
                                                );
1122
                                                $second_part = substr(
1123
                                                    $second_part,
1124
                                                    $pos1 + 1,
1125
                                                    $pos2 - ($pos1 + 1)
1126
                                                );
1127
                                                if (false !== strpos($second_part, api_get_path(WEB_PATH))) {
1128
                                                    // We found the current portal url.
1129
                                                    $files_list[] = [
1130
                                                        $second_part,
1131
                                                        'local',
1132
                                                        'url',
1133
                                                    ];
1134
                                                    $in_files_list[] = self::get_resources_from_source(
0 ignored issues
show
Bug Best Practice introduced by
The method learnpathItem::get_resources_from_source() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1134
                                                    /** @scrutinizer ignore-call */ 
1135
                                                    $in_files_list[] = self::get_resources_from_source(
Loading history...
1135
                                                        TOOL_DOCUMENT,
1136
                                                        $second_part,
1137
                                                        $recursivity + 1
1138
                                                    );
1139
                                                    if (count($in_files_list) > 0) {
1140
                                                        $files_list = array_merge(
1141
                                                            $files_list,
1142
                                                            $in_files_list
1143
                                                        );
1144
                                                    }
1145
                                                } else {
1146
                                                    // We didn't find any trace of current portal.
1147
                                                    $files_list[] = [
1148
                                                        $second_part,
1149
                                                        'remote',
1150
                                                        'url',
1151
                                                    ];
1152
                                                }
1153
                                            } elseif (strpos($second_part, '=') > 0) {
1154
                                                if ('/' === substr($second_part, 0, 1)) {
1155
                                                    // Link starts with a /,
1156
                                                    // making it absolute (relative to DocumentRoot).
1157
                                                    $files_list[] = [
1158
                                                        $second_part,
1159
                                                        'local',
1160
                                                        'abs',
1161
                                                    ];
1162
                                                    $in_files_list[] = self::get_resources_from_source(
1163
                                                        TOOL_DOCUMENT,
1164
                                                        $second_part,
1165
                                                        $recursivity + 1
1166
                                                    );
1167
                                                    if (count($in_files_list) > 0) {
1168
                                                        $files_list = array_merge(
1169
                                                            $files_list,
1170
                                                            $in_files_list
1171
                                                        );
1172
                                                    }
1173
                                                } elseif (0 === strstr($second_part, '..')) {
1174
                                                    // Link is relative but going back in the hierarchy.
1175
                                                    $files_list[] = [
1176
                                                        $second_part,
1177
                                                        'local',
1178
                                                        'rel',
1179
                                                    ];
1180
                                                    $dir = dirname(
1181
                                                        $abs_path
1182
                                                    );
1183
                                                    $new_abs_path = realpath(
1184
                                                        $dir.'/'.$second_part
1185
                                                    );
1186
                                                    $in_files_list[] = self::get_resources_from_source(
1187
                                                        TOOL_DOCUMENT,
1188
                                                        $new_abs_path,
1189
                                                        $recursivity + 1
1190
                                                    );
1191
                                                    if (count($in_files_list) > 0) {
1192
                                                        $files_list = array_merge(
1193
                                                            $files_list,
1194
                                                            $in_files_list
1195
                                                        );
1196
                                                    }
1197
                                                } else {
1198
                                                    // No starting '/', making it relative to current document's path.
1199
                                                    if ('./' == substr($second_part, 0, 2)) {
1200
                                                        $second_part = substr(
1201
                                                            $second_part,
1202
                                                            2
1203
                                                        );
1204
                                                    }
1205
                                                    $files_list[] = [
1206
                                                        $second_part,
1207
                                                        'local',
1208
                                                        'rel',
1209
                                                    ];
1210
                                                    $dir = dirname(
1211
                                                        $abs_path
1212
                                                    );
1213
                                                    $new_abs_path = realpath(
1214
                                                        $dir.'/'.$second_part
1215
                                                    );
1216
                                                    $in_files_list[] = self::get_resources_from_source(
1217
                                                        TOOL_DOCUMENT,
1218
                                                        $new_abs_path,
1219
                                                        $recursivity + 1
1220
                                                    );
1221
                                                    if (count($in_files_list) > 0) {
1222
                                                        $files_list = array_merge(
1223
                                                            $files_list,
1224
                                                            $in_files_list
1225
                                                        );
1226
                                                    }
1227
                                                }
1228
                                            }
1229
                                            // Leave that second part behind now.
1230
                                            $source = substr(
1231
                                                $source,
1232
                                                0,
1233
                                                strpos($source, '?')
1234
                                            );
1235
                                            if (strpos($source, '://') > 0) {
1236
                                                if (false !== strpos($source, api_get_path(WEB_PATH))) {
1237
                                                    // We found the current portal url.
1238
                                                    $files_list[] = [
1239
                                                        $source,
1240
                                                        'local',
1241
                                                        'url',
1242
                                                    ];
1243
                                                    $in_files_list[] = self::get_resources_from_source(
1244
                                                        TOOL_DOCUMENT,
1245
                                                        $source,
1246
                                                        $recursivity + 1
1247
                                                    );
1248
                                                    if (count($in_files_list) > 0) {
1249
                                                        $files_list = array_merge(
1250
                                                            $files_list,
1251
                                                            $in_files_list
1252
                                                        );
1253
                                                    }
1254
                                                } else {
1255
                                                    // We didn't find any trace of current portal.
1256
                                                    $files_list[] = [
1257
                                                        $source,
1258
                                                        'remote',
1259
                                                        'url',
1260
                                                    ];
1261
                                                }
1262
                                            } else {
1263
                                                // No protocol found, make link local.
1264
                                                if ('/' === substr($source, 0, 1)) {
1265
                                                    // Link starts with a /, making it absolute (relative to DocumentRoot).
1266
                                                    $files_list[] = [
1267
                                                        $source,
1268
                                                        'local',
1269
                                                        'abs',
1270
                                                    ];
1271
                                                    $in_files_list[] = self::get_resources_from_source(
1272
                                                        TOOL_DOCUMENT,
1273
                                                        $source,
1274
                                                        $recursivity + 1
1275
                                                    );
1276
                                                    if (count($in_files_list) > 0) {
1277
                                                        $files_list = array_merge(
1278
                                                            $files_list,
1279
                                                            $in_files_list
1280
                                                        );
1281
                                                    }
1282
                                                } elseif (0 === strstr($source, '..')) {
1283
                                                    // Link is relative but going back in the hierarchy.
1284
                                                    $files_list[] = [
1285
                                                        $source,
1286
                                                        'local',
1287
                                                        'rel',
1288
                                                    ];
1289
                                                    $dir = dirname(
1290
                                                        $abs_path
1291
                                                    );
1292
                                                    $new_abs_path = realpath(
1293
                                                        $dir.'/'.$source
1294
                                                    );
1295
                                                    $in_files_list[] = self::get_resources_from_source(
1296
                                                        TOOL_DOCUMENT,
1297
                                                        $new_abs_path,
1298
                                                        $recursivity + 1
1299
                                                    );
1300
                                                    if (count($in_files_list) > 0) {
1301
                                                        $files_list = array_merge(
1302
                                                            $files_list,
1303
                                                            $in_files_list
1304
                                                        );
1305
                                                    }
1306
                                                } else {
1307
                                                    // No starting '/', making it relative to current document's path.
1308
                                                    if ('./' == substr($source, 0, 2)) {
1309
                                                        $source = substr(
1310
                                                            $source,
1311
                                                            2
1312
                                                        );
1313
                                                    }
1314
                                                    $files_list[] = [
1315
                                                        $source,
1316
                                                        'local',
1317
                                                        'rel',
1318
                                                    ];
1319
                                                    $dir = dirname(
1320
                                                        $abs_path
1321
                                                    );
1322
                                                    $new_abs_path = realpath(
1323
                                                        $dir.'/'.$source
1324
                                                    );
1325
                                                    $in_files_list[] = self::get_resources_from_source(
1326
                                                        TOOL_DOCUMENT,
1327
                                                        $new_abs_path,
1328
                                                        $recursivity + 1
1329
                                                    );
1330
                                                    if (count($in_files_list) > 0) {
1331
                                                        $files_list = array_merge(
1332
                                                            $files_list,
1333
                                                            $in_files_list
1334
                                                        );
1335
                                                    }
1336
                                                }
1337
                                            }
1338
                                        }
1339
1340
                                        // Found some protocol there.
1341
                                        if (false !== strpos($source, api_get_path(WEB_PATH))) {
1342
                                            // We found the current portal url.
1343
                                            $files_list[] = [
1344
                                                $source,
1345
                                                'local',
1346
                                                'url',
1347
                                            ];
1348
                                            $in_files_list[] = self::get_resources_from_source(
1349
                                                TOOL_DOCUMENT,
1350
                                                $source,
1351
                                                $recursivity + 1
1352
                                            );
1353
                                            if (count($in_files_list) > 0) {
1354
                                                $files_list = array_merge(
1355
                                                    $files_list,
1356
                                                    $in_files_list
1357
                                                );
1358
                                            }
1359
                                        } else {
1360
                                            // We didn't find any trace of current portal.
1361
                                            $files_list[] = [
1362
                                                $source,
1363
                                                'remote',
1364
                                                'url',
1365
                                            ];
1366
                                        }
1367
                                    } else {
1368
                                        // No protocol found, make link local.
1369
                                        if ('/' === substr($source, 0, 1)) {
1370
                                            // Link starts with a /, making it absolute (relative to DocumentRoot).
1371
                                            $files_list[] = [
1372
                                                $source,
1373
                                                'local',
1374
                                                'abs',
1375
                                            ];
1376
                                            $in_files_list[] = self::get_resources_from_source(
1377
                                                TOOL_DOCUMENT,
1378
                                                $source,
1379
                                                $recursivity + 1
1380
                                            );
1381
                                            if (count($in_files_list) > 0) {
1382
                                                $files_list = array_merge(
1383
                                                    $files_list,
1384
                                                    $in_files_list
1385
                                                );
1386
                                            }
1387
                                        } elseif (0 === strstr($source, '..')) {
1388
                                            // Link is relative but going back in the hierarchy.
1389
                                            $files_list[] = [
1390
                                                $source,
1391
                                                'local',
1392
                                                'rel',
1393
                                            ];
1394
                                            $dir = dirname($abs_path);
1395
                                            $new_abs_path = realpath(
1396
                                                $dir.'/'.$source
1397
                                            );
1398
                                            $in_files_list[] = self::get_resources_from_source(
1399
                                                TOOL_DOCUMENT,
1400
                                                $new_abs_path,
1401
                                                $recursivity + 1
1402
                                            );
1403
                                            if (count($in_files_list) > 0) {
1404
                                                $files_list = array_merge(
1405
                                                    $files_list,
1406
                                                    $in_files_list
1407
                                                );
1408
                                            }
1409
                                        } else {
1410
                                            // No starting '/', making it relative to current document's path.
1411
                                            if (strpos($source, 'width=') ||
1412
                                                strpos($source, 'autostart=')
1413
                                            ) {
1414
                                                continue;
1415
                                            }
1416
1417
                                            if ('./' == substr($source, 0, 2)) {
1418
                                                $source = substr(
1419
                                                    $source,
1420
                                                    2
1421
                                                );
1422
                                            }
1423
                                            $files_list[] = [
1424
                                                $source,
1425
                                                'local',
1426
                                                'rel',
1427
                                            ];
1428
                                            $dir = dirname($abs_path);
1429
                                            $new_abs_path = realpath(
1430
                                                $dir.'/'.$source
1431
                                            );
1432
                                            $in_files_list[] = self::get_resources_from_source(
1433
                                                TOOL_DOCUMENT,
1434
                                                $new_abs_path,
1435
                                                $recursivity + 1
1436
                                            );
1437
                                            if (count($in_files_list) > 0) {
1438
                                                $files_list = array_merge(
1439
                                                    $files_list,
1440
                                                    $in_files_list
1441
                                                );
1442
                                            }
1443
                                        }
1444
                                    }
1445
                                }
1446
                            }
1447
                        }
1448
                        break;
1449
                    default:
1450
                        break;
1451
                }
1452
1453
                break;
1454
            default: // Ignore.
1455
                break;
1456
        }
1457
1458
        $checked_files_list = [];
1459
        $checked_array_list = [];
1460
        foreach ($files_list as $idx => $file) {
1461
            if (!empty($file[0])) {
1462
                if (!in_array($file[0], $checked_files_list)) {
1463
                    $checked_files_list[] = $files_list[$idx][0];
1464
                    $checked_array_list[] = $files_list[$idx];
1465
                }
1466
            }
1467
        }
1468
1469
        return $checked_array_list;
1470
    }
1471
1472
    /**
1473
     * Gets the score.
1474
     *
1475
     * @return float The current score or 0 if no score set yet
1476
     */
1477
    public function get_score()
1478
    {
1479
        $res = 0;
1480
        if (!empty($this->current_score)) {
1481
            $res = $this->current_score;
1482
        }
1483
1484
        return $res;
1485
    }
1486
1487
    /**
1488
     * Gets the item status.
1489
     *
1490
     * @param bool $check_db     Do or don't check into the database for the
1491
     *                           latest value. Optional. Default is true
1492
     * @param bool $update_local Do or don't update the local attribute
1493
     *                           value with what's been found in DB
1494
     *
1495
     * @return string Current status or 'Not attempted' if no status set yet
1496
     */
1497
    public function get_status($check_db = true, $update_local = false)
1498
    {
1499
        $debug = self::DEBUG;
1500
        if ($debug) {
1501
            error_log('learnpathItem::get_status() on item '.$this->db_id);
1502
        }
1503
        if ($check_db) {
1504
            if ($debug) {
1505
                error_log('learnpathItem::get_status(): checking db');
1506
            }
1507
            if (!empty($this->db_item_view_id)) {
1508
                $table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1509
                $sql = "SELECT status FROM $table
1510
                        WHERE
1511
                            iid = '".$this->db_item_view_id."' AND
1512
                            view_count = '".$this->get_attempt_id()."'";
1513
                $res = Database::query($sql);
1514
                if (1 == Database::num_rows($res)) {
1515
                    $row = Database::fetch_array($res);
1516
                    if ($update_local) {
1517
                        $this->set_status($row['status']);
1518
                    }
1519
1520
                    return $row['status'];
1521
                }
1522
            }
1523
        } else {
1524
            if (!empty($this->status)) {
1525
                return $this->status;
1526
            }
1527
        }
1528
1529
        return $this->possible_status[0];
1530
    }
1531
1532
    /**
1533
     * Gets the suspend data.
1534
     */
1535
    public function get_suspend_data()
1536
    {
1537
        // TODO: Improve cleaning of breaklines ... it works but is it really
1538
        // a beautiful way to do it ?
1539
        if (!empty($this->current_data)) {
1540
            return str_replace(
1541
                ["\r", "\n", "'"],
1542
                ['\r', '\n', "\\'"],
1543
                $this->current_data
1544
            );
1545
        }
1546
1547
        return '';
1548
    }
1549
1550
    /**
1551
     * @param string $origin
1552
     * @param string $time
1553
     *
1554
     * @return string
1555
     */
1556
    public static function getScormTimeFromParameter(
1557
        $origin = 'php',
1558
        $time = null
1559
    ) {
1560
        $h = get_lang('h');
1561
        if (!isset($time)) {
1562
            if ('js' == $origin) {
1563
                return '00 : 00: 00';
1564
            }
1565
1566
            return '00 '.$h.' 00 \' 00"';
1567
        }
1568
1569
        return api_format_time($time, $origin);
1570
    }
1571
1572
    /**
1573
     * Gets the total time spent on this item view so far.
1574
     *
1575
     * @param string   $origin     Origin of the request. If coming from PHP,
1576
     *                             send formatted as xxhxx'xx", otherwise use scorm format 00:00:00
1577
     * @param int|null $given_time Given time is a default time to return formatted
1578
     * @param bool     $query_db   Whether to get the value from db or from memory
1579
     *
1580
     * @return string A string with the time in SCORM format
1581
     */
1582
    public function get_scorm_time(
1583
        $origin = 'php',
1584
        $given_time = null,
1585
        $query_db = false
1586
    ) {
1587
        $time = null;
1588
        $courseId = $this->courseId;
1589
        if (empty($courseId)) {
1590
            $courseId = api_get_course_int_id();
1591
        }
1592
1593
        $courseId = (int) $courseId;
1594
        if (!isset($given_time)) {
1595
            if (self::DEBUG > 2) {
1596
                error_log(
1597
                    'learnpathItem::get_scorm_time(): given time empty, current_start_time = '.$this->current_start_time,
1598
                    0
1599
                );
1600
            }
1601
            if (true === $query_db) {
1602
                $table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1603
                $sql = "SELECT start_time, total_time
1604
                        FROM $table
1605
                        WHERE
1606
                            iid = '".$this->db_item_view_id."' AND
1607
                            view_count = '".$this->get_attempt_id()."'";
1608
                $res = Database::query($sql);
1609
                $row = Database::fetch_array($res);
1610
                $start = $row['start_time'];
1611
                $stop = $start + $row['total_time'];
1612
            } else {
1613
                $start = $this->current_start_time;
1614
                $stop = $this->current_stop_time;
1615
            }
1616
            if (!empty($start)) {
1617
                if (!empty($stop)) {
1618
                    $time = $stop - $start;
1619
                } else {
1620
                    $time = time() - $start;
1621
                }
1622
            }
1623
        } else {
1624
            $time = $given_time;
1625
        }
1626
        if (self::DEBUG > 2) {
1627
            error_log(
1628
                'learnpathItem::get_scorm_time(): intermediate = '.$time,
1629
                0
1630
            );
1631
        }
1632
        $time = api_format_time($time, $origin);
1633
1634
        return $time;
1635
    }
1636
1637
    /**
1638
     * Get the extra terms (tags) that identify this item.
1639
     *
1640
     * @return mixed
1641
     */
1642
    public function get_terms()
1643
    {
1644
        $table = Database::get_course_table(TABLE_LP_ITEM);
1645
        $sql = "SELECT * FROM $table
1646
                WHERE iid = ".intval($this->db_id);
1647
        $res = Database::query($sql);
1648
        $row = Database::fetch_array($res);
1649
1650
        return $row['terms'];
1651
    }
1652
1653
    /**
1654
     * Returns the item's title.
1655
     *
1656
     * @return string Title
1657
     */
1658
    public function get_title()
1659
    {
1660
        if (empty($this->title)) {
1661
            return '';
1662
        }
1663
1664
        return $this->title;
1665
    }
1666
1667
    /**
1668
     * Returns the total time used to see that item.
1669
     *
1670
     * @return int Total time
1671
     */
1672
    public function get_total_time()
1673
    {
1674
        $debug = self::DEBUG;
1675
        if ($debug) {
1676
            error_log(
1677
                'learnpathItem::get_total_time() for item '.$this->db_id.
1678
                ' - Initially, current_start_time = '.$this->current_start_time.
1679
                ' and current_stop_time = '.$this->current_stop_time,
1680
                0
1681
            );
1682
        }
1683
        if (0 == $this->current_start_time) {
1684
            // Shouldn't be necessary thanks to the open() method.
1685
            if ($debug) {
1686
                error_log(
1687
                    'learnpathItem::get_total_time() - Current start time was empty',
1688
                    0
1689
                );
1690
            }
1691
            $this->current_start_time = time();
1692
        }
1693
1694
        if (time() < $this->current_stop_time ||
1695
            0 == $this->current_stop_time
1696
        ) {
1697
            if ($debug) {
1698
                error_log(
1699
                    'learnpathItem::get_total_time() - Current stop time was '
1700
                    .'greater than the current time or was empty',
1701
                    0
1702
                );
1703
            }
1704
            // If this case occurs, then we risk to write huge time data in db.
1705
            // In theory, stop time should be *always* updated here, but it
1706
            // might be used in some unknown goal.
1707
            $this->current_stop_time = time();
1708
        }
1709
1710
        $time = $this->current_stop_time - $this->current_start_time;
1711
1712
        if ($time < 0) {
1713
            if ($debug) {
1714
                error_log(
1715
                    'learnpathItem::get_total_time() - Time smaller than 0. Returning 0',
1716
                    0
1717
                );
1718
            }
1719
1720
            return 0;
1721
        } else {
1722
            $time = $this->fixAbusiveTime($time);
1723
            if ($debug) {
1724
                error_log(
1725
                    'Current start time = '.$this->current_start_time.', current stop time = '.
1726
                    $this->current_stop_time.' Returning '.$time."-----------\n"
1727
                );
1728
            }
1729
1730
            return $time;
1731
        }
1732
    }
1733
1734
    /**
1735
     * Sometimes time recorded for a learning path item is superior to the maximum allowed duration of the session.
1736
     * In this case, this session resets the time for that particular learning path item to 5 minutes
1737
     * (something more realistic, that is also used when leaving the portal without closing one's session).
1738
     *
1739
     * @param int $time
1740
     *
1741
     * @return int
1742
     */
1743
    public function fixAbusiveTime($time)
1744
    {
1745
        // Code based from Event::courseLogout
1746
        $sessionLifetime = api_get_configuration_value('session_lifetime');
1747
        // If session life time too big use 1 hour
1748
        if (empty($sessionLifetime) || $sessionLifetime > 86400) {
1749
            $sessionLifetime = 3600;
1750
        }
1751
1752
        if (!Tracking::minimumTimeAvailable(api_get_session_id(), api_get_course_int_id())) {
1753
            $fixedAddedMinute = 5 * 60; // Add only 5 minutes
1754
            if ($time > $sessionLifetime) {
1755
                error_log("fixAbusiveTime: Total time is too big: $time replaced with: $fixedAddedMinute");
1756
                error_log("item_id : ".$this->db_id." lp_item_view.iid: ".$this->db_item_view_id);
1757
                $time = $fixedAddedMinute;
1758
            }
1759
1760
            return $time;
1761
        } else {
1762
            // Calulate minimum and accumulated time
1763
            $user_id = api_get_user_id();
1764
            $myLP = learnpath::getLpFromSession(api_get_course_int_id(), $this->lp_id, $user_id);
1765
            $timeLp = $myLP->getAccumulateWorkTime();
1766
            $timeTotalCourse = $myLP->getAccumulateWorkTimeTotalCourse();
1767
            /*
1768
            $timeLp = $_SESSION['oLP']->getAccumulateWorkTime();
1769
            $timeTotalCourse = $_SESSION['oLP']->getAccumulateWorkTimeTotalCourse();
1770
            */
1771
            // Minimum connection percentage
1772
            $perc = 100;
1773
            // Time from the course
1774
            $tc = $timeTotalCourse;
1775
            /*if (!empty($sessionId) && $sessionId != 0) {
1776
                $sql = "SELECT hours, perc FROM plugin_licences_course_session WHERE session_id = $sessionId";
1777
                $res = Database::query($sql);
1778
                if (Database::num_rows($res) > 0) {
1779
                    $aux = Database::fetch_assoc($res);
1780
                    $perc = $aux['perc'];
1781
                    $tc = $aux['hours'] * 60;
1782
                }
1783
            }*/
1784
            // Percentage of the learning paths
1785
            $pl = 0;
1786
            if (!empty($timeTotalCourse)) {
1787
                $pl = $timeLp / $timeTotalCourse;
1788
            }
1789
1790
            // Minimum time for each learning path
1791
            $accumulateWorkTime = ($pl * $tc * $perc / 100);
1792
            $time_seg = intval($accumulateWorkTime * 60);
1793
1794
            if ($time_seg < $sessionLifetime) {
1795
                $sessionLifetime = $time_seg;
1796
            }
1797
1798
            if ($time > $sessionLifetime) {
1799
                $fixedAddedMinute = $time_seg + mt_rand(0, 300);
1800
                if (self::DEBUG > 2) {
1801
                    error_log("Total time is too big: $time replaced with: $fixedAddedMinute");
1802
                }
1803
                $time = $fixedAddedMinute;
1804
            }
1805
1806
            return $time;
1807
        }
1808
    }
1809
1810
    /**
1811
     * Gets the item type.
1812
     *
1813
     * @return string The item type (can be doc, dir, sco, asset)
1814
     */
1815
    public function get_type()
1816
    {
1817
        $res = 'asset';
1818
        if (!empty($this->type)) {
1819
            $res = $this->type;
1820
        }
1821
1822
        return $res;
1823
    }
1824
1825
    /**
1826
     * Gets the view count for this item.
1827
     *
1828
     * @return int Number of attempts or 0
1829
     */
1830
    public function get_view_count()
1831
    {
1832
        if (!empty($this->attempt_id)) {
1833
            return $this->attempt_id;
1834
        }
1835
1836
        return 0;
1837
    }
1838
1839
    /**
1840
     * Tells if an item is done ('completed','passed','succeeded') or not.
1841
     *
1842
     * @return bool True if the item is done ('completed','passed','succeeded'),
1843
     *              false otherwise
1844
     */
1845
    public function is_done()
1846
    {
1847
        $completedStatusList = [
1848
            'completed',
1849
            'passed',
1850
            'succeeded',
1851
            'failed',
1852
        ];
1853
1854
        if ($this->status_is($completedStatusList)) {
1855
            return true;
1856
        }
1857
1858
        return false;
1859
    }
1860
1861
    /**
1862
     * Tells if a restart is allowed (take it from $this->prevent_reinit and $this->status).
1863
     *
1864
     * @return int -1 if retaking the sco another time for credit is not allowed,
1865
     *             0 if it is not allowed but the item has to be finished
1866
     *             1 if it is allowed. Defaults to 1
1867
     */
1868
    public function isRestartAllowed()
1869
    {
1870
        $restart = 1;
1871
        $mystatus = $this->get_status(true);
1872
        if ($this->get_prevent_reinit() > 0) {
1873
            // If prevent_reinit == 1 (or more)
1874
            // If status is not attempted or incomplete, authorize retaking (of the same) anyway. Otherwise:
1875
            if ($mystatus != $this->possible_status[0] && $mystatus != $this->possible_status[1]) {
1876
                $restart = -1;
1877
            } else { //status incompleted or not attempted
1878
                $restart = 0;
1879
            }
1880
        } else {
1881
            if ($mystatus == $this->possible_status[0] || $mystatus == $this->possible_status[1]) {
1882
                $restart = -1;
1883
            }
1884
        }
1885
1886
        return $restart;
1887
    }
1888
1889
    /**
1890
     * Opens/launches the item. Initialises runtime values.
1891
     *
1892
     * @param bool $allow_new_attempt
1893
     *
1894
     * @return bool true on success, false on failure
1895
     */
1896
    public function open($allow_new_attempt = false)
1897
    {
1898
        if (0 == $this->prevent_reinit) {
1899
            $this->current_score = 0;
1900
            $this->current_start_time = time();
1901
            // In this case, as we are opening the item, what is important to us
1902
            // is the database status, in order to know if this item has already
1903
            // been used in the past (rather than just loaded and modified by
1904
            // some javascript but not written in the database).
1905
            // If the database status is different from 'not attempted', we can
1906
            // consider this item has already been used, and as such we can
1907
            // open a new attempt. Otherwise, we'll just reuse the current
1908
            // attempt, which is generally created the first time the item is
1909
            // loaded (for example as part of the table of contents).
1910
            $stat = $this->get_status(true);
1911
            if ($allow_new_attempt && isset($stat) && ($stat != $this->possible_status[0])) {
1912
                $this->attempt_id = $this->attempt_id + 1; // Open a new attempt.
1913
            }
1914
            $this->status = $this->possible_status[1];
1915
        } else {
1916
            /*if ($this->current_start_time == 0) {
1917
                // Small exception for start time, to avoid amazing values.
1918
                $this->current_start_time = time();
1919
            }*/
1920
            // If we don't init start time here, the time is sometimes calculated from the last start time.
1921
            $this->current_start_time = time();
1922
        }
1923
    }
1924
1925
    /**
1926
     * Outputs the item contents.
1927
     *
1928
     * @return string HTML file (displayable in an <iframe>) or empty string if no path defined
1929
     */
1930
    public function output()
1931
    {
1932
        if (!empty($this->path) and is_file($this->path)) {
1933
            $output = '';
1934
            $output .= file_get_contents($this->path);
1935
1936
            return $output;
1937
        }
1938
1939
        return '';
1940
    }
1941
1942
    /**
1943
     * Parses the prerequisites string with the AICC logic language.
1944
     *
1945
     * @param string $prereqs_string The prerequisites string as it figures in imsmanifest.xml
1946
     * @param array  $items          Array of items in the current learnpath object.
1947
     *                               Although we're in the learnpathItem object, it's necessary to have
1948
     *                               a list of all items to be able to check the current item's prerequisites
1949
     * @param array  $refs_list      list of references
1950
     *                               (the "ref" column in the lp_item table) that are strings used in the
1951
     *                               expression of prerequisites
1952
     * @param int    $user_id        The user ID. In some cases like Chamilo quizzes,
1953
     *                               it's necessary to have the user ID to query other tables (like the results of quizzes)
1954
     *
1955
     * @return bool True if the list of prerequisites given is entirely satisfied, false otherwise
1956
     */
1957
    public function parse_prereq($prereqs_string, $items, $refs_list, $user_id)
1958
    {
1959
        $debug = self::DEBUG;
1960
        if ($debug > 0) {
1961
            error_log(
1962
                'learnpathItem::parse_prereq() for learnpath '.$this->lp_id.' with string '.$prereqs_string,
1963
                0
1964
            );
1965
        }
1966
1967
        $courseId = $this->courseId;
1968
        $sessionId = api_get_session_id();
1969
1970
        // Deal with &, |, ~, =, <>, {}, ,, X*, () in reverse order.
1971
        $this->prereq_alert = '';
1972
1973
        // First parse all parenthesis by using a sequential loop
1974
        //  (looking for less-inclusives first).
1975
        if ('_true_' == $prereqs_string) {
1976
            return true;
1977
        }
1978
1979
        if ('_false_' == $prereqs_string) {
1980
            if (empty($this->prereq_alert)) {
1981
                $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.');
1982
            }
1983
1984
            return false;
1985
        }
1986
1987
        while (false !== strpos($prereqs_string, '(')) {
1988
            // Remove any () set and replace with its value.
1989
            $matches = [];
1990
            $res = preg_match_all(
1991
                '/(\(([^\(\)]*)\))/',
1992
                $prereqs_string,
1993
                $matches
1994
            );
1995
            if ($res) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $res of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1996
                foreach ($matches[2] as $id => $match) {
1997
                    $str_res = $this->parse_prereq(
1998
                        $match,
1999
                        $items,
2000
                        $refs_list,
2001
                        $user_id
2002
                    );
2003
                    if ($str_res) {
2004
                        $prereqs_string = str_replace(
2005
                            $matches[1][$id],
2006
                            '_true_',
2007
                            $prereqs_string
2008
                        );
2009
                    } else {
2010
                        $prereqs_string = str_replace(
2011
                            $matches[1][$id],
2012
                            '_false_',
2013
                            $prereqs_string
2014
                        );
2015
                    }
2016
                }
2017
            }
2018
        }
2019
2020
        // Parenthesis removed, now look for ORs as it is the lesser-priority
2021
        //  binary operator (= always uses one text operand).
2022
        if (false === strpos($prereqs_string, '|')) {
2023
            if ($debug) {
2024
                error_log('New LP - Didnt find any OR, looking for AND', 0);
2025
            }
2026
            if (false !== strpos($prereqs_string, '&')) {
2027
                $list = explode('&', $prereqs_string);
2028
                if (count($list) > 1) {
2029
                    $andstatus = true;
2030
                    foreach ($list as $condition) {
2031
                        $andstatus = $andstatus && $this->parse_prereq(
2032
                            $condition,
2033
                            $items,
2034
                            $refs_list,
2035
                            $user_id
2036
                        );
2037
2038
                        if (!$andstatus) {
2039
                            if ($debug) {
2040
                                error_log(
2041
                                    'New LP - One condition in AND was false, short-circuit',
2042
                                    0
2043
                                );
2044
                            }
2045
                            break;
2046
                        }
2047
                    }
2048
2049
                    if (empty($this->prereq_alert) && !$andstatus) {
2050
                        $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2051
                    }
2052
2053
                    return $andstatus;
2054
                } else {
2055
                    if (isset($items[$refs_list[$list[0]]])) {
2056
                        $status = $items[$refs_list[$list[0]]]->get_status(true);
2057
                        $returnstatus = ($status == $this->possible_status[2]) || ($status == $this->possible_status[3]);
2058
                        if (empty($this->prereq_alert) && !$returnstatus) {
2059
                            $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.');
2060
                        }
2061
2062
                        return $returnstatus;
2063
                    }
2064
                    $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.');
2065
2066
                    return false;
2067
                }
2068
            } else {
2069
                // No ORs found, now look for ANDs.
2070
                if ($debug) {
2071
                    error_log('New LP - Didnt find any AND, looking for =', 0);
2072
                }
2073
2074
                if (false !== strpos($prereqs_string, '=')) {
2075
                    if ($debug) {
2076
                        error_log('New LP - Found =, looking into it', 0);
2077
                    }
2078
                    // We assume '=' signs only appear when there's nothing else around.
2079
                    $params = explode('=', $prereqs_string);
2080
                    if (2 == count($params)) {
2081
                        // Right number of operands.
2082
                        if (isset($items[$refs_list[$params[0]]])) {
2083
                            $status = $items[$refs_list[$params[0]]]->get_status(true);
2084
                            $returnstatus = $status == $params[1];
2085
                            if (empty($this->prereq_alert) && !$returnstatus) {
2086
                                $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.');
2087
                            }
2088
2089
                            return $returnstatus;
2090
                        }
2091
                        $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.');
2092
2093
                        return false;
2094
                    }
2095
                } else {
2096
                    // No ANDs found, look for <>
2097
                    if ($debug) {
2098
                        error_log(
2099
                            'New LP - Didnt find any =, looking for <>',
2100
                            0
2101
                        );
2102
                    }
2103
2104
                    if (false !== strpos($prereqs_string, '<>')) {
2105
                        if ($debug) {
2106
                            error_log('New LP - Found <>, looking into it', 0);
2107
                        }
2108
                        // We assume '<>' signs only appear when there's nothing else around.
2109
                        $params = explode('<>', $prereqs_string);
2110
                        if (2 == count($params)) {
2111
                            // Right number of operands.
2112
                            if (isset($items[$refs_list[$params[0]]])) {
2113
                                $status = $items[$refs_list[$params[0]]]->get_status(true);
2114
                                $returnstatus = $status != $params[1];
2115
                                if (empty($this->prereq_alert) && !$returnstatus) {
2116
                                    $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.');
2117
                                }
2118
2119
                                return $returnstatus;
2120
                            }
2121
                            $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.');
2122
2123
                            return false;
2124
                        }
2125
                    } else {
2126
                        // No <> found, look for ~ (unary)
2127
                        if ($debug) {
2128
                            error_log(
2129
                                'New LP - Didnt find any =, looking for ~',
2130
                                0
2131
                            );
2132
                        }
2133
                        // Only remains: ~ and X*{}
2134
                        if (false !== strpos($prereqs_string, '~')) {
2135
                            // Found NOT.
2136
                            if ($debug) {
2137
                                error_log(
2138
                                    'New LP - Found ~, looking into it',
2139
                                    0
2140
                                );
2141
                            }
2142
                            $list = [];
2143
                            $myres = preg_match(
2144
                                '/~([^(\d+\*)\{]*)/',
2145
                                $prereqs_string,
2146
                                $list
2147
                            );
2148
                            if ($myres) {
2149
                                $returnstatus = !$this->parse_prereq(
2150
                                    $list[1],
2151
                                    $items,
2152
                                    $refs_list,
2153
                                    $user_id
2154
                                );
2155
                                if (empty($this->prereq_alert) && !$returnstatus) {
2156
                                    $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.');
2157
                                }
2158
2159
                                return $returnstatus;
2160
                            } else {
2161
                                // Strange...
2162
                                if ($debug) {
2163
                                    error_log(
2164
                                        'New LP - Found ~ but strange string: '.$prereqs_string,
2165
                                        0
2166
                                    );
2167
                                }
2168
                            }
2169
                        } else {
2170
                            // Finally, look for sets/groups
2171
                            if ($debug) {
2172
                                error_log(
2173
                                    'New LP - Didnt find any ~, looking for groups',
2174
                                    0
2175
                                );
2176
                            }
2177
                            // Only groups here.
2178
                            $groups = [];
2179
                            $groups_there = preg_match_all(
2180
                                '/((\d+\*)?\{([^\}]+)\}+)/',
2181
                                $prereqs_string,
2182
                                $groups
2183
                            );
2184
2185
                            if ($groups_there) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $groups_there of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2186
                                foreach ($groups[1] as $gr) {
2187
                                    // Only take the results that correspond to
2188
                                    //  the big brackets-enclosed condition.
2189
                                    if ($debug) {
2190
                                        error_log(
2191
                                            'New LP - Dealing with group '.$gr,
2192
                                            0
2193
                                        );
2194
                                    }
2195
                                    $multi = [];
2196
                                    $mycond = false;
2197
                                    if (preg_match(
2198
                                        '/(\d+)\*\{([^\}]+)\}/',
2199
                                        $gr,
2200
                                        $multi
2201
                                    )
2202
                                    ) {
2203
                                        if ($debug) {
2204
                                            error_log(
2205
                                                'New LP - Found multiplier '.$multi[0],
2206
                                                0
2207
                                            );
2208
                                        }
2209
                                        $count = $multi[1];
2210
                                        $list = explode(',', $multi[2]);
2211
                                        $mytrue = 0;
2212
                                        foreach ($list as $cond) {
2213
                                            if (isset($items[$refs_list[$cond]])) {
2214
                                                $status = $items[$refs_list[$cond]]->get_status(true);
2215
                                                if ($status == $this->possible_status[2] ||
2216
                                                    $status == $this->possible_status[3]
2217
                                                ) {
2218
                                                    $mytrue++;
2219
                                                    if ($debug) {
2220
                                                        error_log(
2221
                                                            'New LP - Found true item, counting.. ('.($mytrue).')',
2222
                                                            0
2223
                                                        );
2224
                                                    }
2225
                                                }
2226
                                            } else {
2227
                                                if ($debug) {
2228
                                                    error_log(
2229
                                                        'New LP - item '.$cond.' does not exist in items list',
2230
                                                        0
2231
                                                    );
2232
                                                }
2233
                                            }
2234
                                        }
2235
                                        if ($mytrue >= $count) {
2236
                                            if ($debug) {
2237
                                                error_log(
2238
                                                    'New LP - Got enough true results, return true',
2239
                                                    0
2240
                                                );
2241
                                            }
2242
                                            $mycond = true;
2243
                                        } else {
2244
                                            if ($debug) {
2245
                                                error_log(
2246
                                                    'New LP - Not enough true results',
2247
                                                    0
2248
                                                );
2249
                                            }
2250
                                        }
2251
                                    } else {
2252
                                        if ($debug) {
2253
                                            error_log(
2254
                                                'New LP - No multiplier',
2255
                                                0
2256
                                            );
2257
                                        }
2258
                                        $list = explode(',', $gr);
2259
                                        $mycond = true;
2260
                                        foreach ($list as $cond) {
2261
                                            if (isset($items[$refs_list[$cond]])) {
2262
                                                $status = $items[$refs_list[$cond]]->get_status(true);
2263
                                                if ($status == $this->possible_status[2] ||
2264
                                                    $status == $this->possible_status[3]
2265
                                                ) {
2266
                                                    $mycond = true;
2267
                                                    if ($debug) {
2268
                                                        error_log(
2269
                                                            'New LP - Found true item',
2270
                                                            0
2271
                                                        );
2272
                                                    }
2273
                                                } else {
2274
                                                    if ($debug) {
2275
                                                        error_log(
2276
                                                            'New LP - '.
2277
                                                            ' Found false item, the set is not true, return false',
2278
                                                            0
2279
                                                        );
2280
                                                    }
2281
                                                    $mycond = false;
2282
                                                    break;
2283
                                                }
2284
                                            } else {
2285
                                                if ($debug) {
2286
                                                    error_log(
2287
                                                        'New LP - item '.$cond.' does not exist in items list',
2288
                                                        0
2289
                                                    );
2290
                                                }
2291
                                                if ($debug) {
2292
                                                    error_log(
2293
                                                        'New LP - Found false item, the set is not true, return false',
2294
                                                        0
2295
                                                    );
2296
                                                }
2297
                                                $mycond = false;
2298
                                                break;
2299
                                            }
2300
                                        }
2301
                                    }
2302
                                    if (!$mycond && empty($this->prereq_alert)) {
2303
                                        $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.');
2304
                                    }
2305
2306
                                    return $mycond;
2307
                                }
2308
                            } else {
2309
                                // Nothing found there either. Now return the
2310
                                // value of the corresponding resource completion status.
2311
                                if (isset($refs_list[$prereqs_string]) &&
2312
                                    isset($items[$refs_list[$prereqs_string]])
2313
                                ) {
2314
                                    /** @var learnpathItem $itemToCheck */
2315
                                    $itemToCheck = $items[$refs_list[$prereqs_string]];
2316
2317
                                    if ('quiz' === $itemToCheck->type) {
2318
                                        // 1. Checking the status in current items.
2319
                                        $status = $itemToCheck->get_status(true);
2320
                                        $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2321
2322
                                        if (!$returnstatus) {
2323
                                            $explanation = sprintf(
2324
                                                get_lang('Item %s blocks this step'),
2325
                                                $itemToCheck->get_title()
2326
                                            );
2327
                                            $this->prereq_alert = $explanation;
2328
                                        }
2329
2330
                                        // For one and first attempt.
2331
                                        if (1 == $this->prevent_reinit) {
2332
                                            // 2. If is completed we check the results in the DB of the quiz.
2333
                                            if ($returnstatus) {
2334
                                                $sql = 'SELECT score, max_score
2335
                                                        FROM '.Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES).'
2336
                                                        WHERE
2337
                                                            exe_exo_id = '.$items[$refs_list[$prereqs_string]]->path.' AND
2338
                                                            exe_user_id = '.$user_id.' AND
2339
                                                            orig_lp_id = '.$this->lp_id.' AND
2340
                                                            orig_lp_item_id = '.$prereqs_string.' AND
2341
                                                            status <> "incomplete" AND
2342
                                                            c_id = '.$courseId.'
2343
                                                        ORDER BY exe_date DESC
2344
                                                        LIMIT 0, 1';
2345
                                                $rs_quiz = Database::query($sql);
2346
                                                if ($quiz = Database::fetch_array($rs_quiz)) {
2347
                                                    /** @var learnpathItem $myItemToCheck */
2348
                                                    $myItemToCheck = $items[$refs_list[$this->get_id()]];
2349
                                                    $minScore = $myItemToCheck->getPrerequisiteMinScore();
2350
                                                    $maxScore = $myItemToCheck->getPrerequisiteMaxScore();
2351
2352
                                                    if (isset($minScore) && isset($minScore)) {
2353
                                                        // Taking min/max prerequisites values see BT#5776
2354
                                                        if ($quiz['score'] >= $minScore &&
2355
                                                            $quiz['score'] <= $maxScore
2356
                                                        ) {
2357
                                                            $returnstatus = true;
2358
                                                        } else {
2359
                                                            $explanation = sprintf(
2360
                                                                get_lang('Your result at %s blocks this step'),
2361
                                                                $itemToCheck->get_title()
2362
                                                            );
2363
                                                            $this->prereq_alert = $explanation;
2364
                                                            $returnstatus = false;
2365
                                                        }
2366
                                                    } else {
2367
                                                        // Classic way
2368
                                                        if ($quiz['score'] >=
2369
                                                            $items[$refs_list[$prereqs_string]]->get_mastery_score()
2370
                                                        ) {
2371
                                                            $returnstatus = true;
2372
                                                        } else {
2373
                                                            $explanation = sprintf(
2374
                                                                get_lang('Your result at %s blocks this step'),
2375
                                                                $itemToCheck->get_title()
2376
                                                            );
2377
                                                            $this->prereq_alert = $explanation;
2378
                                                            $returnstatus = false;
2379
                                                        }
2380
                                                    }
2381
                                                } else {
2382
                                                    $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.');
2383
                                                    $returnstatus = false;
2384
                                                }
2385
                                            }
2386
                                        } else {
2387
                                            // 3. For multiple attempts we check that there are minimum 1 item completed
2388
                                            // Checking in the database.
2389
                                            $sql = 'SELECT score, max_score
2390
                                                    FROM '.Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES).'
2391
                                                    WHERE
2392
                                                        c_id = '.$courseId.' AND
2393
                                                        exe_exo_id = '.$items[$refs_list[$prereqs_string]]->path.' AND
2394
                                                        exe_user_id = '.$user_id.' AND
2395
                                                        orig_lp_id = '.$this->lp_id.' AND
2396
                                                        orig_lp_item_id = '.$prereqs_string;
2397
2398
                                            $rs_quiz = Database::query($sql);
2399
                                            if (Database::num_rows($rs_quiz) > 0) {
2400
                                                while ($quiz = Database::fetch_array($rs_quiz)) {
2401
                                                    /** @var learnpathItem $myItemToCheck */
2402
                                                    $myItemToCheck = $items[$refs_list[$this->get_id()]];
2403
                                                    $minScore = $myItemToCheck->getPrerequisiteMinScore();
2404
                                                    $maxScore = $myItemToCheck->getPrerequisiteMaxScore();
2405
2406
                                                    if (empty($minScore)) {
2407
                                                        // Try with mastery_score
2408
                                                        $masteryScoreAsMin = $myItemToCheck->get_mastery_score();
2409
                                                        if (!empty($masteryScoreAsMin)) {
2410
                                                            $minScore = $masteryScoreAsMin;
2411
                                                        }
2412
                                                    }
2413
2414
                                                    if (isset($minScore) && isset($minScore)) {
2415
                                                        // Taking min/max prerequisites values see BT#5776
2416
                                                        if ($quiz['score'] >= $minScore && $quiz['score'] <= $maxScore) {
2417
                                                            $returnstatus = true;
2418
                                                            break;
2419
                                                        } else {
2420
                                                            $explanation = sprintf(
2421
                                                                get_lang('Your result at %s blocks this step'),
2422
                                                                $itemToCheck->get_title()
2423
                                                            );
2424
                                                            $this->prereq_alert = $explanation;
2425
                                                            $returnstatus = false;
2426
                                                        }
2427
                                                    } else {
2428
                                                        if ($quiz['score'] >=
2429
                                                            $items[$refs_list[$prereqs_string]]->get_mastery_score()
2430
                                                        ) {
2431
                                                            $returnstatus = true;
2432
                                                            break;
2433
                                                        } else {
2434
                                                            $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.');
2435
                                                            $returnstatus = false;
2436
                                                        }
2437
                                                    }
2438
                                                }
2439
                                            } else {
2440
                                                $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.');
2441
                                                $returnstatus = false;
2442
                                            }
2443
                                        }
2444
2445
                                        if (false === $returnstatus) {
2446
                                            // Check results from another sessions.
2447
                                            $checkOtherSessions = ('true' === api_get_setting('lp.validate_lp_prerequisite_from_other_session'));
2448
                                            if ($checkOtherSessions) {
2449
                                                $returnstatus = $this->getStatusFromOtherSessions(
2450
                                                    $user_id,
2451
                                                    $prereqs_string,
2452
                                                    $refs_list
2453
                                                );
2454
                                            }
2455
                                        }
2456
2457
                                        return $returnstatus;
2458
                                    } elseif ('student_publication' === $itemToCheck->type) {
2459
                                        $workId = $items[$refs_list[$prereqs_string]]->path;
2460
                                        $count = get_work_count_by_student($user_id, $workId);
2461
                                        if ($count >= 1) {
2462
                                            $returnstatus = true;
2463
                                        } else {
2464
                                            $returnstatus = false;
2465
                                            $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.');
2466
                                        }
2467
2468
                                        return $returnstatus;
2469
                                    } else {
2470
                                        $status = $itemToCheck->get_status(true);
2471
                                        if (self::DEBUG) {
2472
                                            error_log('Status:'.$status);
2473
                                        }
2474
                                        $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2475
2476
                                        // Check results from another sessions.
2477
                                        $checkOtherSessions = ('true' === api_get_setting('lp.validate_lp_prerequisite_from_other_session'));
2478
                                        if ($checkOtherSessions && !$returnstatus) {
2479
                                            $returnstatus = $this->getStatusFromOtherSessions(
2480
                                                $user_id,
2481
                                                $prereqs_string,
2482
                                                $refs_list
2483
                                            );
2484
                                        }
2485
2486
                                        if (!$returnstatus) {
2487
                                            $explanation = sprintf(
2488
                                                get_lang('Item %s blocks this step'),
2489
                                                $itemToCheck->get_title()
2490
                                            );
2491
                                            $this->prereq_alert = $explanation;
2492
                                        }
2493
2494
                                        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2495
                                        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
2496
2497
                                        if ($returnstatus && 1 == $this->prevent_reinit) {
2498
                                            $sql = "SELECT iid FROM $lp_view
2499
                                                    WHERE
2500
                                                        c_id = $courseId AND
2501
                                                        user_id = $user_id  AND
2502
                                                        lp_id = $this->lp_id AND
2503
                                                        session_id = $sessionId
2504
                                                    LIMIT 0, 1";
2505
                                            $rs_lp = Database::query($sql);
2506
                                            if (Database::num_rows($rs_lp)) {
2507
                                                $lp_id = Database::fetch_row($rs_lp);
2508
                                                $my_lp_id = $lp_id[0];
2509
2510
                                                $sql = "SELECT status FROM $lp_item_view
2511
                                                        WHERE
2512
                                                            lp_view_id = $my_lp_id AND
2513
                                                            lp_item_id = $refs_list[$prereqs_string]
2514
                                                        LIMIT 0, 1";
2515
                                                $rs_lp = Database::query($sql);
2516
                                                $status_array = Database::fetch_row($rs_lp);
2517
                                                $status = $status_array[0];
2518
2519
                                                $returnstatus = $status == $this->possible_status[2] || $status == $this->possible_status[3];
2520
                                                if (!$returnstatus && empty($this->prereq_alert)) {
2521
                                                    $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.');
2522
                                                }
2523
                                            }
2524
2525
                                            if ($checkOtherSessions && false === $returnstatus) {
2526
                                                $returnstatus = $returnstatus = $this->getStatusFromOtherSessions(
2527
                                                    $user_id,
2528
                                                    $prereqs_string,
2529
                                                    $refs_list
2530
                                                );
2531
                                            }
2532
                                        }
2533
2534
                                        return $returnstatus;
2535
                                    }
2536
                                }
2537
                            }
2538
                        }
2539
                    }
2540
                }
2541
            }
2542
        } else {
2543
            $list = explode("\|", $prereqs_string);
2544
            if (count($list) > 1) {
2545
                if (self::DEBUG > 1) {
2546
                    error_log('New LP - Found OR, looking into it', 0);
2547
                }
2548
                $orstatus = false;
2549
                foreach ($list as $condition) {
2550
                    if (self::DEBUG) {
2551
                        error_log(
2552
                            'New LP - Found OR, adding it ('.$condition.')',
2553
                            0
2554
                        );
2555
                    }
2556
                    $orstatus = $orstatus || $this->parse_prereq(
2557
                        $condition,
2558
                        $items,
2559
                        $refs_list,
2560
                        $user_id
2561
                    );
2562
                    if ($orstatus) {
2563
                        // Shortcircuit OR.
2564
                        if (self::DEBUG > 1) {
2565
                            error_log(
2566
                                'New LP - One condition in OR was true, short-circuit',
2567
                                0
2568
                            );
2569
                        }
2570
                        break;
2571
                    }
2572
                }
2573
                if (!$orstatus && empty($this->prereq_alert)) {
2574
                    $this->prereq_alert = get_lang('This learning object cannot display because the course prerequisites are not completed. This happens when a course imposes that you follow it step by step or get a minimum score in tests before you reach the next steps.');
2575
                }
2576
2577
                return $orstatus;
2578
            } else {
2579
                if (self::DEBUG > 1) {
2580
                    error_log(
2581
                        'New LP - OR was found but only one elem present !?',
2582
                        0
2583
                    );
2584
                }
2585
                if (isset($items[$refs_list[$list[0]]])) {
2586
                    $status = $items[$refs_list[$list[0]]]->get_status(true);
2587
                    $returnstatus = 'completed' == $status || 'passed' == $status;
2588
                    if (!$returnstatus && empty($this->prereq_alert)) {
2589
                        $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.');
2590
                    }
2591
2592
                    return $returnstatus;
2593
                }
2594
            }
2595
        }
2596
        if (empty($this->prereq_alert)) {
2597
            $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.');
2598
        }
2599
2600
        if (self::DEBUG > 1) {
2601
            error_log(
2602
                'New LP - End of parse_prereq. Error code is now '.$this->prereq_alert,
2603
                0
2604
            );
2605
        }
2606
2607
        return false;
2608
    }
2609
2610
    /**
2611
     * Reinits all local values as the learnpath is restarted.
2612
     *
2613
     * @return bool True on success, false otherwise
2614
     */
2615
    public function restart()
2616
    {
2617
        if (self::DEBUG > 0) {
2618
            error_log('learnpathItem::restart()', 0);
2619
        }
2620
        $seriousGame = $this->get_seriousgame_mode();
0 ignored issues
show
Deprecated Code introduced by
The function learnpathItem::get_seriousgame_mode() has been deprecated: seriousgame_mode seems not to be used ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2620
        $seriousGame = /** @scrutinizer ignore-deprecated */ $this->get_seriousgame_mode();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
2621
        //For serious game  : We reuse same attempt_id
2622
        if (1 == $seriousGame && 'sco' == $this->type) {
2623
            // If this is a sco, Chamilo can't update the time without an
2624
            //  explicit scorm call
2625
            $this->current_start_time = 0;
2626
            $this->current_stop_time = 0; //Those 0 value have this effect
2627
            $this->last_scorm_session_time = 0;
2628
            $this->save();
2629
2630
            return true;
2631
        }
2632
2633
        $this->save();
2634
2635
        $allowed = $this->isRestartAllowed();
2636
        if (-1 === $allowed) {
2637
            // Nothing allowed, do nothing.
2638
        } elseif (1 === $allowed) {
2639
            // Restart as new attempt is allowed, record a new attempt.
2640
            $this->attempt_id = $this->attempt_id + 1; // Simply reuse the previous attempt_id.
2641
            $this->current_score = 0;
2642
            $this->current_start_time = 0;
2643
            $this->current_stop_time = 0;
2644
            $this->current_data = '';
2645
            $this->status = $this->possible_status[0];
2646
            $this->interactions_count = 0;
2647
            $this->interactions = [];
2648
            $this->objectives_count = 0;
2649
            $this->objectives = [];
2650
            $this->lesson_location = '';
2651
            if (TOOL_QUIZ != $this->type) {
2652
                $this->write_to_db();
2653
            }
2654
        } else {
2655
            // Restart current element is allowed (because it's not finished yet),
2656
            // reinit current.
2657
            //$this->current_score = 0;
2658
            $this->current_start_time = 0;
2659
            $this->current_stop_time = 0;
2660
            $this->interactions_count = $this->get_interactions_count(true);
2661
        }
2662
2663
        return true;
2664
    }
2665
2666
    /**
2667
     * Saves data in the database.
2668
     *
2669
     * @param bool $from_outside     Save from URL params (1) or from object attributes (0)
2670
     * @param bool $prereqs_complete The results of a check on prerequisites for this item.
2671
     *                               True if prerequisites are completed, false otherwise. Defaults to false. Only used if not sco or au
2672
     *
2673
     * @return bool True on success, false on failure
2674
     */
2675
    public function save($from_outside = true, $prereqs_complete = false)
2676
    {
2677
        $debug = self::DEBUG;
2678
        if ($debug) {
2679
            error_log('learnpathItem::save()', 0);
2680
        }
2681
        // First check if parameters passed via GET can be saved here
2682
        // in case it's a SCORM, we should get:
2683
        if ('sco' === $this->type || 'au' === $this->type) {
2684
            $status = $this->get_status(true);
2685
            if (1 == $this->prevent_reinit &&
2686
                $status != $this->possible_status[0] && // not attempted
2687
                $status != $this->possible_status[1]    //incomplete
2688
            ) {
2689
                if ($debug) {
2690
                    error_log(
2691
                        'learnpathItem::save() - save reinit blocked by setting',
2692
                        0
2693
                    );
2694
                }
2695
                // Do nothing because the status has already been set. Don't allow it to change.
2696
                // TODO: Check there isn't a special circumstance where this should be saved.
2697
            } else {
2698
                if ($debug) {
2699
                    error_log(
2700
                        'learnpathItem::save() - SCORM save request received',
2701
                        0
2702
                    );
2703
                }
2704
2705
                if ($from_outside) {
2706
                    if ($debug) {
2707
                        error_log(
2708
                            'learnpathItem::save() - Getting item data from outside',
2709
                            0
2710
                        );
2711
                    }
2712
                    foreach ($_GET as $param => $value) {
2713
                        switch ($param) {
2714
                            case 'score':
2715
                                $this->set_score($value);
2716
                                if ($debug) {
2717
                                    error_log(
2718
                                        'learnpathItem::save() - setting score to '.$value,
2719
                                        0
2720
                                    );
2721
                                }
2722
                                break;
2723
                            case 'max':
2724
                                $this->set_max_score($value);
2725
                                if ($debug) {
2726
                                    error_log(
2727
                                        'learnpathItem::save() - setting view_max_score to '.$value,
2728
                                        0
2729
                                    );
2730
                                }
2731
                                break;
2732
                            case 'min':
2733
                                $this->min_score = $value;
2734
                                if ($debug) {
2735
                                    error_log(
2736
                                        'learnpathItem::save() - setting min_score to '.$value,
2737
                                        0
2738
                                    );
2739
                                }
2740
                                break;
2741
                            case 'lesson_status':
2742
                                if (!empty($value)) {
2743
                                    $this->set_status($value);
2744
                                    if ($debug) {
2745
                                        error_log(
2746
                                            'learnpathItem::save() - setting status to '.$value,
2747
                                            0
2748
                                        );
2749
                                    }
2750
                                }
2751
                                break;
2752
                            case 'time':
2753
                                $this->set_time($value);
2754
                                if ($debug) {
2755
                                    error_log(
2756
                                        'learnpathItem::save() - setting time to '.$value,
2757
                                        0
2758
                                    );
2759
                                }
2760
                                break;
2761
                            case 'suspend_data':
2762
                                $this->current_data = $value;
2763
                                if ($debug) {
2764
                                    error_log(
2765
                                        'learnpathItem::save() - setting suspend_data to '.$value,
2766
                                        0
2767
                                    );
2768
                                }
2769
                                break;
2770
                            case 'lesson_location':
2771
                                $this->set_lesson_location($value);
2772
                                if ($debug) {
2773
                                    error_log(
2774
                                        'learnpathItem::save() - setting lesson_location to '.$value,
2775
                                        0
2776
                                    );
2777
                                }
2778
                                break;
2779
                            case 'core_exit':
2780
                                $this->set_core_exit($value);
2781
                                if ($debug) {
2782
                                    error_log(
2783
                                        'learnpathItem::save() - setting core_exit to '.$value,
2784
                                        0
2785
                                    );
2786
                                }
2787
                                break;
2788
                            case 'interactions':
2789
                                break;
2790
                            case 'objectives':
2791
                                break;
2792
                            default:
2793
                                // Ignore.
2794
                                break;
2795
                        }
2796
                    }
2797
                } else {
2798
                    // Do nothing, just let the local attributes be used.
2799
                    if ($debug) {
2800
                        error_log(
2801
                            'learnpathItem::save() - Using inside item status',
2802
                            0
2803
                        );
2804
                    }
2805
                }
2806
            }
2807
        } else {
2808
            // If not SCO, such messages should not be expected.
2809
            $type = strtolower($this->type);
2810
            if ($debug) {
2811
                error_log("type: $type");
2812
            }
2813
2814
            switch ($type) {
2815
                case 'asset':
2816
                    if ($prereqs_complete) {
2817
                        $this->set_status($this->possible_status[2]);
2818
                    }
2819
                    break;
2820
                case TOOL_HOTPOTATOES:
2821
                    break;
2822
                case TOOL_QUIZ:
2823
                    return false;
2824
                    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...
2825
                default:
2826
                    // For now, everything that is not sco and not asset is set to
2827
                    // completed when saved.
2828
                    if ($prereqs_complete) {
2829
                        $this->set_status($this->possible_status[2]);
2830
                    }
2831
                    break;
2832
            }
2833
        }
2834
2835
        if ($debug) {
2836
            error_log('End of learnpathItem::save() - Calling write_to_db() now');
2837
        }
2838
2839
        return $this->write_to_db();
2840
    }
2841
2842
    /**
2843
     * Sets the number of attempt_id to a given value.
2844
     *
2845
     * @param int $num The given value to set attempt_id to
2846
     *
2847
     * @return bool TRUE on success, FALSE otherwise
2848
     */
2849
    public function set_attempt_id($num)
2850
    {
2851
        if ($num == strval(intval($num)) && $num >= 0) {
2852
            $this->attempt_id = $num;
2853
2854
            return true;
2855
        }
2856
2857
        return false;
2858
    }
2859
2860
    /**
2861
     * Sets the core_exit value to the one given.
2862
     *
2863
     * @return bool $value  True (always)
2864
     */
2865
    public function set_core_exit($value)
2866
    {
2867
        switch ($value) {
2868
            case '':
2869
                $this->core_exit = '';
2870
                break;
2871
            case 'suspend':
2872
                $this->core_exit = 'suspend';
2873
                break;
2874
            default:
2875
                $this->core_exit = 'none';
2876
                break;
2877
        }
2878
2879
        return true;
2880
    }
2881
2882
    /**
2883
     * Sets the item's description.
2884
     *
2885
     * @param string $string Description
2886
     */
2887
    public function set_description($string = '')
2888
    {
2889
        if (!empty($string)) {
2890
            $this->description = $string;
2891
        }
2892
    }
2893
2894
    /**
2895
     * Sets the lesson_location value.
2896
     *
2897
     * @param string $location lesson_location as provided by the SCO
2898
     *
2899
     * @return bool True on success, false otherwise
2900
     */
2901
    public function set_lesson_location($location)
2902
    {
2903
        if (isset($location)) {
2904
            $this->lesson_location = $location;
2905
2906
            return true;
2907
        }
2908
2909
        return false;
2910
    }
2911
2912
    /**
2913
     * Sets the item's depth level in the LP tree (0 is at root).
2914
     *
2915
     * @param int $int Level
2916
     */
2917
    public function set_level($int = 0)
2918
    {
2919
        $this->level = (int) $int;
2920
    }
2921
2922
    /**
2923
     * Sets the lp_view id this item view is registered to.
2924
     *
2925
     * @param int $lp_view_id lp_view DB ID
2926
     *
2927
     * @todo //todo insert into lp_item_view if lp_view not exists
2928
     */
2929
    public function set_lp_view(int $lp_view_id): bool
2930
    {
2931
        $lpItemId = $this->get_id();
2932
2933
        if (empty($lpItemId)) {
2934
            return false;
2935
        }
2936
2937
        if (empty($lp_view_id)) {
2938
            return false;
2939
        }
2940
2941
        if (self::DEBUG > 0) {
2942
            error_log('learnpathItem::set_lp_view('.$lp_view_id.')', 0);
2943
        }
2944
2945
        $this->view_id = $lp_view_id;
2946
2947
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2948
        // Get the lp_item_view with the highest view_count.
2949
        $sql = "SELECT * FROM $item_view_table
2950
                WHERE
2951
                    lp_item_id = $lpItemId AND
2952
                    lp_view_id = $lp_view_id
2953
                ORDER BY view_count DESC";
2954
2955
        if (self::DEBUG > 2) {
2956
            error_log(
2957
                'learnpathItem::set_lp_view() - Querying lp_item_view: '.$sql,
2958
                0
2959
            );
2960
        }
2961
        $res = Database::query($sql);
2962
        if (Database::num_rows($res) > 0) {
2963
            $row = Database::fetch_array($res);
2964
            $this->db_item_view_id = $row['iid'];
2965
            $this->attempt_id = $row['view_count'];
2966
            $this->current_score = $row['score'];
2967
            $this->current_data = $row['suspend_data'];
2968
            $this->view_max_score = $row['max_score'];
2969
            $this->status = $row['status'];
2970
            $this->current_start_time = $row['start_time'];
2971
            $this->current_stop_time = $this->current_start_time + $row['total_time'];
2972
            $this->lesson_location = $row['lesson_location'];
2973
            $this->core_exit = $row['core_exit'];
2974
2975
            if (self::DEBUG > 2) {
2976
                error_log(
2977
                    'learnpathItem::set_lp_view() - Updated item object with database values',
2978
                    0
2979
                );
2980
            }
2981
2982
            // Now get the number of interactions for this little guy.
2983
            $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2984
            $sql = "SELECT * FROM $table
2985
                    WHERE lp_iv_id = ".$this->db_item_view_id;
2986
2987
            $res = Database::query($sql);
2988
            if (false !== $res) {
2989
                $this->interactions_count = Database::num_rows($res);
2990
            } else {
2991
                $this->interactions_count = 0;
2992
            }
2993
            // Now get the number of objectives for this little guy.
2994
            $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2995
            $sql = "SELECT * FROM $table
2996
                    WHERE lp_iv_id = ".$this->db_item_view_id;
2997
2998
            $this->objectives_count = 0;
2999
            $res = Database::query($sql);
3000
            if (false !== $res) {
3001
                $this->objectives_count = Database::num_rows($res);
3002
            }
3003
        }
3004
3005
        return true;
3006
    }
3007
3008
    /**
3009
     * Sets the path.
3010
     *
3011
     * @param string $string Path
3012
     */
3013
    public function set_path($string = '')
3014
    {
3015
        if (!empty($string)) {
3016
            $this->path = $string;
3017
        }
3018
    }
3019
3020
    /**
3021
     * Sets the prevent_reinit attribute.
3022
     * This is based on the LP value and is set at creation time for
3023
     * each learnpathItem. It is a (bad?) way of avoiding
3024
     * a reference to the LP when saving an item.
3025
     *
3026
     * @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...
3027
     * saving freshened values (new "not attempted" status etc)
3028
     */
3029
    public function set_prevent_reinit($prevent)
3030
    {
3031
        $this->prevent_reinit = 0;
3032
        if ($prevent) {
3033
            $this->prevent_reinit = 1;
3034
        }
3035
    }
3036
3037
    /**
3038
     * Sets the score value. If the mastery_score is set and the score reaches
3039
     * it, then set the status to 'passed'.
3040
     *
3041
     * @param float $score Score
3042
     *
3043
     * @return bool True on success, false otherwise
3044
     */
3045
    public function set_score($score)
3046
    {
3047
        $debug = self::DEBUG;
3048
        if ($debug > 0) {
3049
            error_log('learnpathItem::set_score('.$score.')', 0);
3050
        }
3051
        if (($this->max_score <= 0 || $score <= $this->max_score) && ($score >= $this->min_score)) {
3052
            $this->current_score = $score;
3053
            $masteryScore = $this->get_mastery_score();
3054
            $current_status = $this->get_status(false);
3055
3056
            // Fixes bug when SCORM doesn't send a mastery score even if they sent a score!
3057
            if (-1 == $masteryScore) {
3058
                $masteryScore = $this->max_score;
3059
            }
3060
3061
            if ($debug > 0) {
3062
                error_log('get_mastery_score: '.$masteryScore);
3063
                error_log('current_status: '.$current_status);
3064
                error_log('current score : '.$this->current_score);
3065
            }
3066
3067
            // If mastery_score is set AND the current score reaches the mastery
3068
            //  score AND the current status is different from 'completed', then
3069
            //  set it to 'passed'.
3070
            /*
3071
            if ($master != -1 && $this->current_score >= $master && $current_status != $this->possible_status[2]) {
3072
                if ($debug > 0) error_log('Status changed to: '.$this->possible_status[3]);
3073
                $this->set_status($this->possible_status[3]); //passed
3074
            } elseif ($master != -1 && $this->current_score < $master) {
3075
                if ($debug > 0) error_log('Status changed to: '.$this->possible_status[4]);
3076
                $this->set_status($this->possible_status[4]); //failed
3077
            }*/
3078
            return true;
3079
        }
3080
3081
        return false;
3082
    }
3083
3084
    /**
3085
     * Sets the maximum score for this item.
3086
     *
3087
     * @param int $score Maximum score - must be a decimal or an empty string
3088
     *
3089
     * @return bool True on success, false on error
3090
     */
3091
    public function set_max_score($score)
3092
    {
3093
        if (is_int($score) || '' == $score) {
3094
            $this->view_max_score = $score;
3095
3096
            return true;
3097
        }
3098
3099
        return false;
3100
    }
3101
3102
    /**
3103
     * Sets the status for this item.
3104
     *
3105
     * @param string $status Status - must be one of the values defined in $this->possible_status
3106
     *                       (this affects the status setting)
3107
     *
3108
     * @return bool True on success, false on error
3109
     */
3110
    public function set_status($status)
3111
    {
3112
        if (self::DEBUG) {
3113
            error_log('learnpathItem::set_status('.$status.')');
3114
        }
3115
3116
        $found = false;
3117
        foreach ($this->possible_status as $possible) {
3118
            if (preg_match('/^'.$possible.'$/i', $status)) {
3119
                $found = true;
3120
            }
3121
        }
3122
3123
        if ($found) {
3124
            $this->status = $status;
3125
            if (self::DEBUG) {
3126
                error_log(
3127
                    'learnpathItem::set_status() - '.
3128
                        'Updated object status of item '.$this->db_id.
3129
                    ' to '.$this->status
3130
                );
3131
            }
3132
3133
            return true;
3134
        }
3135
3136
        if (self::DEBUG) {
3137
            error_log('Setting status: '.$this->possible_status[0]);
3138
        }
3139
        $this->status = $this->possible_status[0];
3140
3141
        return false;
3142
    }
3143
3144
    /**
3145
     * Set the (indexing) terms for this learnpath item.
3146
     *
3147
     * @param string $terms Terms, as a comma-split list
3148
     *
3149
     * @return bool Always return true
3150
     */
3151
    public function set_terms($terms)
3152
    {
3153
        global $charset;
3154
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
3155
        $a_terms = preg_split('/,/', $terms);
3156
        $i_terms = preg_split('/,/', $this->get_terms());
3157
        foreach ($i_terms as $term) {
3158
            if (!in_array($term, $a_terms)) {
3159
                array_push($a_terms, $term);
3160
            }
3161
        }
3162
        $new_terms = $a_terms;
3163
        $new_terms_string = implode(',', $new_terms);
3164
3165
        // TODO: Validate csv string.
3166
        $terms = Database::escape_string(api_htmlentities($new_terms_string, ENT_QUOTES));
3167
        $sql = "UPDATE $lp_item
3168
                SET terms = '$terms'
3169
                WHERE iid=".$this->get_id();
3170
        Database::query($sql);
3171
        // Save it to search engine.
3172
        if ('true' == api_get_setting('search_enabled')) {
3173
            $di = new ChamiloIndexer();
3174
            $di->update_terms($this->get_search_did(), $new_terms, 'T');
3175
        }
3176
3177
        return true;
3178
    }
3179
3180
    /**
3181
     * Get the document ID from inside the text index database.
3182
     *
3183
     * @return int Search index database document ID
3184
     */
3185
    public function get_search_did()
3186
    {
3187
        return $this->search_did;
3188
    }
3189
3190
    /**
3191
     * Sets the item viewing time in a usable form, given that SCORM packages
3192
     * often give it as 00:00:00.0000.
3193
     *
3194
     * @param    string    Time as given by SCORM
3195
     * @param string $format
3196
     */
3197
    public function set_time($scorm_time, $format = 'scorm')
3198
    {
3199
        $debug = self::DEBUG;
3200
        if ($debug) {
3201
            error_log("learnpathItem::set_time($scorm_time, $format)");
3202
            error_log("this->type: ".$this->type);
3203
            error_log("this->current_start_time: ".$this->current_start_time);
3204
        }
3205
3206
        if ('0' == $scorm_time &&
3207
            'sco' !== $this->type &&
3208
            0 != $this->current_start_time
3209
        ) {
3210
            $myTime = time() - $this->current_start_time;
3211
            if ($myTime > 0) {
3212
                $this->update_time($myTime);
3213
                if ($debug) {
3214
                    error_log('found asset - set time to '.$myTime);
3215
                }
3216
            } else {
3217
                if ($debug) {
3218
                    error_log('Time not set');
3219
                }
3220
            }
3221
        } else {
3222
            switch ($format) {
3223
                case 'scorm':
3224
                    $res = [];
3225
                    if (preg_match(
3226
                        '/^(\d{1,4}):(\d{2}):(\d{2})(\.\d{1,4})?/',
3227
                        $scorm_time,
3228
                        $res
3229
                    )
3230
                    ) {
3231
                        $hour = $res[1];
3232
                        $min = $res[2];
3233
                        $sec = $res[3];
3234
                        // Getting total number of seconds spent.
3235
                        $totalSec = $hour * 3600 + $min * 60 + $sec;
3236
                        if ($debug) {
3237
                            error_log("totalSec : $totalSec");
3238
                            error_log("Now calling to scorm_update_time()");
3239
                        }
3240
                        $this->scorm_update_time($totalSec);
3241
                    }
3242
                    break;
3243
                case 'int':
3244
                    if ($debug) {
3245
                        error_log("scorm_time = $scorm_time");
3246
                        error_log("Now calling to scorm_update_time()");
3247
                    }
3248
                    $this->scorm_update_time($scorm_time);
3249
                    break;
3250
            }
3251
        }
3252
    }
3253
3254
    /**
3255
     * Sets the item's title.
3256
     *
3257
     * @param string $string Title
3258
     */
3259
    public function set_title($string = '')
3260
    {
3261
        if (!empty($string)) {
3262
            $this->title = $string;
3263
        }
3264
    }
3265
3266
    /**
3267
     * Sets the item's type.
3268
     *
3269
     * @param string $string Type
3270
     */
3271
    public function set_type($string = '')
3272
    {
3273
        if (!empty($string)) {
3274
            $this->type = $string;
3275
        }
3276
    }
3277
3278
    /**
3279
     * Checks if the current status is part of the list of status given.
3280
     *
3281
     * @param array $list An array of status to check for.
3282
     *                    If the current status is one of the strings, return true
3283
     *
3284
     * @return bool True if the status was one of the given strings,
3285
     *              false otherwise
3286
     */
3287
    public function status_is($list = [])
3288
    {
3289
        if (self::DEBUG > 1) {
3290
            error_log(
3291
                'learnpathItem::status_is('.print_r(
0 ignored issues
show
Bug introduced by
Are you sure print_r($list, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3291
                'learnpathItem::status_is('./** @scrutinizer ignore-type */ print_r(
Loading history...
3292
                    $list,
3293
                    true
3294
                ).') on item '.$this->db_id,
3295
                0
3296
            );
3297
        }
3298
        $currentStatus = $this->get_status(true);
3299
        if (empty($currentStatus)) {
3300
            return false;
3301
        }
3302
        $found = false;
3303
        foreach ($list as $status) {
3304
            if (preg_match('/^'.$status.'$/i', $currentStatus)) {
3305
                if (self::DEBUG > 2) {
3306
                    error_log(
3307
                        'New LP - learnpathItem::status_is() - Found status '.
3308
                            $status.' corresponding to current status',
3309
                        0
3310
                    );
3311
                }
3312
                $found = true;
3313
3314
                return $found;
3315
            }
3316
        }
3317
        if (self::DEBUG > 2) {
3318
            error_log(
3319
                'New LP - learnpathItem::status_is() - Status '.
3320
                    $currentStatus.' did not match request',
3321
                0
3322
            );
3323
        }
3324
3325
        return $found;
3326
    }
3327
3328
    /**
3329
     * Updates the time info according to the given session_time.
3330
     *
3331
     * @param int $totalSec Time in seconds
3332
     */
3333
    public function update_time($totalSec = 0)
3334
    {
3335
        if (self::DEBUG > 0) {
3336
            error_log('learnpathItem::update_time('.$totalSec.')');
3337
        }
3338
        if ($totalSec >= 0) {
3339
            // Getting start time from finish time. The only problem in the calculation is it might be
3340
            // modified by the scripts processing time.
3341
            $now = time();
3342
            $start = $now - $totalSec;
3343
            $this->current_start_time = $start;
3344
            $this->current_stop_time = $now;
3345
        }
3346
    }
3347
3348
    /**
3349
     * Special scorm update time function. This function will update time
3350
     * directly into db for scorm objects.
3351
     *
3352
     * @param int $total_sec Total number of seconds
3353
     */
3354
    public function scorm_update_time($total_sec = 0)
3355
    {
3356
        $debug = self::DEBUG;
3357
        if ($debug) {
3358
            error_log('learnpathItem::scorm_update_time()');
3359
            error_log("total_sec: $total_sec");
3360
        }
3361
3362
        // Step 1 : get actual total time stored in db
3363
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3364
3365
        $sql = 'SELECT total_time, status
3366
                FROM '.$item_view_table.'
3367
                WHERE
3368
                    lp_item_id = "'.$this->db_id.'" AND
3369
                    lp_view_id = "'.$this->view_id.'" AND
3370
                    view_count = "'.$this->get_attempt_id().'"';
3371
        $result = Database::query($sql);
3372
        $row = Database::fetch_array($result);
3373
3374
        if (!isset($row['total_time'])) {
3375
            $total_time = 0;
3376
        } else {
3377
            $total_time = $row['total_time'];
3378
        }
3379
        if ($debug) {
3380
            error_log("Original total_time: $total_time");
3381
        }
3382
3383
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3384
        $lp_id = (int) $this->lp_id;
3385
        $sql = "SELECT * FROM $lp_table WHERE iid = $lp_id";
3386
        $res = Database::query($sql);
3387
        $accumulateScormTime = 'false';
3388
        if (Database::num_rows($res) > 0) {
3389
            $row = Database::fetch_assoc($res);
3390
            $accumulateScormTime = $row['accumulate_scorm_time'];
3391
        }
3392
3393
        // Step 2.1 : if normal mode total_time = total_time + total_sec
3394
        if ('sco' === $this->type && 0 != $accumulateScormTime) {
3395
            if ($debug) {
3396
                error_log("accumulateScormTime is on. total_time modified: $total_time + $total_sec");
3397
            }
3398
            $total_time += $total_sec;
3399
        } else {
3400
            // Step 2.2 : if not cumulative mode total_time = total_time - last_update + total_sec
3401
            $total_sec = $this->fixAbusiveTime($total_sec);
3402
            if ($debug) {
3403
                error_log("after fix abusive: $total_sec");
3404
                error_log("total_time: $total_time");
3405
                error_log("this->last_scorm_session_time: ".$this->last_scorm_session_time);
3406
            }
3407
3408
            $total_time = $total_time - $this->last_scorm_session_time + $total_sec;
3409
            $this->last_scorm_session_time = $total_sec;
3410
3411
            if ($total_time < 0) {
3412
                $total_time = $total_sec;
3413
            }
3414
        }
3415
3416
        if ($debug) {
3417
            error_log("accumulate_scorm_time: $accumulateScormTime");
3418
            error_log("total_time modified: $total_time");
3419
        }
3420
3421
        // Step 3 update db only if status != completed, passed, browsed or seriousgamemode not activated
3422
        // @todo complete
3423
        $case_completed = [
3424
            'completed',
3425
            'passed',
3426
            'browsed',
3427
            'failed',
3428
        ];
3429
3430
        if (1 != $this->seriousgame_mode ||
3431
            !in_array($row['status'], $case_completed)
3432
        ) {
3433
            $sql = "UPDATE $item_view_table
3434
                      SET total_time = '$total_time'
3435
                    WHERE
3436
                        lp_item_id = {$this->db_id} AND
3437
                        lp_view_id = {$this->view_id} AND
3438
                        view_count = {$this->get_attempt_id()}";
3439
            if ($debug) {
3440
                error_log('-------------total_time updated ------------------------');
3441
                error_log($sql);
3442
                error_log('-------------------------------------');
3443
            }
3444
            Database::query($sql);
3445
        }
3446
    }
3447
3448
    /**
3449
     * Write objectives to DB. This method is separate from write_to_db() because otherwise
3450
     * objectives are lost as a side effect to AJAX and session concurrent access.
3451
     *
3452
     * @return bool True or false on error
3453
     */
3454
    public function write_objectives_to_db()
3455
    {
3456
        if (self::DEBUG > 0) {
3457
            error_log('learnpathItem::write_objectives_to_db()', 0);
3458
        }
3459
        if (api_is_invitee()) {
3460
            // If the user is an invitee, we don't write anything to DB
3461
            return true;
3462
        }
3463
        $courseId = api_get_course_int_id();
3464
        if (is_array($this->objectives) && count($this->objectives) > 0) {
3465
            // Save objectives.
3466
            $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3467
            $sql = "SELECT iid
3468
                    FROM $tbl
3469
                    WHERE
3470
                        lp_item_id = ".$this->db_id." AND
3471
                        lp_view_id = ".$this->view_id." AND
3472
                        view_count = ".$this->attempt_id;
3473
            $res = Database::query($sql);
3474
            if (Database::num_rows($res) > 0) {
3475
                $row = Database::fetch_array($res);
3476
                $lp_iv_id = $row[0];
3477
                if (self::DEBUG > 2) {
3478
                    error_log(
3479
                        'learnpathItem::write_to_db() - Got item_view_id '.
3480
                            $lp_iv_id.', now checking objectives ',
3481
                        0
3482
                    );
3483
                }
3484
                foreach ($this->objectives as $index => $objective) {
3485
                    $iva_table = Database::get_course_table(
3486
                        TABLE_LP_IV_OBJECTIVE
3487
                    );
3488
                    $iva_sql = "SELECT iid FROM $iva_table
3489
                                WHERE
3490
                                    c_id = $courseId AND
3491
                                    lp_iv_id = $lp_iv_id AND
3492
                                    objective_id = '".Database::escape_string($objective[0])."'";
3493
                    $iva_res = Database::query($iva_sql);
3494
                    // id(0), type(1), time(2), weighting(3),
3495
                    // correct_responses(4), student_response(5),
3496
                    // result(6), latency(7)
3497
                    if (Database::num_rows($iva_res) > 0) {
3498
                        // Update (or don't).
3499
                        $iva_row = Database::fetch_array($iva_res);
3500
                        $iva_id = $iva_row[0];
3501
                        $ivau_sql = "UPDATE $iva_table ".
3502
                            "SET objective_id = '".Database::escape_string($objective[0])."',".
3503
                            "status = '".Database::escape_string($objective[1])."',".
3504
                            "score_raw = '".Database::escape_string($objective[2])."',".
3505
                            "score_min = '".Database::escape_string($objective[4])."',".
3506
                            "score_max = '".Database::escape_string($objective[3])."' ".
3507
                            "WHERE c_id = $courseId AND iid = $iva_id";
3508
                        Database::query($ivau_sql);
3509
                    } else {
3510
                        // Insert new one.
3511
                        $params = [
3512
                            'c_id' => $courseId,
3513
                            'lp_iv_id' => $lp_iv_id,
3514
                            'order_id' => $index,
3515
                            'objective_id' => $objective[0],
3516
                            'status' => $objective[1],
3517
                            'score_raw' => $objective[2],
3518
                            'score_min' => $objective[4],
3519
                            'score_max' => $objective[3],
3520
                        ];
3521
3522
                        $insertId = Database::insert($iva_table, $params);
3523
                        if ($insertId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $insertId of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3524
                            $sql = "UPDATE $iva_table SET id = iid
3525
                                    WHERE iid = $insertId";
3526
                            Database::query($sql);
3527
                        }
3528
                    }
3529
                }
3530
            }
3531
        }
3532
    }
3533
3534
    /**
3535
     * Writes the current data to the database.
3536
     *
3537
     * @return bool Query result
3538
     */
3539
    public function write_to_db()
3540
    {
3541
        $debug = self::DEBUG;
3542
        if ($debug) {
3543
            error_log('------------------------');
3544
            error_log('learnpathItem::write_to_db()');
3545
        }
3546
3547
        // Check the session visibility.
3548
        if (!api_is_allowed_to_session_edit()) {
3549
            if ($debug) {
3550
                error_log('return false api_is_allowed_to_session_edit');
3551
            }
3552
3553
            return false;
3554
        }
3555
        if (api_is_invitee()) {
3556
            if ($debug) {
3557
                error_log('api_is_invitee');
3558
            }
3559
            // If the user is an invitee, we don't write anything to DB
3560
            return true;
3561
        }
3562
3563
        $courseId = api_get_course_int_id();
3564
        $mode = $this->get_lesson_mode();
3565
        $credit = $this->get_credit();
3566
        $total_time = ' ';
3567
        $my_status = ' ';
3568
        $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3569
        $sql = 'SELECT status, total_time
3570
                FROM '.$item_view_table.'
3571
                WHERE
3572
                    lp_item_id="'.$this->db_id.'" AND
3573
                    lp_view_id="'.$this->view_id.'" AND
3574
                    view_count="'.$this->get_attempt_id().'" ';
3575
        $rs_verified = Database::query($sql);
3576
        $row_verified = Database::fetch_array($rs_verified);
3577
        $my_case_completed = [
3578
            'completed',
3579
            'passed',
3580
            'browsed',
3581
            'failed',
3582
        ];
3583
3584
        $save = true;
3585
3586
        if (!empty($row_verified)) {
3587
            $oldTotalTime = $row_verified['total_time'];
3588
            $this->oldTotalTime = $oldTotalTime;
3589
            if (isset($row_verified['status'])) {
3590
                if (in_array($row_verified['status'], $my_case_completed)) {
3591
                    $save = false;
3592
                }
3593
            }
3594
        }
3595
3596
        if (((false === $save && 'sco' === $this->type) ||
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (false === $save && 'sco...& 'sco' === $this->type, Probably Intended Meaning: false === $save && 'sco'... 'sco' === $this->type)
Loading history...
3597
           ('sco' === $this->type && ('no-credit' === $credit || 'review' === $mode || 'browse' === $mode))) &&
3598
           (1 != $this->seriousgame_mode && 'sco' === $this->type)
3599
        ) {
3600
            if ($debug) {
3601
                error_log(
3602
                    "This info shouldn't be saved as the credit or lesson mode info prevent it"
3603
                );
3604
                error_log(
3605
                    'learnpathItem::write_to_db() - credit('.$credit.') or'.
3606
                    ' lesson_mode('.$mode.') prevent recording!',
3607
                    0
3608
                );
3609
            }
3610
        } else {
3611
            // Check the row exists.
3612
            $inserted = false;
3613
            // This a special case for multiple attempts and Chamilo exercises.
3614
            if ('quiz' === $this->type &&
3615
                0 == $this->get_prevent_reinit() &&
3616
                'completed' === $this->get_status()
3617
            ) {
3618
                // We force the item to be restarted.
3619
                $this->restart();
3620
                $params = [
3621
                    "c_id" => $courseId,
3622
                    "total_time" => $this->get_total_time(),
3623
                    "start_time" => $this->current_start_time,
3624
                    "score" => $this->get_score(),
3625
                    "status" => $this->get_status(false),
3626
                    "max_score" => $this->get_max(),
3627
                    "lp_item_id" => $this->db_id,
3628
                    "lp_view_id" => $this->view_id,
3629
                    "view_count" => $this->get_attempt_id(),
3630
                    "suspend_data" => $this->current_data,
3631
                    //"max_time_allowed" => ,
3632
                    "lesson_location" => $this->lesson_location,
3633
                ];
3634
                if ($debug) {
3635
                    error_log('learnpathItem::write_to_db() - Inserting into item_view forced: '.print_r($params, 1));
0 ignored issues
show
Bug introduced by
Are you sure print_r($params, 1) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3635
                    error_log('learnpathItem::write_to_db() - Inserting into item_view forced: './** @scrutinizer ignore-type */ print_r($params, 1));
Loading history...
3636
                }
3637
                $this->db_item_view_id = Database::insert($item_view_table, $params);
3638
                if ($this->db_item_view_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->db_item_view_id of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3639
                    $inserted = true;
3640
                }
3641
            }
3642
3643
            $sql = "SELECT * FROM $item_view_table
3644
                    WHERE
3645
                        lp_item_id = ".$this->db_id." AND
3646
                        lp_view_id = ".$this->view_id." AND
3647
                        view_count = ".$this->get_attempt_id();
3648
            if ($debug) {
3649
                error_log('learnpathItem::write_to_db() - Querying item_view: '.$sql);
3650
            }
3651
3652
            $check_res = Database::query($sql);
3653
            // Depending on what we want (really), we'll update or insert a new row
3654
            // now save into DB.
3655
            if (!$inserted && Database::num_rows($check_res) < 1) {
3656
                $params = [
3657
                    "c_id" => $courseId,
3658
                    "total_time" => $this->get_total_time(),
3659
                    "start_time" => $this->current_start_time,
3660
                    "score" => $this->get_score(),
3661
                    "status" => $this->get_status(false),
3662
                    "max_score" => $this->get_max(),
3663
                    "lp_item_id" => $this->db_id,
3664
                    "lp_view_id" => $this->view_id,
3665
                    "view_count" => $this->get_attempt_id(),
3666
                    "suspend_data" => $this->current_data,
3667
                    //"max_time_allowed" => ,$this->get_max_time_allowed()
3668
                    "lesson_location" => $this->lesson_location,
3669
                ];
3670
3671
                if ($debug) {
3672
                    error_log(
3673
                        'learnpathItem::write_to_db() - Inserting into item_view forced: '.print_r($params, 1),
3674
                        0
3675
                    );
3676
                }
3677
                $this->db_item_view_id = Database::insert($item_view_table, $params);
3678
            } else {
3679
                if ('hotpotatoes' === $this->type) {
3680
                    $params = [
3681
                        'total_time' => $this->get_total_time(),
3682
                        'start_time' => $this->get_current_start_time(),
3683
                        'score' => $this->get_score(),
3684
                        'status' => $this->get_status(false),
3685
                        'max_score' => $this->get_max(),
3686
                        'suspend_data' => $this->current_data,
3687
                        'lesson_location' => $this->lesson_location,
3688
                    ];
3689
                    $where = [
3690
                        'c_id = ? AND lp_item_id = ? AND lp_view_id = ? AND view_count = ?' => [
3691
                            $courseId,
3692
                            $this->db_id,
3693
                            $this->view_id,
3694
                            $this->get_attempt_id(),
3695
                        ],
3696
                    ];
3697
                    Database::update($item_view_table, $params, $where);
3698
                } else {
3699
                    // For all other content types...
3700
                    if ('quiz' === $this->type) {
3701
                        $my_status = ' ';
3702
                        $total_time = ' ';
3703
                        if (!empty($_REQUEST['exeId'])) {
3704
                            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3705
                            $exeId = (int) $_REQUEST['exeId'];
3706
                            $sql = "SELECT exe_duration
3707
                                    FROM $table
3708
                                    WHERE exe_id = $exeId";
3709
                            $res = Database::query($sql);
3710
                            $exeRow = Database::fetch_array($res);
3711
                            $duration = isset($exeRow['exe_duration']) ? (int) $exeRow['exe_duration'] : 0;
3712
                            $total_time = " total_time = ".$duration.", ";
3713
                            if ($debug) {
3714
                                error_log("quiz: $total_time");
3715
                            }
3716
                        }
3717
                    } else {
3718
                        $my_type_lp = learnpath::get_type_static($this->lp_id);
3719
                        // This is a array containing values finished
3720
                        $case_completed = [
3721
                            'completed',
3722
                            'passed',
3723
                            'browsed',
3724
                            'failed',
3725
                        ];
3726
3727
                        // Is not multiple attempts
3728
                        if (1 == $this->seriousgame_mode && 'sco' === $this->type) {
3729
                            $total_time = " total_time = total_time +".$this->get_total_time().", ";
3730
                            $my_status = " status = '".$this->get_status(false)."' ,";
3731
                            if ($debug) {
3732
                                error_log("seriousgame_mode time changed: $total_time");
3733
                            }
3734
                        } elseif (1 == $this->get_prevent_reinit()) {
3735
                            // Process of status verified into data base.
3736
                            $sql = 'SELECT status FROM '.$item_view_table.'
3737
                                    WHERE
3738
                                        lp_item_id="'.$this->db_id.'" AND
3739
                                        lp_view_id="'.$this->view_id.'" AND
3740
                                        view_count="'.$this->get_attempt_id().'"
3741
                                    ';
3742
                            $rs_verified = Database::query($sql);
3743
                            $row_verified = Database::fetch_array($rs_verified);
3744
3745
                            // Get type lp: 1=lp dokeos and  2=scorm.
3746
                            // If not is completed or passed or browsed and learning path is scorm.
3747
                            if (!in_array($this->get_status(false), $case_completed) &&
3748
                                2 == $my_type_lp
3749
                            ) {
3750
                                $total_time = " total_time = total_time +".$this->get_total_time().", ";
3751
                                $my_status = " status = '".$this->get_status(false)."' ,";
3752
                                if ($debug) {
3753
                                    error_log("get_prevent_reinit = 1 time changed: $total_time");
3754
                                }
3755
                            } else {
3756
                                // Verified into database.
3757
                                if (!in_array($row_verified['status'], $case_completed) &&
3758
                                    2 == $my_type_lp
3759
                                ) {
3760
                                    $total_time = " total_time = total_time +".$this->get_total_time().", ";
3761
                                    $my_status = " status = '".$this->get_status(false)."' ,";
3762
                                    if ($debug) {
3763
                                        error_log("total_time time changed case 1: $total_time");
3764
                                    }
3765
                                } elseif (in_array($row_verified['status'], $case_completed) &&
3766
                                    2 == $my_type_lp && 'sco' != $this->type
3767
                                ) {
3768
                                    $total_time = " total_time = total_time +".$this->get_total_time().", ";
3769
                                    $my_status = " status = '".$this->get_status(false)."' ,";
3770
                                    if ($debug) {
3771
                                        error_log("total_time time changed case 2: $total_time");
3772
                                    }
3773
                                } else {
3774
                                    if ((3 == $my_type_lp && 'au' == $this->type) ||
3775
                                        (1 == $my_type_lp && 'dir' != $this->type)) {
3776
                                        // Is AICC or Chamilo LP
3777
                                        $total_time = " total_time = total_time + ".$this->get_total_time().", ";
3778
                                        $my_status = " status = '".$this->get_status(false)."' ,";
3779
                                        if ($debug) {
3780
                                            error_log("total_time time changed case 3: $total_time");
3781
                                        }
3782
                                    }
3783
                                }
3784
                            }
3785
                        } else {
3786
                            // Multiple attempts are allowed.
3787
                            if (in_array($this->get_status(false), $case_completed) && 2 == $my_type_lp) {
3788
                                // Reset zero new attempt ?
3789
                                $my_status = " status = '".$this->get_status(false)."' ,";
3790
                                if ($debug) {
3791
                                    error_log("total_time time changed Multiple attempt case 1: $total_time");
3792
                                }
3793
                            } elseif (!in_array($this->get_status(false), $case_completed) && 2 == $my_type_lp) {
3794
                                $total_time = " total_time = ".$this->get_total_time().", ";
3795
                                $my_status = " status = '".$this->get_status(false)."' ,";
3796
                                if ($debug) {
3797
                                    error_log("total_time time changed Multiple attempt case 2: $total_time");
3798
                                }
3799
                            } else {
3800
                                // It is chamilo LP.
3801
                                $total_time = " total_time = total_time +".$this->get_total_time().", ";
3802
                                $my_status = " status = '".$this->get_status(false)."' ,";
3803
                                if ($debug) {
3804
                                    error_log("total_time time changed Multiple attempt case 3: $total_time");
3805
                                }
3806
                            }
3807
3808
                            // This code line fixes the problem of wrong status.
3809
                            if (2 == $my_type_lp) {
3810
                                // Verify current status in multiples attempts.
3811
                                $sql = 'SELECT status FROM '.$item_view_table.'
3812
                                        WHERE
3813
                                            c_id = '.$courseId.' AND
3814
                                            lp_item_id="'.$this->db_id.'" AND
3815
                                            lp_view_id="'.$this->view_id.'" AND
3816
                                            view_count="'.$this->get_attempt_id().'" ';
3817
                                $rs_status = Database::query($sql);
3818
                                $current_status = Database::result($rs_status, 0, 'status');
3819
                                if (in_array($current_status, $case_completed)) {
3820
                                    $my_status = '';
3821
                                    $total_time = '';
3822
                                } else {
3823
                                    $total_time = " total_time = total_time + ".$this->get_total_time().", ";
3824
                                }
3825
3826
                                if ($debug) {
3827
                                    error_log("total_time time my_type_lp: $total_time");
3828
                                }
3829
                            }
3830
                        }
3831
                    }
3832
3833
                    if ('sco' === $this->type) {
3834
                        //IF scorm scorm_update_time has already updated total_time in db
3835
                        //" . //start_time = ".$this->get_current_start_time().", " . //scorm_init_time does it
3836
                        ////" max_time_allowed = '".$this->get_max_time_allowed()."'," .
3837
                        $sql = "UPDATE $item_view_table SET
3838
                                    score = ".$this->get_score().",
3839
                                    $my_status
3840
                                    max_score = '".$this->get_max()."',
3841
                                    suspend_data = '".Database::escape_string($this->current_data)."',
3842
                                    lesson_location = '".$this->lesson_location."'
3843
                                WHERE
3844
                                    lp_item_id = ".$this->db_id." AND
3845
                                    lp_view_id = ".$this->view_id."  AND
3846
                                    view_count = ".$this->get_attempt_id();
3847
                    } else {
3848
                        //" max_time_allowed = '".$this->get_max_time_allowed()."'," .
3849
                        $sql = "UPDATE $item_view_table SET
3850
                                    $total_time
3851
                                    start_time = ".$this->get_current_start_time().",
3852
                                    score = ".$this->get_score().",
3853
                                    $my_status
3854
                                    max_score = '".$this->get_max()."',
3855
                                    suspend_data = '".Database::escape_string($this->current_data)."',
3856
                                    lesson_location = '".$this->lesson_location."'
3857
                                WHERE
3858
                                    lp_item_id = ".$this->db_id." AND
3859
                                    lp_view_id = ".$this->view_id." AND
3860
                                    view_count = ".$this->get_attempt_id();
3861
                    }
3862
                    $this->current_start_time = time();
3863
                }
3864
                if ($debug) {
3865
                    error_log('-------------------------------------------');
3866
                    error_log('learnpathItem::write_to_db() - Updating item_view:');
3867
                    error_log($sql);
3868
                    error_log('-------------------------------------------');
3869
                }
3870
                Database::query($sql);
3871
            }
3872
3873
            if (is_array($this->interactions) &&
3874
                count($this->interactions) > 0
3875
            ) {
3876
                $sql = "SELECT iid FROM $item_view_table
3877
                        WHERE
3878
                            lp_item_id = ".$this->db_id." AND
3879
                            lp_view_id = ".$this->view_id." AND
3880
                            view_count = ".$this->get_attempt_id();
3881
                $res = Database::query($sql);
3882
                if (Database::num_rows($res) > 0) {
3883
                    $row = Database::fetch_array($res);
3884
                    $lp_iv_id = $row[0];
3885
                    if ($debug) {
3886
                        error_log(
3887
                            'learnpathItem::write_to_db() - Got item_view_id '.
3888
                            $lp_iv_id.', now checking interactions ',
3889
                            0
3890
                        );
3891
                    }
3892
                    foreach ($this->interactions as $index => $interaction) {
3893
                        $correct_resp = '';
3894
                        if (is_array($interaction[4]) && !empty($interaction[4][0])) {
3895
                            foreach ($interaction[4] as $resp) {
3896
                                $correct_resp .= $resp.',';
3897
                            }
3898
                            $correct_resp = substr(
3899
                                $correct_resp,
3900
                                0,
3901
                                strlen($correct_resp) - 1
3902
                            );
3903
                        }
3904
                        $iva_table = Database::get_course_table(
3905
                            TABLE_LP_IV_INTERACTION
3906
                        );
3907
3908
                        //also check for the interaction ID as it must be unique for this SCO view
3909
                        $iva_sql = "SELECT iid FROM $iva_table
3910
                                    WHERE
3911
                                        c_id = $courseId AND
3912
                                        lp_iv_id = $lp_iv_id AND
3913
                                        (
3914
                                            order_id = $index OR
3915
                                            interaction_id = '".Database::escape_string($interaction[0])."'
3916
                                        )
3917
                                    ";
3918
                        $iva_res = Database::query($iva_sql);
3919
3920
                        $interaction[0] = $interaction[0] ?? '';
3921
                        $interaction[1] = $interaction[1] ?? '';
3922
                        $interaction[2] = $interaction[2] ?? '';
3923
                        $interaction[3] = $interaction[3] ?? '';
3924
                        $interaction[4] = $interaction[4] ?? '';
3925
                        $interaction[5] = $interaction[5] ?? '';
3926
                        $interaction[6] = $interaction[6] ?? '';
3927
                        $interaction[7] = $interaction[7] ?? '';
3928
3929
                        // id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7)
3930
                        if (Database::num_rows($iva_res) > 0) {
3931
                            // Update (or don't).
3932
                            $iva_row = Database::fetch_array($iva_res);
3933
                            $iva_id = $iva_row[0];
3934
                            // Insert new one.
3935
                            $params = [
3936
                                'interaction_id' => $interaction[0],
3937
                                'interaction_type' => $interaction[1],
3938
                                'weighting' => $interaction[3],
3939
                                'completion_time' => $interaction[2],
3940
                                'correct_responses' => $correct_resp,
3941
                                'student_response' => $interaction[5],
3942
                                'result' => $interaction[6],
3943
                                'latency' => $interaction[7],
3944
                            ];
3945
                            Database::update(
3946
                                $iva_table,
3947
                                $params,
3948
                                [
3949
                                    'c_id = ? AND iid = ?' => [
3950
                                        $courseId,
3951
                                        $iva_id,
3952
                                    ],
3953
                                ]
3954
                            );
3955
                        } else {
3956
                            // Insert new one.
3957
                            $params = [
3958
                                'c_id' => $courseId,
3959
                                'order_id' => $index,
3960
                                'lp_iv_id' => $lp_iv_id,
3961
                                'interaction_id' => $interaction[0],
3962
                                'interaction_type' => $interaction[1],
3963
                                'weighting' => $interaction[3],
3964
                                'completion_time' => $interaction[2],
3965
                                'correct_responses' => $correct_resp,
3966
                                'student_response' => $interaction[5],
3967
                                'result' => $interaction[6],
3968
                                'latency' => $interaction[7],
3969
                            ];
3970
3971
                            $insertId = Database::insert($iva_table, $params);
3972
                            if ($insertId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $insertId of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3973
                                $sql = "UPDATE $iva_table SET id = iid
3974
                                        WHERE iid = $insertId";
3975
                                Database::query($sql);
3976
                            }
3977
                        }
3978
                    }
3979
                }
3980
            }
3981
        }
3982
3983
        if ($debug) {
3984
            error_log('End of learnpathItem::write_to_db()', 0);
3985
        }
3986
3987
        return true;
3988
    }
3989
3990
    /**
3991
     * Adds an audio file attached to the current item (store on disk and in db).
3992
     *
3993
     * @return bool
3994
     */
3995
    public function addAudio()
3996
    {
3997
        $course_info = api_get_course_info();
3998
        $userId = api_get_user_id();
3999
4000
        $folderDocument = create_unexisting_directory(
4001
            $course_info,
4002
            $userId,
4003
            0,
4004
            0,
4005
            0,
4006
            null,
4007
            '/audio',
4008
            get_lang('Audio'),
4009
            0,
4010
            false,
4011
            false
4012
        );
4013
4014
        /*$filepath = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document/';
4015
        if (!is_dir($filepath.'audio')) {
4016
            mkdir(
4017
                $filepath.'audio',
4018
                api_get_permissions_for_new_directories()
4019
            );
4020
            DocumentManager::addDocument(
4021
                $course_info,
4022
                '/audio',
4023
                'folder',
4024
                0,
4025
                'audio'
4026
            );
4027
        }*/
4028
4029
        $key = 'file';
4030
        if (!isset($_FILES[$key]['name']) || !isset($_FILES[$key]['tmp_name'])) {
4031
            return false;
4032
        }
4033
4034
        $document = null;
4035
        /*$document = DocumentManager::upload_document(
4036
            $_FILES,
4037
            null,
4038
            null,
4039
            null,
4040
            0,
4041
            'rename',
4042
            false,
4043
            true,
4044
            'file',
4045
            false,
4046
            $folderDocument->getIid(),
4047
        );*/
4048
4049
        if ($document) {
0 ignored issues
show
introduced by
$document is of type null, thus it always evaluated to false.
Loading history...
4050
            $name = '/audio/'.$document->getResourceNode()->getResourceFiles()->first()->getOriginalName();
4051
            // Store the mp3 file in the lp_item table.
4052
            $table = Database::get_course_table(TABLE_LP_ITEM);
4053
            $sql = "UPDATE $table SET
4054
                        audio = '".Database::escape_string($name)."'
4055
                    WHERE iid = ".intval($this->db_id);
4056
            Database::query($sql);
4057
4058
            return true;
4059
        }
4060
4061
        return false;
4062
    }
4063
4064
    /**
4065
     * Removes the relation between the current item and an audio file. The file
4066
     * is only removed from the lp_item table, but remains in the document table
4067
     * and directory.
4068
     *
4069
     * @return bool
4070
     */
4071
    public function removeAudio()
4072
    {
4073
        $courseInfo = api_get_course_info();
4074
4075
        if (empty($this->db_id) || empty($courseInfo)) {
4076
            return false;
4077
        }
4078
4079
        $table = Database::get_course_table(TABLE_LP_ITEM);
4080
        $sql = "UPDATE $table SET
4081
                audio = ''
4082
                WHERE iid = ".$this->db_id;
4083
        Database::query($sql);
4084
    }
4085
4086
    /**
4087
     * Adds an audio file to the current item, using a file already in documents.
4088
     *
4089
     * @param int $documentId
4090
     *
4091
     * @return string
4092
     */
4093
    public function add_audio_from_documents($documentId)
4094
    {
4095
        $courseInfo = api_get_course_info();
4096
        $documentData = DocumentManager::get_document_data_by_id($documentId, $courseInfo['code']);
0 ignored issues
show
Deprecated Code introduced by
The function DocumentManager::get_document_data_by_id() has been deprecated: use $repo->find() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

4096
        $documentData = /** @scrutinizer ignore-deprecated */ DocumentManager::get_document_data_by_id($documentId, $courseInfo['code']);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
4097
4098
        $path = '';
4099
        if (!empty($documentData)) {
4100
            $path = $documentData['path'];
4101
            // Store the mp3 file in the lp_item table.
4102
            $table = Database::get_course_table(TABLE_LP_ITEM);
4103
            $sql = "UPDATE $table SET
4104
                        audio = '".Database::escape_string($path)."'
4105
                    WHERE iid = ".$this->db_id;
4106
            Database::query($sql);
4107
        }
4108
4109
        return $path;
4110
    }
4111
4112
    /**
4113
     * Transform the SCORM status to a string that can be translated by Chamilo
4114
     * in different user languages.
4115
     *
4116
     * @param $status
4117
     * @param bool   $decorate
4118
     * @param string $type     classic|simple
4119
     *
4120
     * @return array|string
4121
     */
4122
    public static function humanize_status($status, $decorate = true, $type = 'classic')
4123
    {
4124
        $statusList = [
4125
            'completed' => 'Completed',
4126
            'incomplete' => 'Incomplete',
4127
            'failed' => 'Failed',
4128
            'passed' => 'Passed',
4129
            'browsed' => 'Browsed',
4130
            'not attempted' => 'Not attempted',
4131
        ];
4132
4133
        $myLessonStatus = get_lang($statusList[$status]);
4134
4135
        switch ($status) {
4136
            case 'completed':
4137
            case 'browsed':
4138
                $classStatus = 'info';
4139
                break;
4140
            case 'incomplete':
4141
                $classStatus = 'warning';
4142
                break;
4143
            case 'passed':
4144
                $classStatus = 'success';
4145
                break;
4146
            case 'failed':
4147
                $classStatus = 'important';
4148
                break;
4149
            default:
4150
                $classStatus = 'default';
4151
                break;
4152
        }
4153
4154
        if ('simple' === $type) {
4155
            if (in_array($status, ['failed', 'passed', 'browsed'])) {
4156
                $myLessonStatus = get_lang('Incomplete');
4157
4158
                $classStatus = 'warning';
4159
            }
4160
        }
4161
4162
        if ($decorate) {
4163
            return Display::label($myLessonStatus, $classStatus);
4164
        }
4165
4166
        return $myLessonStatus;
4167
    }
4168
4169
    /**
4170
     * @return float
4171
     */
4172
    public function getPrerequisiteMaxScore()
4173
    {
4174
        return $this->prerequisiteMaxScore;
4175
    }
4176
4177
    /**
4178
     * @param float $prerequisiteMaxScore
4179
     */
4180
    public function setPrerequisiteMaxScore($prerequisiteMaxScore)
4181
    {
4182
        $this->prerequisiteMaxScore = $prerequisiteMaxScore;
4183
    }
4184
4185
    /**
4186
     * @return float
4187
     */
4188
    public function getPrerequisiteMinScore()
4189
    {
4190
        return $this->prerequisiteMinScore;
4191
    }
4192
4193
    /**
4194
     * @param float $prerequisiteMinScore
4195
     */
4196
    public function setPrerequisiteMinScore($prerequisiteMinScore)
4197
    {
4198
        $this->prerequisiteMinScore = $prerequisiteMinScore;
4199
    }
4200
4201
    /**
4202
     * @return int
4203
     */
4204
    public function getLastScormSessionTime()
4205
    {
4206
        return $this->last_scorm_session_time;
4207
    }
4208
4209
    /**
4210
     * @return int
4211
     */
4212
    public function getIid()
4213
    {
4214
        return $this->iId;
4215
    }
4216
4217
    /**
4218
     * @param int    $user_id
4219
     * @param string $prereqs_string
4220
     * @param array  $refs_list
4221
     *
4222
     * @return bool
4223
     */
4224
    public function getStatusFromOtherSessions($user_id, $prereqs_string, $refs_list)
4225
    {
4226
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
4227
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
4228
        $courseId = api_get_course_int_id();
4229
        $user_id = (int) $user_id;
4230
4231
        // Check results from another sessions:
4232
        $checkOtherSessions = ('true' === api_get_setting('lp.validate_lp_prerequisite_from_other_session'));
4233
        if ($checkOtherSessions) {
4234
            // Check items
4235
            $sql = "SELECT iid FROM $lp_view
4236
                    WHERE
4237
                        c_id = $courseId AND
4238
                        user_id = $user_id  AND
4239
                        lp_id = $this->lp_id AND
4240
                        session_id <> 0
4241
                    ";
4242
            $result = Database::query($sql);
4243
            $resultFromOtherSessions = false;
4244
            while ($row = Database::fetch_array($result)) {
4245
                $lpIid = $row['iid'];
4246
                $sql = "SELECT status FROM $lp_item_view
4247
                        WHERE
4248
                            lp_view_id = $lpIid AND
4249
                            lp_item_id = $refs_list[$prereqs_string]
4250
                        LIMIT 1";
4251
                $resultRow = Database::query($sql);
4252
                if (Database::num_rows($resultRow)) {
4253
                    $statusResult = Database::fetch_array($resultRow);
4254
                    $status = $statusResult['status'];
4255
                    $checked = $status == $this->possible_status[2] || $status == $this->possible_status[3];
4256
                    if ($checked) {
4257
                        $resultFromOtherSessions = true;
4258
                        break;
4259
                    }
4260
                }
4261
            }
4262
4263
            return $resultFromOtherSessions;
4264
        }
4265
    }
4266
}
4267