Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * functions used by AICC packages.
19
 *
20
 * @package    mod_scorm
21
 * @copyright 1999 onwards Roberto Pinna
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
function scorm_add_time($a, $b) {
26
    $aes = explode(':', $a);
27
    $bes = explode(':', $b);
28
    $aseconds = explode('.', $aes[2]);
29
    $bseconds = explode('.', $bes[2]);
30
    $change = 0;
31
 
32
    $acents = 0;  // Cents.
33
    if (count($aseconds) > 1) {
34
        $acents = $aseconds[1];
35
    }
36
    $bcents = 0;
37
    if (count($bseconds) > 1) {
38
        $bcents = $bseconds[1];
39
    }
40
    $cents = $acents + $bcents;
41
    $change = floor($cents / 100);
42
    $cents = $cents - ($change * 100);
43
    if (floor($cents) < 10) {
44
        $cents = '0'. $cents;
45
    }
46
 
47
    $secs = $aseconds[0] + $bseconds[0] + $change;  // Seconds.
48
    $change = floor($secs / 60);
49
    $secs = $secs - ($change * 60);
50
    if (floor($secs) < 10) {
51
        $secs = '0'. $secs;
52
    }
53
 
54
    $mins = $aes[1] + $bes[1] + $change;   // Minutes.
55
    $change = floor($mins / 60);
56
    $mins = $mins - ($change * 60);
57
    if ($mins < 10) {
58
        $mins = '0' .  $mins;
59
    }
60
 
61
    $hours = $aes[0] + $bes[0] + $change;  // Hours.
62
    if ($hours < 10) {
63
        $hours = '0' . $hours;
64
    }
65
 
66
    if ($cents != '0') {
67
        return $hours . ":" . $mins . ":" . $secs . '.' . $cents;
68
    } else {
69
        return $hours . ":" . $mins . ":" . $secs;
70
    }
71
}
72
 
73
/**
74
 * Take the header row of an AICC definition file
75
 * and returns sequence of columns and a pointer to
76
 * the sco identifier column.
77
 *
78
 * @param string $row AICC header row
79
 * @param string $mastername AICC sco identifier column
80
 * @return mixed
81
 */
82
function scorm_get_aicc_columns($row, $mastername='system_id') {
83
    $tok = strtok(strtolower($row), "\",\n\r");
84
    $result = new stdClass();
85
    $result->columns = array();
86
    $result->mastercol = 0;
87
    $i = 0;
88
    while ($tok) {
89
        if ($tok != '') {
90
            $result->columns[] = $tok;
91
            if ($tok == $mastername) {
92
                $result->mastercol = $i;
93
            }
94
            $i++;
95
        }
96
        $tok = strtok("\",\n\r");
97
    }
98
    return $result;
99
}
100
 
101
/**
102
 * Given a colums array return a string containing the regular
103
 * expression to match the columns in a text row.
104
 *
105
 * @param array $column The header columns
106
 * @param string $remodule The regular expression module for a single column
107
 * @return string
108
 */
109
function scorm_forge_cols_regexp($columns, $remodule='(".*")?,') {
110
    $regexp = '/^';
111
    foreach ($columns as $column) {
112
        $regexp .= $remodule;
113
    }
114
    $regexp = substr($regexp, 0, -1) . '/';
115
    return $regexp;
116
}
117
 
118
/**
119
 * Sets up AICC packages
120
 * Called whenever package changes
121
 * @param object $scorm instance - fields are updated and changes saved into database
122
 * @return bool
123
 */
124
function scorm_parse_aicc(&$scorm) {
125
    global $DB;
126
 
127
    if ($scorm->scormtype == SCORM_TYPE_AICCURL) {
128
        return scorm_aicc_generate_simple_sco($scorm);
129
    }
130
    if (!isset($scorm->cmid)) {
131
        $cm = get_coursemodule_from_instance('scorm', $scorm->id);
132
        $scorm->cmid = $cm->id;
133
    }
134
    $context = context_module::instance($scorm->cmid);
135
 
136
    $fs = get_file_storage();
137
 
138
    $files = $fs->get_area_files($context->id, 'mod_scorm', 'content', 0, 'sortorder, itemid, filepath, filename', false);
139
 
140
    $version = 'AICC';
141
    $ids = array();
142
    $courses = array();
143
    $extaiccfiles = array('crs', 'des', 'au', 'cst', 'ort', 'pre', 'cmp');
144
 
145
    foreach ($files as $file) {
146
        $filename = $file->get_filename();
147
        $ext = substr($filename, strrpos($filename, '.'));
148
        $extension = strtolower(substr($ext, 1));
149
        if (in_array($extension, $extaiccfiles)) {
150
            $id = strtolower(basename($filename, $ext));
151
            if (!isset($ids[$id])) {
152
                $ids[$id] = new stdClass();
153
            }
154
            $ids[$id]->$extension = $file;
155
        }
156
    }
157
 
158
    foreach ($ids as $courseid => $id) {
159
        if (!isset($courses[$courseid])) {
160
            $courses[$courseid] = new stdClass();
161
        }
162
        if (isset($id->crs)) {
163
            $contents = $id->crs->get_content();
164
            $rows = explode("\r\n", $contents);
165
            if (is_array($rows)) {
166
                foreach ($rows as $row) {
167
                    if (preg_match("/^(.+)=(.+)$/", $row, $matches)) {
168
                        switch (strtolower(trim($matches[1]))) {
169
                            case 'course_id':
170
                                $courses[$courseid]->id = trim($matches[2]);
171
                            break;
172
                            case 'course_title':
173
                                $courses[$courseid]->title = trim($matches[2]);
174
                            break;
175
                            case 'version':
176
                                $courses[$courseid]->version = 'AICC_'.trim($matches[2]);
177
                            break;
178
                        }
179
                    }
180
                }
181
            }
182
        }
183
        if (isset($id->des)) {
184
            $contents = $id->des->get_content();
185
            $rows = explode("\r\n", $contents);
186
            $columns = scorm_get_aicc_columns($rows[0]);
187
            $regexp = scorm_forge_cols_regexp($columns->columns);
188
            for ($i = 1; $i < count($rows); $i++) {
189
                if (preg_match($regexp, $rows[$i], $matches)) {
190
                    for ($j = 0; $j < count($columns->columns); $j++) {
191
                        $column = $columns->columns[$j];
192
                        if (!isset($courses[$courseid]->elements[substr(trim($matches[$columns->mastercol + 1]), 1 , -1)])) {
193
                            $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol + 1]), 1 , -1)] = new stdClass();
194
                        }
195
                        $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol + 1]), 1 , -1)]->$column = substr(trim($matches[$j + 1]), 1, -1);
196
                    }
197
                }
198
            }
199
        }
200
        if (isset($id->au)) {
201
            $contents = $id->au->get_content();
202
            $rows = explode("\r\n", $contents);
203
            $columns = scorm_get_aicc_columns($rows[0]);
204
            $regexp = scorm_forge_cols_regexp($columns->columns);
205
            for ($i = 1; $i < count($rows); $i++) {
206
                if (preg_match($regexp, $rows[$i], $matches)) {
207
                    for ($j = 0; $j < count($columns->columns); $j++) {
208
                        $column = $columns->columns[$j];
209
                        $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol + 1]), 1, -1)]->$column = substr(trim($matches[$j + 1]), 1, -1);
210
                    }
211
                }
212
            }
213
        }
214
        if (isset($id->cst)) {
215
            $contents = $id->cst->get_content();
216
            $rows = explode("\r\n", $contents);
217
            $columns = scorm_get_aicc_columns($rows[0], 'block');
218
            $regexp = scorm_forge_cols_regexp($columns->columns, '(.+)?,');
219
            for ($i = 1; $i < count($rows); $i++) {
220
                if (preg_match($regexp, $rows[$i], $matches)) {
221
                    for ($j = 0; $j < count($columns->columns); $j++) {
222
                        if ($j != $columns->mastercol) {
223
                            $element = substr(trim($matches[$j + 1]), 1 , -1);
224
                            if (!empty($element)) {
225
                                $courses[$courseid]->elements[$element]->parent = substr(trim($matches[$columns->mastercol + 1]), 1, -1);
226
                            }
227
                        }
228
                    }
229
                }
230
            }
231
        }
232
        if (isset($id->ort)) {
233
            $contents = $id->ort->get_content();
234
            $rows = explode("\r\n", $contents);
235
            $columns = scorm_get_aicc_columns($rows[0], 'course_element');
236
            $regexp = scorm_forge_cols_regexp($columns->columns, '(.+)?,');
237
            for ($i = 1; $i < count($rows); $i++) {
238
                if (preg_match($regexp, $rows[$i], $matches)) {
239
                    for ($j = 0; $j < count($matches) - 1; $j++) {
240
                        if ($j != $columns->mastercol) {
241
                            $courses[$courseid]->elements[substr(trim($matches[$j + 1]), 1, -1)]->parent = substr(trim($matches[$columns->mastercol + 1]), 1, -1);
242
                        }
243
                    }
244
                }
245
            }
246
        }
247
        if (isset($id->pre)) {
248
            $contents = $id->pre->get_content();
249
            $rows = explode("\r\n", $contents);
250
            $columns = scorm_get_aicc_columns($rows[0], 'structure_element');
251
            $regexp = scorm_forge_cols_regexp($columns->columns, '(.+),');
252
            for ($i = 1; $i < count($rows); $i++) {
253
                if (preg_match($regexp, $rows[$i], $matches)) {
254
                    $elementid = trim($matches[$columns->mastercol + 1]);
255
                    $elementid = trim(trim($elementid, '"'), "'"); // Remove any quotes.
256
 
257
                    $prereq = trim($matches[2 - $columns->mastercol]);
258
                    $prereq = trim(trim($prereq, '"'), "'"); // Remove any quotes.
259
 
260
                    $courses[$courseid]->elements[$elementid]->prerequisites = $prereq;
261
                }
262
            }
263
        }
264
        if (isset($id->cmp)) {
265
            $contents = $id->cmp->get_content();
266
            $rows = explode("\r\n", $contents);
267
        }
268
    }
269
 
270
    $oldscoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id));
271
    $sortorder = 0;
272
    $launch = 0;
273
    if (isset($courses)) {
274
        foreach ($courses as $course) {
275
            $sortorder++;
276
            $sco = new stdClass();
277
            $sco->identifier = $course->id;
278
            $sco->scorm = $scorm->id;
279
            $sco->organization = '';
280
            $sco->title = $course->title;
281
            $sco->parent = '/';
282
            $sco->launch = '';
283
            $sco->scormtype = '';
284
            $sco->sortorder = $sortorder;
285
 
286
            if ($ss = $DB->get_record('scorm_scoes', array('scorm' => $scorm->id,
287
                                                           'identifier' => $sco->identifier))) {
288
                $id = $ss->id;
289
                $sco->id = $id;
290
                $DB->update_record('scorm_scoes', $sco);
291
                unset($oldscoes[$id]);
292
            } else {
293
                $id = $DB->insert_record('scorm_scoes', $sco);
294
            }
295
 
296
            if ($launch == 0) {
297
                $launch = $id;
298
            }
299
            if (isset($course->elements)) {
300
                foreach ($course->elements as $element) {
301
                    unset($sco);
302
                    $sco = new stdClass();
303
                    $sco->identifier = $element->system_id;
304
                    $sco->scorm = $scorm->id;
305
                    $sco->organization = $course->id;
306
                    $sco->title = $element->title;
307
 
308
                    if (!isset($element->parent)) {
309
                        $sco->parent = '/';
310
                    } else if (strtolower($element->parent) == 'root') {
311
                        $sco->parent = $course->id;
312
                    } else {
313
                        $sco->parent = $element->parent;
314
                    }
315
                    $sco->launch = '';
316
                    $sco->scormtype = '';
317
                    $sco->previous = 0;
318
                    $sco->next = 0;
319
                    $id = null;
320
                    // Is it an Assignable Unit (AU)?
321
                    if (isset($element->file_name)) {
322
                        $sco->launch = $element->file_name;
323
                        $sco->scormtype = 'sco';
324
                    }
325
                    if ($oldscoid = scorm_array_search('identifier', $sco->identifier, $oldscoes)) {
326
                        $sco->id = $oldscoid;
327
                        $DB->update_record('scorm_scoes', $sco);
328
                        $id = $oldscoid;
329
                        $DB->delete_records('scorm_scoes_data', array('scoid' => $oldscoid));
330
                        unset($oldscoes[$oldscoid]);
331
                    } else {
332
                        $id = $DB->insert_record('scorm_scoes', $sco);
333
                    }
334
                    if (!empty($id)) {
335
                        $scodata = new stdClass();
336
                        $scodata->scoid = $id;
337
                        if (isset($element->web_launch)) {
338
                            $scodata->name = 'parameters';
339
                            $scodata->value = $element->web_launch;
340
                            $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
341
                        }
342
                        if (isset($element->prerequisites)) {
343
                            $scodata->name = 'prerequisites';
344
                            $scodata->value = $element->prerequisites;
345
                            $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
346
                        }
347
                        if (isset($element->max_time_allowed)) {
348
                            $scodata->name = 'max_time_allowed';
349
                            $scodata->value = $element->max_time_allowed;
350
                            $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
351
                        }
352
                        if (isset($element->time_limit_action)) {
353
                            $scodata->name = 'time_limit_action';
354
                            $scodata->value = $element->time_limit_action;
355
                            $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
356
                        }
357
                        if (isset($element->mastery_score)) {
358
                            $scodata->name = 'mastery_score';
359
                            $scodata->value = $element->mastery_score;
360
                            $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
361
                        }
362
                        if (isset($element->core_vendor)) {
363
                            $scodata->name = 'datafromlms';
364
                            $scodata->value = preg_replace('/<cr>/i', "\r\n", $element->core_vendor);
365
                            $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
366
                        }
367
                    }
368
                    if ($launch == 0) {
369
                        $launch = $id;
370
                    }
371
                }
372
            }
373
        }
374
    }
375
    if (!empty($oldscoes)) {
376
        foreach ($oldscoes as $oldsco) {
377
            scorm_delete_tracks($scorm->id, $oldsco->id);
378
            $DB->delete_records('scorm_scoes', ['id' => $oldsco->id]);
379
        }
380
    }
381
 
382
    // Find first launchable object.
383
    $sqlselect = 'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true);
384
    // We use get_records here as we need to pass a limit in the query that works cross db.
385
    $scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scorm->id), 'sortorder', 'id', 0, 1);
386
    if (!empty($scoes)) {
387
        $sco = reset($scoes); // We only care about the first record - the above query only returns one.
388
        $scorm->launch = $sco->id;
389
    } else {
390
        $scorm->launch = $launch;
391
    }
392
 
393
    $scorm->version = 'AICC';
394
 
395
    return true;
396
}
397
 
398
/**
399
 * Given a scormid creates an AICC Session record to allow HACP
400
 *
401
 * @param int $scormid - id from scorm table
402
 * @return string hacpsession
403
 */
404
function scorm_aicc_get_hacp_session($scormid) {
405
    global $USER, $DB, $SESSION;
406
    $cfgscorm = get_config('scorm');
407
    if (empty($cfgscorm->allowaicchacp)) {
408
        return false;
409
    }
410
    $now = time();
411
 
412
    $hacpsession = $SESSION->scorm;
413
    $hacpsession->scormid = $scormid;
414
    $hacpsession->hacpsession = random_string(20);
415
    $hacpsession->userid      = $USER->id;
416
    $hacpsession->timecreated = $now;
417
    $hacpsession->timemodified = $now;
418
    $DB->insert_record('scorm_aicc_session', $hacpsession);
419
 
420
    return $hacpsession->hacpsession;
421
}
422
 
423
/**
424
 * Check the hacp_session for whether it is valid.
425
 *
426
 * @param string $hacpsession The hacpsession value to check (optional). Normally leave this blank
427
 *      and this function will do required_param('sesskey', ...).
428
 * @return mixed - false if invalid, otherwise returns record from scorm_aicc_session table.
429
 */
430
function scorm_aicc_confirm_hacp_session($hacpsession) {
431
    global $DB;
432
    $cfgscorm = get_config('scorm');
433
    if (empty($cfgscorm->allowaicchacp)) {
434
        return false;
435
    }
436
    $time = time() - ($cfgscorm->aicchacptimeout * 60);
437
    $sql = "hacpsession = ? AND timemodified > ?";
438
    $hacpsession = $DB->get_record_select('scorm_aicc_session', $sql, array($hacpsession, $time));
439
    if (!empty($hacpsession)) { // Update timemodified as this is still an active session - resets the timeout.
440
        $hacpsession->timemodified = time();
441
        $DB->update_record('scorm_aicc_session', $hacpsession);
442
    }
443
    return $hacpsession;
444
}
445
 
446
/**
447
 * generate a simple single activity AICC object
448
 * structure to wrap around and externally linked
449
 * AICC package URL
450
 *
451
 * @param object $scorm package record
452
 */
453
function scorm_aicc_generate_simple_sco($scorm) {
454
    global $DB;
455
    // Find the oldest one.
456
    $scos = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id), 'id');
457
    if (!empty($scos)) {
458
        $sco = array_shift($scos);
459
    } else {
460
        $sco = new stdClass();
461
    }
462
    // Get rid of old ones.
463
    foreach ($scos as $oldsco) {
464
        scorm_delete_tracks($scorm->id, $oldsco->id);
465
        $DB->delete_records('scorm_scoes', ['id' => $oldsco->id]);
466
    }
467
 
468
    $sco->identifier = 'A1';
469
    $sco->scorm = $scorm->id;
470
    $sco->organization = '';
471
    $sco->title = $scorm->name;
472
    $sco->parent = '/';
473
    // Add the HACP signal to the activity launcher.
474
    if (preg_match('/\?/', $scorm->reference)) {
475
        $sco->launch = $scorm->reference.'&CMI=HACP';
476
    } else {
477
        $sco->launch = $scorm->reference.'?CMI=HACP';
478
    }
479
    $sco->scormtype = 'sco';
480
    if (isset($sco->id)) {
481
        $DB->update_record('scorm_scoes', $sco);
482
        $id = $sco->id;
483
    } else {
484
        $id = $DB->insert_record('scorm_scoes', $sco);
485
    }
486
    return $id;
487
}
488
 
489
/**
490
 * Sets up $userdata array and default values for AICC package.
491
 *
492
 * @param stdClass $userdata an empty stdClass variable that should be set up with user values
493
 * @param object $scorm package record
494
 * @param string $scoid SCO Id
495
 * @param string $attempt attempt number for the user
496
 * @param string $mode scorm display mode type
497
 * @return array The default values that should be used for AICC package
498
 */
499
function get_scorm_default (&$userdata, $scorm, $scoid, $attempt, $mode) {
500
    global $USER;
501
    $aiccuserid = get_config('scorm', 'aiccuserid');
502
    if (!empty($aiccuserid)) {
503
        $userdata->student_id = $USER->id;
504
    } else {
505
        $userdata->student_id = $USER->username;
506
    }
507
    $userdata->student_name = $USER->lastname .', '. $USER->firstname;
508
 
509
    if ($usertrack = scorm_get_tracks($scoid, $USER->id, $attempt)) {
510
        foreach ($usertrack as $key => $value) {
511
            $userdata->$key = $value;
512
        }
513
    } else {
514
        $userdata->status = '';
515
        $userdata->score_raw = '';
516
    }
517
 
518
    if ($scodatas = scorm_get_sco($scoid, SCO_DATA)) {
519
        foreach ($scodatas as $key => $value) {
520
            $userdata->$key = $value;
521
        }
522
    } else {
523
        throw new \moodle_exception('cannotfindsco', 'scorm');
524
    }
525
    if (!$sco = scorm_get_sco($scoid)) {
526
        throw new \moodle_exception('cannotfindsco', 'scorm');
527
    }
528
 
529
    $userdata->mode = 'normal';
530
    if (!empty($mode)) {
531
        $userdata->mode = $mode;
532
    }
533
    if ($userdata->mode == 'normal') {
534
        $userdata->credit = 'credit';
535
    } else {
536
        $userdata->credit = 'no-credit';
537
    }
538
 
539
    if (isset($userdata->status)) {
540
        if ($userdata->status == '') {
541
            $userdata->entry = 'ab-initio';
542
        } else {
543
            if (isset($userdata->{'cmi.core.exit'}) && ($userdata->{'cmi.core.exit'} == 'suspend')) {
544
                $userdata->entry = 'resume';
545
            } else {
546
                $userdata->entry = '';
547
            }
548
        }
549
    }
550
 
551
    $def = array();
552
    $def['cmi.core.student_id'] = $userdata->student_id;
553
    $def['cmi.core.student_name'] = $userdata->student_name;
554
    $def['cmi.core.credit'] = $userdata->credit;
555
    $def['cmi.core.entry'] = $userdata->entry;
556
    $def['cmi.launch_data'] = scorm_isset($userdata, 'datafromlms');
557
    $def['cmi.core.lesson_mode'] = $userdata->mode;
558
    $def['cmi.student_data.attempt_number'] = scorm_isset($userdata, 'cmi.student_data.attempt_number');
559
    $def['cmi.student_data.mastery_score'] = scorm_isset($userdata, 'mastery_score');
560
    $def['cmi.student_data.max_time_allowed'] = scorm_isset($userdata, 'max_time_allowed');
561
    $def['cmi.student_data.time_limit_action'] = scorm_isset($userdata, 'time_limit_action');
562
    $def['cmi.student_data.tries_during_lesson'] = scorm_isset($userdata, 'cmi.student_data.tries_during_lesson');
563
 
564
    $def['cmi.core.lesson_location'] = scorm_isset($userdata, 'cmi.core.lesson_location');
565
    $def['cmi.core.lesson_status'] = scorm_isset($userdata, 'cmi.core.lesson_status');
566
    $def['cmi.core.exit'] = scorm_isset($userdata, 'cmi.core.exit');
567
    $def['cmi.core.score.raw'] = scorm_isset($userdata, 'cmi.core.score.raw');
568
    $def['cmi.core.score.max'] = scorm_isset($userdata, 'cmi.core.score.max');
569
    $def['cmi.core.score.min'] = scorm_isset($userdata, 'cmi.core.score.min');
570
    $def['cmi.core.total_time'] = scorm_isset($userdata, 'cmi.core.total_time', '00:00:00');
571
    $def['cmi.suspend_data'] = scorm_isset($userdata, 'cmi.suspend_data');
572
    $def['cmi.comments'] = scorm_isset($userdata, 'cmi.comments');
573
 
574
    return $def;
575
}