Passed
Push — 1.11.x ( 5b9a4b...89d96a )
by Julito
12:39
created

learnpathItem   F

Complexity

Total Complexity 653

Size/Duplication

Total Lines 4542
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 2367
dl 0
loc 4542
rs 0.8
c 0
b 0
f 0
wmc 653

88 Methods

Rating   Name   Duplication   Size   Complexity  
A setPrerequisiteMinScore() 0 3 1
A getPrerequisiteMinScore() 0 3 1
A setPrerequisiteMaxScore() 0 3 1
A getPrerequisiteMaxScore() 0 3 1
B __construct() 0 89 9
A add_interaction() 0 6 2
A add_objective() 0 9 3
A fixAudio() 0 24 5
A delete() 0 29 4
A get_id() 0 7 2
A get_core_exit() 0 3 1
A get_objectives_count() 0 8 2
A get_max() 0 18 6
A getScormTimeFromParameter() 0 14 3
B fixAbusiveTime() 0 64 9
A get_attempt_id() 0 8 2
A get_lesson_location() 0 11 2
A get_parent() 0 7 2
A get_view_count() 0 7 2
C get_file_path() 0 43 12
F get_resources_from_source() 0 514 61
A get_launch_data() 0 11 2
A get_description() 0 7 2
A get_lesson_mode() 0 12 4
A get_interactions_js_array() 0 22 4
A get_max_time_allowed() 0 7 2
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 is_done() 0 29 4
A get_prereq_string() 0 7 2
A get_suspend_data() 0 13 2
A open() 0 29 6
A get_level() 0 7 2
B get_credit() 0 35 7
A get_children() 0 10 3
A get_seriousgame_mode() 0 23 5
B get_scorm_time() 0 55 8
A get_score() 0 8 2
B isRestartAllowed() 0 25 7
A get_path() 0 7 2
A get_type() 0 8 2
A get_terms() 0 9 1
A get_ref() 0 3 1
B get_total_time() 0 59 10
A load_interactions() 0 29 3
A get_min() 0 7 2
C get_status() 0 49 13
A get_current_start_time() 0 7 2
B get_interactions_count() 0 40 6
A set_level() 0 3 1
A getForumThread() 0 50 3
A dissociateForumThread() 0 18 2
B write_objectives_to_db() 0 74 10
B addAudio() 0 70 5
A set_lesson_location() 0 9 2
A update_time() 0 12 3
A set_path() 0 4 2
A lpItemHasThread() 0 34 2
B set_status() 0 33 7
A createForumThread() 0 42 2
F save() 0 169 40
B humanize_status() 0 45 9
A set_title() 0 4 2
B set_score() 0 37 7
C set_time() 0 53 13
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 set_description() 0 4 2
A set_attempt_id() 0 9 3
A set_prevent_reinit() 0 5 2
B set_lp_view() 0 89 10
B getStatusFromOtherSessions() 0 41 6
A getLastScormSessionTime() 0 3 1
F scorm_update_time() 0 94 14
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 486 85
F parse_prereq() 0 655 125
B close() 0 35 8
A isLpItemAutoComplete() 0 15 5

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