Passed
Push — master ( 4791a8...56927d )
by Julito
09:52
created

ScormApi   F

Complexity

Total Complexity 156

Size/Duplication

Total Lines 808
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 418
dl 0
loc 808
rs 2
c 1
b 0
f 0
wmc 156

2 Methods

Rating   Name   Duplication   Size   Complexity  
F saveItem() 0 518 131
F switchItem() 0 252 25

How to fix   Complexity   

Complex Class

Complex classes like ScormApi 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 ScormApi, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use ChamiloSession as Session;
6
7
class ScormApi
8
{
9
    /**
10
     * Writes an item's new values into the database and returns the operation result.
11
     *
12
     * @param int    $lp_id                Learnpath ID
13
     * @param int    $user_id              User ID
14
     * @param int    $view_id              View ID
15
     * @param int    $item_id              Item ID
16
     * @param float  $score                Current score
17
     * @param float  $max                  Maximum score
18
     * @param float  $min                  Minimum score
19
     * @param string $status               Lesson status
20
     * @param int    $time                 Session time
21
     * @param string $suspend              Suspend data
22
     * @param string $location             Lesson location
23
     * @param array  $interactions         Interactions array
24
     * @param string $core_exit            Core exit SCORM string
25
     * @param int    $sessionId            Session ID
26
     * @param int    $courseId             Course ID
27
     * @param int    $lmsFinish            Whether the call was issued from SCORM's LMSFinish()
28
     * @param int    $userNavigatesAway    Whether the user is moving to another item
29
     * @param int    $statusSignalReceived Whether the SCO called SetValue(lesson_status)
30
     * @param int    $nextItem             Switch to next item
31
     *
32
     * @return bool|string|null The resulting JS string
33
     */
34
    public static function saveItem(
35
        $lp_id,
36
        $user_id,
37
        $view_id,
38
        $item_id,
39
        $score = -1.0,
40
        $max = -1.0,
41
        $min = -1.0,
42
        $status = '',
43
        $time = 0,
44
        $suspend = '',
45
        $location = '',
46
        $interactions = [],
47
        $core_exit = 'none',
48
        $sessionId = null,
49
        $courseId = null,
50
        $lmsFinish = 0,
51
        $userNavigatesAway = 0,
52
        $statusSignalReceived = 0,
53
        $nextItem = 0
54
    ) {
55
        $debug = 0;
56
        $return = null;
57
        $courseCode = api_get_course_id();
58
        if (!empty($courseId)) {
59
            $courseInfo = api_get_course_info_by_id($courseId);
60
            if ($courseInfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $courseInfo of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
61
                $courseCode = $courseInfo['code'];
62
            }
63
        }
64
65
        if ($debug > 0) {
66
            error_log('--------------------------------------');
67
            error_log('SAVE ITEM - lp_ajax_save_item.php');
68
            error_log('--------------------------------------');
69
            error_log("item_id: $item_id - lp_id: $lp_id - user_id: - $user_id - view_id: $view_id - item_id: $item_id");
70
            error_log("SCORE: $score - max:$max - min: $min - status:$status");
71
            error_log("TIME: $time - suspend: $suspend - location: $location - core_exit: $core_exit");
72
            error_log("finish: $lmsFinish - navigatesAway: $userNavigatesAway");
73
            error_log("courseCode: $courseCode");
74
        }
75
76
        $myLP = learnpath::getLpFromSession($courseCode, $lp_id, $user_id);
77
78
        if (!is_a($myLP, 'learnpath')) {
79
            if ($debug) {
80
                error_log('mylp variable is not an learnpath object');
81
            }
82
83
            return null;
84
        }
85
        $prerequisitesCheck = $myLP->prerequisites_match($item_id);
86
87
        /** @var learnpathItem $myLPI */
88
        if ($myLP->items && isset($myLP->items[$item_id])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $myLP->items of type learnpathItem[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
89
            $myLPI = $myLP->items[$item_id];
90
        }
91
92
        if (empty($myLPI)) {
93
            if ($debug > 0) {
94
                error_log("item #$item_id not found in the items array: ".print_r($myLP->items, 1));
0 ignored issues
show
Bug introduced by
Are you sure print_r($myLP->items, 1) of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

94
                error_log("item #$item_id not found in the items array: "./** @scrutinizer ignore-type */ print_r($myLP->items, 1));
Loading history...
95
            }
96
97
            return null;
98
        }
99
100
        // This functions sets the $this->db_item_view_id variable needed in get_status() see BT#5069
101
        $myLPI->set_lp_view($view_id);
102
103
        // Launch the prerequisites check and set error if needed
104
        if (true !== $prerequisitesCheck) {
105
            // If prerequisites were not matched, don't update any item info
106
            if ($debug) {
107
                error_log("prereq_check failed: ".intval($prerequisitesCheck));
108
            }
109
110
            return null;
111
        } else {
112
            if ($debug > 1) {
113
                error_log('Prerequisites are OK');
114
            }
115
116
            $logInfo = [
117
                'tool' => TOOL_LEARNPATH,
118
                'tool_id' => $lp_id,
119
                'tool_id_detail' => $item_id,
120
                'action' => 'view',
121
                'action_details' => $myLP->getCurrentAttempt(),
122
            ];
123
            Event::registerLog($logInfo);
0 ignored issues
show
Bug introduced by
The method registerLog() does not exist on Event. ( Ignorable by Annotation )

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

123
            Event::/** @scrutinizer ignore-call */ 
124
                   registerLog($logInfo);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
124
125
            if (isset($max) && -1 != $max) {
126
                $myLPI->max_score = $max;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $myLPI does not seem to be defined for all execution paths leading up to this point.
Loading history...
127
                $myLPI->set_max_score($max);
128
                if ($debug > 1) {
129
                    error_log("Setting max_score: $max");
130
                }
131
            }
132
133
            if (isset($min) && -1 != $min && 'undefined' !== $min) {
134
                $myLPI->min_score = $min;
135
                if ($debug > 1) {
136
                    error_log("Setting min_score: $min");
137
                }
138
            }
139
140
            // set_score function used to save the status, but this is not the case anymore
141
            if (isset($score) && -1 != $score) {
142
                if ($debug > 1) {
143
                    error_log('Calling set_score('.$score.')');
144
                    error_log('set_score changes the status to failed/passed if mastery score is provided');
145
                }
146
                $myLPI->set_score($score);
147
                if ($debug > 1) {
148
                    error_log('Done calling set_score '.$myLPI->get_score());
149
                }
150
            } else {
151
                if ($debug > 1) {
152
                    error_log('Score not updated');
153
                }
154
            }
155
156
            $statusIsSet = false;
157
            // Default behaviour.
158
            if (isset($status) && '' != $status && 'undefined' !== $status) {
159
                if ($debug > 1) {
160
                    error_log('Calling set_status('.$status.')');
161
                }
162
163
                $myLPI->set_status($status);
164
                $statusIsSet = true;
165
                if ($debug > 1) {
166
                    error_log('Done calling set_status: checking from memory: '.$myLPI->get_status(false));
167
                }
168
            } else {
169
                if ($debug > 1) {
170
                    error_log('Status not updated');
171
                }
172
            }
173
174
            $my_type = $myLPI->get_type();
175
            // Set status to completed for hotpotatoes if score > 80%.
176
            if ('hotpotatoes' === $my_type) {
177
                if ((empty($status) || 'undefined' === $status || 'not attempted' === $status) && $max > 0) {
178
                    if (($score / $max) > 0.8) {
179
                        $myStatus = 'completed';
180
                        if ($debug > 1) {
181
                            error_log('Calling set_status('.$myStatus.') for hotpotatoes');
182
                        }
183
                        $myLPI->set_status($myStatus);
184
                        $statusIsSet = true;
185
                        if ($debug > 1) {
186
                            error_log('Done calling set_status for hotpotatoes - now '.$myLPI->get_status(false));
187
                        }
188
                    }
189
                } elseif ('completed' === $status && $max > 0 && ($score / $max) < 0.8) {
190
                    $myStatus = 'failed';
191
                    if ($debug > 1) {
192
                        error_log('Calling set_status('.$myStatus.') for hotpotatoes');
193
                    }
194
                    $myLPI->set_status($myStatus);
195
                    $statusIsSet = true;
196
                    if ($debug > 1) {
197
                        error_log('Done calling set_status for hotpotatoes - now '.$myLPI->get_status(false));
198
                    }
199
                }
200
            } elseif ('sco' === $my_type) {
201
                /*
202
                 * This is a specific implementation for SCORM 1.2, matching page 26 of SCORM 1.2's RTE
203
                 * "Normally the SCO determines its own status and passes it to the LMS.
204
                 * 1) If cmi.core.credit is set to "credit" and there is a mastery
205
                 *    score in the manifest (adlcp:masteryscore), the LMS can change
206
                 *    the status to either passed or failed depending on the
207
                 *    student's score compared to the mastery score.
208
                 * 2) If there is no mastery score in the manifest
209
                 *    (adlcp:masteryscore), the LMS cannot override SCO
210
                 *    determined status.
211
                 * 3) If the student is taking the SCO for no-credit, there is no
212
                 *    change to the lesson_status, with one exception.  If the
213
                 *    lesson_mode is "browse", the lesson_status may change to
214
                 *    "browsed" even if the cmi.core.credit is set to no-credit.
215
                 * "
216
                 * Additionally, the LMS behaviour should be:
217
                 * If a SCO sets the cmi.core.lesson_status then there is no problem.
218
                 * However, the SCORM does not force the SCO to set the cmi.core.lesson_status.
219
                 * There is some additional requirements that must be adhered to
220
                 * successfully handle these cases:
221
                 * Upon initial launch
222
                 *   the LMS should set the cmi.core.lesson_status to "not attempted".
223
                 * Upon receiving the LMSFinish() call or the user navigates away,
224
                 *   the LMS should set the cmi.core.lesson_status for the SCO to "completed".
225
                 * After setting the cmi.core.lesson_status to "completed",
226
                 *   the LMS should now check to see if a Mastery Score has been
227
                 *   specified in the cmi.student_data.mastery_score, if supported,
228
                 *   or the manifest that the SCO is a member of.
229
                 *   If a Mastery Score is provided and the SCO did set the
230
                 *   cmi.core.score.raw, the LMS shall compare the cmi.core.score.raw
231
                 *   to the Mastery Score and set the cmi.core.lesson_status to
232
                 *   either "passed" or "failed".  If no Mastery Score is provided,
233
                 *   the LMS will leave the cmi.core.lesson_status as "completed"
234
                 */
235
                $masteryScore = $myLPI->get_mastery_score();
236
                if (-1 == $masteryScore || empty($masteryScore)) {
237
                    $masteryScore = false;
238
                }
239
                $credit = $myLPI->get_credit();
240
241
                /**
242
                 * 1) If cmi.core.credit is set to "credit" and there is a mastery
243
                 *    score in the manifest (adlcp:masteryscore), the LMS can change
244
                 *    the status to either passed or failed depending on the
245
                 *    student's score compared to the mastery score.
246
                 */
247
                if ('credit' === $credit &&
248
                    $masteryScore &&
249
                    (isset($score) && -1 != $score) &&
250
                    !$statusIsSet && !$statusSignalReceived
251
                ) {
252
                    if ($score >= $masteryScore) {
253
                        $myLPI->set_status('passed');
254
                        if ($debug) {
255
                            error_log('Set status: passed');
256
                        }
257
                    } else {
258
                        $myLPI->set_status('failed');
259
                        if ($debug) {
260
                            error_log('Set status: failed');
261
                        }
262
                    }
263
                    $statusIsSet = true;
264
                }
265
266
                /**
267
                 *  2) If there is no mastery score in the manifest
268
                 *    (adlcp:masteryscore), the LMS cannot override SCO
269
                 *    determined status.
270
                 */
271
                if (!$statusIsSet && !$masteryScore && !$statusSignalReceived) {
272
                    if (!empty($status)) {
273
                        if ($debug) {
274
                            error_log("Set status: $status because: statusSignalReceived ");
275
                        }
276
                        $myLPI->set_status($status);
277
                        $statusIsSet = true;
278
                    }
279
                    //if no status was set directly, we keep the previous one
280
                }
281
282
                /**
283
                 * 3) If the student is taking the SCO for no-credit, there is no
284
                 *    change to the lesson_status, with one exception. If the
285
                 *    lesson_mode is "browse", the lesson_status may change to
286
                 *    "browsed" even if the cmi.core.credit is set to no-credit.
287
                 */
288
                if (!$statusIsSet && 'no-credit' === $credit && !$statusSignalReceived) {
289
                    $mode = $myLPI->get_lesson_mode();
290
                    if ('browse' === $mode && 'browsed' === $status) {
291
                        if ($debug) {
292
                            error_log("Set status: $status because mode browse");
293
                        }
294
                        $myLPI->set_status($status);
295
                        $statusIsSet = true;
296
                    }
297
                    //if no status was set directly, we keep the previous one
298
                }
299
300
                /**
301
                 * If a SCO sets the cmi.core.lesson_status then there is no problem.
302
                 * However, the SCORM does not force the SCO to set the
303
                 * cmi.core.lesson_status.  There is some additional requirements
304
                 * that must be adhered to successfully handle these cases:.
305
                 */
306
                if (!$statusIsSet && empty($status) && !$statusSignalReceived) {
307
                    /**
308
                     * Upon initial launch the LMS should set the
309
                     * cmi.core.lesson_status to "not attempted".
310
                     */
311
                    // this case should be handled by LMSInitialize() and xajax_switch_item()
312
                    /**
313
                     * Upon receiving the LMSFinish() call or the user navigates
314
                     * away, the LMS should set the cmi.core.lesson_status for the
315
                     * SCO to "completed".
316
                     */
317
                    if ($lmsFinish || $userNavigatesAway) {
318
                        $myStatus = 'completed';
319
                        $updateStatus = true;
320
                        // Do not update status if "score as progress" and $userNavigatesAway
321
                        // The progress will be saved by the scorm BT#16766.
322
                        if ($userNavigatesAway && !$lmsFinish && $myLP->getUseScoreAsProgress()) {
323
                            $updateStatus = false;
324
                        }
325
326
                        if ($updateStatus) {
327
                            /**
328
                             * After setting the cmi.core.lesson_status to "completed",
329
                             *   the LMS should now check to see if a Mastery Score has been
330
                             *   specified in the cmi.student_data.mastery_score, if supported,
331
                             *   or the manifest that the SCO is a member of.
332
                             *   If a Mastery Score is provided and the SCO did set the
333
                             *   cmi.core.score.raw, the LMS shall compare the cmi.core.score.raw
334
                             *   to the Mastery Score and set the cmi.core.lesson_status to
335
                             *   either "passed" or "failed".  If no Mastery Score is provided,
336
                             *   the LMS will leave the cmi.core.lesson_status as "completed”.
337
                             */
338
                            if ($masteryScore && (isset($score) && -1 != $score)) {
339
                                if ($score >= $masteryScore) {
340
                                    $myStatus = 'passed';
341
                                } else {
342
                                    $myStatus = 'failed';
343
                                }
344
                            }
345
                            if ($debug) {
346
                                error_log("Set status: $myStatus because lmsFinish || userNavigatesAway");
347
                            }
348
                            $myLPI->set_status($myStatus);
349
                            $statusIsSet = true;
350
                        }
351
                    }
352
                }
353
                // End of type=='sco'
354
            }
355
356
            // If no previous condition changed the SCO status, proceed with a
357
            // generic behaviour
358
            if (!$statusIsSet && !$statusSignalReceived) {
359
                // Default behaviour
360
                if (isset($status) && '' != $status && 'undefined' !== $status) {
361
                    if ($debug > 1) {
362
                        error_log('Calling set_status('.$status.')');
363
                    }
364
365
                    $myLPI->set_status($status);
366
367
                    if ($debug > 1) {
368
                        error_log('Done calling set_status: checking from memory: '.$myLPI->get_status(false));
369
                    }
370
                } else {
371
                    if ($debug > 1) {
372
                        error_log("Status not updated");
373
                    }
374
                }
375
            }
376
377
            if (isset($time) && '' != $time && 'undefined' !== $time) {
378
                // If big integer, then it's a timestamp, otherwise it's normal scorm time.
379
                if ($debug > 1) {
380
                    error_log('Calling set_time('.$time.') ');
381
                }
382
                if ($time == intval(strval($time)) && $time > 1000000) {
383
                    if ($debug > 1) {
384
                        error_log("Time is INT");
385
                    }
386
                    $real_time = time() - $time;
387
                    if ($debug > 1) {
388
                        error_log('Calling $real_time '.$real_time.' ');
389
                    }
390
                    $myLPI->set_time($real_time, 'int');
391
                } else {
392
                    if ($debug > 1) {
393
                        error_log("Time is in SCORM format");
394
                    }
395
                    if ($debug > 1) {
396
                        error_log('Calling $time '.$time.' ');
397
                    }
398
                    $myLPI->set_time($time, 'scorm');
399
                }
400
            } else {
401
                $myLPI->current_stop_time = time();
402
            }
403
404
            if (isset($suspend) && '' != $suspend && 'undefined' !== $suspend) {
405
                $myLPI->current_data = $suspend;
406
            }
407
408
            if (isset($location) && '' != $location && 'undefined' !== $location) {
409
                $myLPI->set_lesson_location($location);
410
            }
411
412
            // Deal with interactions provided in arrays in the following format:
413
            // id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7)
414
            if (is_array($interactions) && count($interactions) > 0) {
415
                foreach ($interactions as $index => $interaction) {
416
                    //$mylpi->add_interaction($index,$interactions[$index]);
417
                    //fix DT#4444
418
                    $clean_interaction = str_replace('@.|@', ',', $interactions[$index]);
419
                    $myLPI->add_interaction($index, $clean_interaction);
420
                }
421
            }
422
423
            if ('undefined' !== $core_exit) {
424
                $myLPI->set_core_exit($core_exit);
425
            }
426
            $myLP->save_item($item_id, false);
427
        }
428
429
        $myStatusInDB = $myLPI->get_status(true);
430
        if ($debug) {
431
            error_log("Status in DB: $myStatusInDB");
432
        }
433
434
        if ('completed' !== $myStatusInDB &&
435
            'passed' !== $myStatusInDB &&
436
            'browsed' !== $myStatusInDB &&
437
            'failed' !== $myStatusInDB
438
        ) {
439
            $myStatusInMemory = $myLPI->get_status(false);
440
            if ($debug) {
441
                error_log("myStatusInMemory: $myStatusInMemory");
442
            }
443
444
            if ($myStatusInMemory != $myStatusInDB) {
445
                $myStatus = $myStatusInMemory;
446
            } else {
447
                $myStatus = $myStatusInDB;
448
            }
449
        } else {
450
            $myStatus = $myStatusInDB;
451
        }
452
453
        $myTotal = $myLP->getTotalItemsCountWithoutDirs();
454
        $myComplete = $myLP->get_complete_items_count();
455
        $myProgressMode = $myLP->get_progress_bar_mode();
456
        $myProgressMode = '' === $myProgressMode ? '%' : $myProgressMode;
457
458
        if ($debug > 1) {
459
            error_log("mystatus: $myStatus");
460
            error_log("myprogress_mode: $myProgressMode");
461
            error_log("progress: $myComplete / $myTotal");
462
        }
463
464
        if ('sco' !== $myLPI->get_type()) {
465
            // If this object's JS status has not been updated by the SCORM API, update now.
466
            $return .= "olms.lesson_status='".$myStatus."';";
467
        }
468
        $return .= "update_toc('".$myStatus."','".$item_id."');";
469
        $update_list = $myLP->get_update_queue();
470
471
        foreach ($update_list as $my_upd_id => $my_upd_status) {
472
            if ($my_upd_id != $item_id) {
473
                /* Only update the status from other items (i.e. parents and brothers),
474
                do not update current as we just did it already. */
475
                $return .= "update_toc('".$my_upd_status."','".$my_upd_id."');";
476
            }
477
        }
478
        $progressBarSpecial = false;
479
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
480
        if (true === $scoreAsProgressSetting) {
481
            $scoreAsProgress = $myLP->getUseScoreAsProgress();
482
            if ($scoreAsProgress) {
483
                if (isset($score) && -1 != $score) {
484
                    $score = $myLPI->get_score();
485
                    $maxScore = $myLPI->get_max();
486
                    $return .= "update_progress_bar('$score', '$maxScore', '$myProgressMode');";
487
                }
488
                $progressBarSpecial = true;
489
            }
490
        }
491
        if (!$progressBarSpecial) {
492
            $return .= "update_progress_bar('$myComplete', '$myTotal', '$myProgressMode');";
493
        }
494
495
        if (!Session::read('login_as')) {
496
            // If $_SESSION['login_as'] is set, then the user is an admin logged as the user.
497
            $tbl_track_login = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
498
499
            $sql = "SELECT login_id, login_date
500
                FROM $tbl_track_login
501
                WHERE login_user_id= ".api_get_user_id()."
502
                ORDER BY login_date DESC
503
                LIMIT 0,1";
504
505
            $q_last_connection = Database::query($sql);
506
            if (Database::num_rows($q_last_connection) > 0) {
507
                $current_time = api_get_utc_datetime();
508
                $row = Database::fetch_array($q_last_connection);
509
                $i_id_last_connection = $row['login_id'];
510
                $sql = "UPDATE $tbl_track_login
511
                    SET logout_date='".$current_time."'
512
                    WHERE login_id = $i_id_last_connection";
513
                Database::query($sql);
514
            }
515
        }
516
517
        if (2 == $myLP->get_type()) {
518
            $return .= 'update_stats();';
519
        }
520
521
        // To be sure progress is updated.
522
        $myLP->save_last($score);
523
524
        Session::write('lpobject', serialize($myLP));
525
        Session::write('oLP', $myLP);
526
        if ($debug > 0) {
527
            error_log("lp_view_session_id :".$myLP->lp_view_session_id);
528
            error_log('---------------- lp_ajax_save_item.php : save_item end ----- ');
529
        }
530
531
        $logInfo = [
532
            'tool' => TOOL_LEARNPATH,
533
            'tool_id' => $myLP->get_id(),
534
            'action_details' => $myLP->getCurrentAttempt(),
535
            'tool_id_detail' => $myLP->get_current_item_id(),
536
            'action' => 'save_item',
537
        ];
538
        Event::registerLog($logInfo);
539
540
        $nextItem = (int) $nextItem;
541
        if (!empty($nextItem)) {
542
            $return .= self::switchItem(
543
                $lp_id,
544
                $user_id,
545
                $view_id,
546
                $item_id,
547
                $nextItem
548
            );
549
        }
550
551
        return $return;
552
    }
553
554
    /**
555
     * Get one item's details.
556
     *
557
     * @param   int LP ID
558
     * @param   int user ID
559
     * @param   int View ID
560
     * @param   int Current item ID
561
     * @param   int New item ID
562
     */
563
    public static function switchItem($lpId, $user_id, $view_id, $current_item, $next_item)
564
    {
565
        $debug = 0;
566
        $return = '';
567
        if ($debug > 0) {
568
            error_log('--------------------------------------');
569
            error_log('SWITCH');
570
            error_log('Params('.$lpId.','.$user_id.','.$view_id.','.$current_item.','.$next_item.')');
571
        }
572
        //$objResponse = new xajaxResponse();
573
        /*$item_id may be one of:
574
         * -'next'
575
         * -'previous'
576
         * -'first'
577
         * -'last'
578
         * - a real item ID
579
         */
580
        $mylp = learnpath::getLpFromSession(api_get_course_id(), $lpId, $user_id);
581
        $new_item_id = 0;
582
        switch ($next_item) {
583
            case 'next':
584
                $mylp->set_current_item($current_item);
585
                $mylp->next();
586
                $new_item_id = $mylp->get_current_item_id();
587
                if ($debug > 1) {
588
                    error_log('In {next} - next item is '.$new_item_id.'(current: '.$current_item.')');
589
                }
590
                break;
591
            case 'previous':
592
                $mylp->set_current_item($current_item);
593
                $mylp->previous();
594
                $new_item_id = $mylp->get_current_item_id();
595
                if ($debug > 1) {
596
                    error_log('In {previous} - next item is '.$new_item_id.'(current: '.$current_item.')');
597
                }
598
                break;
599
            case 'first':
600
                $mylp->set_current_item($current_item);
601
                $mylp->first();
602
                $new_item_id = $mylp->get_current_item_id();
603
                if ($debug > 1) {
604
                    error_log('In {first} - next item is '.$new_item_id.'(current: '.$current_item.')');
605
                }
606
                break;
607
            case 'last':
608
                break;
609
            default:
610
                // Should be filtered to check it's not hacked.
611
                if ($next_item == $current_item) {
612
                    // If we're opening the same item again.
613
                    $mylp->items[$current_item]->restart();
614
                }
615
                $new_item_id = $next_item;
616
                $mylp->set_current_item($new_item_id);
617
                if ($debug > 1) {
618
                    error_log('In {default} - next item is '.$new_item_id.'(current: '.$current_item.')');
619
                }
620
                break;
621
        }
622
623
        if (WhispeakAuthPlugin::isLpItemMarked($new_item_id)) {
624
            ChamiloSession::write(
625
                WhispeakAuthPlugin::SESSION_LP_ITEM,
626
                ['lp' => $lpId, 'lp_item' => $new_item_id, 'src' => '']
627
            );
628
        }
629
630
        $mylp->start_current_item(true);
631
        if ($mylp->force_commit) {
632
            $mylp->save_current();
633
        }
634
635
        if (is_object($mylp->items[$new_item_id])) {
636
            $mylpi = $mylp->items[$new_item_id];
637
        } else {
638
            if ($debug > 1) {
639
                error_log('In switch_item_details - generating new item object', 0);
640
            }
641
            $mylpi = new learnpathItem($new_item_id, $user_id);
642
            $mylpi->set_lp_view($view_id);
643
        }
644
        /*
645
         * now get what's needed by the SCORM API:
646
         * -score
647
         * -max
648
         * -min
649
         * -lesson_status
650
         * -session_time
651
         * -suspend_data
652
         */
653
        $myscore = $mylpi->get_score();
654
        $mymax = $mylpi->get_max();
655
        if ('' === $mymax) {
656
            $mymax = "''";
657
        }
658
        $mymin = $mylpi->get_min();
659
        $mylesson_status = $mylpi->get_status();
660
        $mylesson_location = $mylpi->get_lesson_location();
661
        $mytotal_time = $mylpi->get_scorm_time('js');
662
        $mymastery_score = $mylpi->get_mastery_score();
663
        $mymax_time_allowed = $mylpi->get_max_time_allowed();
664
        $mylaunch_data = $mylpi->get_launch_data();
665
        /*
666
        if ($mylpi->get_type() == 'asset') {
667
            // Temporary measure to save completion of an asset. Later on,
668
            // Chamilo should trigger something on unload, maybe...
669
            // (even though that would mean the last item cannot be completed)
670
            $mylesson_status = 'completed';
671
            $mylpi->set_status('completed');
672
            $mylpi->save();
673
        }
674
        */
675
        $mysession_time = $mylpi->get_total_time();
676
        $mysuspend_data = $mylpi->get_suspend_data();
677
        $mylesson_location = $mylpi->get_lesson_location();
678
        $myic = $mylpi->get_interactions_count();
679
        $myistring = '';
680
        for ($i = 0; $i < $myic; $i++) {
681
            $myistring .= ",[".$i.",'','','','','','','']";
682
        }
683
        if (!empty($myistring)) {
684
            $myistring = substr($myistring, 1);
685
        }
686
        /*
687
         * The following lines should reinitialize the values for the SCO
688
         * However, due to many complications, we are now relying more on the
689
         * LMSInitialize() call and its underlying lp_ajax_initialize.php call
690
         * so this code is technically deprecated (but the change of item_id should
691
         * remain). However, due to numerous technical issues with SCORM, we prefer
692
         * leaving it as a double-lock security. If removing, please test carefully
693
         * with both SCORM and proper learning path tracking.
694
         */
695
        $return .=
696
            "olms.score=".$myscore.";".
697
            "olms.max=".$mymax.";".
698
            "olms.min=".$mymin.";".
699
            "olms.lesson_status='".$mylesson_status."';".
700
            "olms.lesson_location='".$mylesson_location."';".
701
            "olms.session_time='".$mysession_time."';".
702
            "olms.suspend_data='".$mysuspend_data."';".
703
            "olms.total_time = '".$mytotal_time."';".
704
            "olms.mastery_score = '".$mymastery_score."';".
705
            "olms.max_time_allowed = '".$mymax_time_allowed."';".
706
            "olms.launch_data = '".$mylaunch_data."';".
707
            "olms.interactions = new Array(".$myistring.");".
708
            "olms.item_objectives = new Array();".
709
            "olms.G_lastError = 0;".
710
            "olms.G_LastErrorMessage = 'No error';".
711
            "olms.finishSignalReceived = 0;";
712
        /*
713
         * and re-initialise the rest
714
         * -lms_lp_id
715
         * -lms_item_id
716
         * -lms_old_item_id
717
         * -lms_new_item_id
718
         * -lms_initialized
719
         * -lms_progress_bar_mode
720
         * -lms_view_id
721
         * -lms_user_id
722
         */
723
        $mytotal = $mylp->getTotalItemsCountWithoutDirs();
724
        $mycomplete = $mylp->get_complete_items_count();
725
        $myprogress_mode = $mylp->get_progress_bar_mode();
726
        $myprogress_mode = ('' == $myprogress_mode ? '%' : $myprogress_mode);
727
        $mynext = $mylp->get_next_item_id();
728
        $myprevious = $mylp->get_previous_item_id();
729
        $myitemtype = $mylpi->get_type();
730
        $mylesson_mode = $mylpi->get_lesson_mode();
731
        $mycredit = $mylpi->get_credit();
732
        $mylaunch_data = $mylpi->get_launch_data();
733
        $myinteractions_count = $mylpi->get_interactions_count();
734
        //$myobjectives_count = $mylpi->get_objectives_count();
735
        $mycore_exit = $mylpi->get_core_exit();
736
737
        $return .=
738
            //"saved_lesson_status='not attempted';" .
739
            "olms.lms_lp_id=".$lpId.";".
740
            "olms.lms_item_id=".$new_item_id.";".
741
            "olms.lms_old_item_id=0;".
742
            //"lms_been_synchronized=0;" .
743
            "olms.lms_initialized=0;".
744
            //"lms_total_lessons=".$mytotal.";" .
745
            //"lms_complete_lessons=".$mycomplete.";" .
746
            //"lms_progress_bar_mode='".$myprogress_mode."';" .
747
            "olms.lms_view_id=".$view_id.";".
748
            "olms.lms_user_id=".$user_id.";".
749
            "olms.next_item=".$new_item_id.";".// This one is very important to replace possible literal strings.
750
            "olms.lms_next_item=".$mynext.";".
751
            "olms.lms_previous_item=".$myprevious.";".
752
            "olms.lms_item_type = '".$myitemtype."';".
753
            "olms.lms_item_credit = '".$mycredit."';".
754
            "olms.lms_item_lesson_mode = '".$mylesson_mode."';".
755
            "olms.lms_item_launch_data = '".$mylaunch_data."';".
756
            "olms.lms_item_interactions_count = '".$myinteractions_count."';".
757
            "olms.lms_item_objectives_count = '".$myinteractions_count."';".
758
            "olms.lms_item_core_exit = '".$mycore_exit."';".
759
            "olms.asset_timer = 0;";
760
761
        $sessionId = api_get_session_id();
762
        $updateMinTime = '';
763
        if (Tracking::minimumTimeAvailable($sessionId, api_get_course_int_id())) {
764
            $timeLp = $mylp->getAccumulateWorkTime();
765
            $timeTotalCourse = $mylp->getAccumulateWorkTimeTotalCourse();
766
            // Minimum connection percentage
767
            $perc = 100;
768
            // Time from the course
769
            $tc = $timeTotalCourse;
770
            // Percentage of the learning paths
771
            $pl = 0;
772
            if (!empty($timeTotalCourse)) {
773
                $pl = $timeLp / $timeTotalCourse;
774
            }
775
776
            // Minimum time for each learning path
777
            $time_total = intval($pl * $tc * $perc / 100) * 60;
778
            $lpTimeList = Tracking::getCalculateTime($user_id, api_get_course_int_id(), $sessionId);
779
            $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$lpId]) ? $lpTimeList[TOOL_LEARNPATH][$lpId] : 0;
780
781
            if ($lpTime >= $time_total) {
782
                $time_spent = $time_total;
783
            } else {
784
                $time_spent = $lpTime;
785
            }
786
787
            $hour = (intval($lpTime / 3600)) < 10 ? '0'.intval($lpTime / 3600) : intval($lpTime / 3600);
788
            $minute = date('i', $lpTime);
789
            $second = date('s', $lpTime);
790
            $updateMinTime = "update_time_bar('$time_spent','$time_total','%');".
791
                "update_chronometer('$hour','$minute','$second');";
792
        }
793
794
        $return .=
795
            "update_toc('unhighlight','".$current_item."');".
796
            "update_toc('highlight','".$new_item_id."');".
797
            "update_toc('$mylesson_status','".$new_item_id."');".
798
            "update_progress_bar('$mycomplete','$mytotal','$myprogress_mode');".
799
            $updateMinTime
800
        ;
801
802
        //$return .= 'updateGamificationValues(); ';
803
        $mylp->set_error_msg('');
804
        $mylp->prerequisites_match(); // Check the prerequisites are all complete.
805
        if ($debug > 1) {
806
            error_log($return);
807
            error_log('Prereq_match() returned '.htmlentities($mylp->error), 0);
808
        }
809
        // Save the new item ID for the exercise tool to use.
810
        Session::write('scorm_item_id', $new_item_id);
811
        Session::write('lpobject', serialize($mylp));
812
        Session::write('oLP', $mylp);
813
814
        return $return;
815
    }
816
817
}
818