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

ScormApi::saveItem()   F

Complexity

Conditions 131

Size

Total Lines 518
Code Lines 259

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 131
eloc 259
nop 19
dl 0
loc 518
rs 3.3333
c 1
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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