MDL-37393 SCORM: Fixed PHP Strict Standards errors with AICC packages
[moodle.git] / mod / scorm / datamodels / aicclib.php
CommitLineData
e5dd8e3b 1<?php
f7b5c6aa
DM
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/>.
e23cc5d2 16
dc383b6f 17function scorm_add_time($a, $b) {
f7b5c6aa
DM
18 $aes = explode(':', $a);
19 $bes = explode(':', $b);
20 $aseconds = explode('.', $aes[2]);
21 $bseconds = explode('.', $bes[2]);
dc383b6f 22 $change = 0;
23
24 $acents = 0; //Cents
25 if (count($aseconds) > 1) {
26 $acents = $aseconds[1];
27 }
28 $bcents = 0;
29 if (count($bseconds) > 1) {
30 $bcents = $bseconds[1];
31 }
32 $cents = $acents + $bcents;
33 $change = floor($cents / 100);
34 $cents = $cents - ($change * 100);
35 if (floor($cents) < 10) {
36 $cents = '0'. $cents;
37 }
38
39 $secs = $aseconds[0] + $bseconds[0] + $change; //Seconds
40 $change = floor($secs / 60);
41 $secs = $secs - ($change * 60);
42 if (floor($secs) < 10) {
43 $secs = '0'. $secs;
44 }
45
46 $mins = $aes[1] + $bes[1] + $change; //Minutes
47 $change = floor($mins / 60);
48 $mins = $mins - ($change * 60);
49 if ($mins < 10) {
50 $mins = '0' . $mins;
51 }
52
53 $hours = $aes[0] + $bes[0] + $change; //Hours
54 if ($hours < 10) {
55 $hours = '0' . $hours;
56 }
57
58 if ($cents != '0') {
59 return $hours . ":" . $mins . ":" . $secs . '.' . $cents;
60 } else {
61 return $hours . ":" . $mins . ":" . $secs;
62 }
63}
64
65/**
f7b5c6aa
DM
66 * Take the header row of an AICC definition file
67 * and returns sequence of columns and a pointer to
68 * the sco identifier column.
69 *
70 * @param string $row AICC header row
71 * @param string $mastername AICC sco identifier column
72 * @return mixed
73 */
74function scorm_get_aicc_columns($row, $mastername='system_id') {
75 $tok = strtok(strtolower($row), "\",\n\r");
39790bd8 76 $result = new stdClass();
dc383b6f 77 $result->columns = array();
78 $i=0;
79 while ($tok) {
80 if ($tok !='') {
81 $result->columns[] = $tok;
82 if ($tok == $mastername) {
83 $result->mastercol = $i;
84 }
85 $i++;
86 }
87 $tok = strtok("\",\n\r");
88 }
89 return $result;
90}
91
92/**
f7b5c6aa
DM
93 * Given a colums array return a string containing the regular
94 * expression to match the columns in a text row.
95 *
96 * @param array $column The header columns
97 * @param string $remodule The regular expression module for a single column
98 * @return string
99 */
100function scorm_forge_cols_regexp($columns, $remodule='(".*")?,') {
dc383b6f 101 $regexp = '/^';
102 foreach ($columns as $column) {
103 $regexp .= $remodule;
104 }
f7b5c6aa 105 $regexp = substr($regexp, 0, -1) . '/';
dc383b6f 106 return $regexp;
107}
108
9528568b 109
110function scorm_parse_aicc($scorm) {
bf347041 111 global $DB;
112
4388bd45
DM
113 if ($scorm->scormtype == SCORM_TYPE_AICCURL) {
114 return scorm_aicc_generate_simple_sco($scorm);
115 }
9528568b 116 if (!isset($scorm->cmid)) {
117 $cm = get_coursemodule_from_instance('scorm', $scorm->id);
118 $scorm->cmid = $cm->id;
119 }
a3fc4b3a 120 $context = context_module::instance($scorm->cmid);
9528568b 121
122 $fs = get_file_storage();
123
849b9a6a 124 $files = $fs->get_area_files($context->id, 'mod_scorm', 'content', 0, 'sortorder, itemid, filepath, filename', false);
9528568b 125
dc383b6f 126 $version = 'AICC';
127 $ids = array();
128 $courses = array();
f7b5c6aa 129 $extaiccfiles = array('crs', 'des', 'au', 'cst', 'ort', 'pre', 'cmp');
9528568b 130
131 foreach ($files as $file) {
132 $filename = $file->get_filename();
f7b5c6aa
DM
133 $ext = substr($filename, strrpos($filename, '.'));
134 $extension = strtolower(substr($ext, 1));
135 if (in_array($extension, $extaiccfiles)) {
136 $id = strtolower(basename($filename, $ext));
9d507f1d
MS
137 if (!isset($ids[$id])) {
138 $ids[$id] = new stdClass();
139 }
9528568b 140 $ids[$id]->$extension = $file;
dc383b6f 141 }
dc383b6f 142 }
9528568b 143
dc383b6f 144 foreach ($ids as $courseid => $id) {
9d507f1d
MS
145 if (!isset($courses[$courseid])) {
146 $courses[$courseid] = new stdClass();
147 }
dc383b6f 148 if (isset($id->crs)) {
e23cc5d2 149 $contents = $id->crs->get_content();
150 $rows = explode("\r\n", $contents);
151 if (is_array($rows)) {
152 foreach ($rows as $row) {
f7b5c6aa 153 if (preg_match("/^(.+)=(.+)$/", $row, $matches)) {
e23cc5d2 154 switch (strtolower(trim($matches[1]))) {
155 case 'course_id':
156 $courses[$courseid]->id = trim($matches[2]);
157 break;
158 case 'course_title':
159 $courses[$courseid]->title = trim($matches[2]);
160 break;
161 case 'version':
162 $courses[$courseid]->version = 'AICC_'.trim($matches[2]);
163 break;
164 }
dc383b6f 165 }
166 }
167 }
168 }
169 if (isset($id->des)) {
e23cc5d2 170 $contents = $id->des->get_content();
171 $rows = explode("\r\n", $contents);
dc383b6f 172 $columns = scorm_get_aicc_columns($rows[0]);
173 $regexp = scorm_forge_cols_regexp($columns->columns);
f7b5c6aa
DM
174 for ($i=1; $i<count($rows); $i++) {
175 if (preg_match($regexp, $rows[$i], $matches)) {
176 for ($j=0; $j<count($columns->columns); $j++) {
dc383b6f 177 $column = $columns->columns[$j];
9d507f1d
MS
178 if (!isset($courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]), 1 , -1)])) {
179 $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]), 1 , -1)] = new stdClass();
180 }
f7b5c6aa 181 $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]), 1 , -1)]->$column = substr(trim($matches[$j+1]), 1, -1);
dc383b6f 182 }
183 }
184 }
185 }
186 if (isset($id->au)) {
e23cc5d2 187 $contents = $id->au->get_content();
188 $rows = explode("\r\n", $contents);
dc383b6f 189 $columns = scorm_get_aicc_columns($rows[0]);
190 $regexp = scorm_forge_cols_regexp($columns->columns);
f7b5c6aa
DM
191 for ($i=1; $i<count($rows); $i++) {
192 if (preg_match($regexp, $rows[$i], $matches)) {
193 for ($j=0; $j<count($columns->columns); $j++) {
dc383b6f 194 $column = $columns->columns[$j];
f7b5c6aa 195 $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]), 1, -1)]->$column = substr(trim($matches[$j+1]), 1, -1);
dc383b6f 196 }
197 }
198 }
199 }
200 if (isset($id->cst)) {
e23cc5d2 201 $contents = $id->cst->get_content();
202 $rows = explode("\r\n", $contents);
f7b5c6aa
DM
203 $columns = scorm_get_aicc_columns($rows[0], 'block');
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++) {
dc383b6f 208 if ($j != $columns->mastercol) {
8ac8aae4
MS
209 $element = substr(trim($matches[$j+1]), 1 , -1);
210 if (!empty($element)) {
211 $courses[$courseid]->elements[$element]->parent = substr(trim($matches[$columns->mastercol+1]), 1, -1);
212 }
dc383b6f 213 }
214 }
215 }
216 }
217 }
218 if (isset($id->ort)) {
e23cc5d2 219 $contents = $id->ort->get_content();
220 $rows = explode("\r\n", $contents);
f7b5c6aa
DM
221 $columns = scorm_get_aicc_columns($rows[0], 'course_element');
222 $regexp = scorm_forge_cols_regexp($columns->columns, '(.+)?,');
223 for ($i=1; $i<count($rows); $i++) {
224 if (preg_match($regexp, $rows[$i], $matches)) {
225 for ($j=0; $j<count($matches)-1; $j++) {
f6520280 226 if ($j != $columns->mastercol) {
f7b5c6aa 227 $courses[$courseid]->elements[substr(trim($matches[$j+1]), 1, -1)]->parent = substr(trim($matches[$columns->mastercol+1]), 1, -1);
f6520280 228 }
229 }
230 }
231 }
dc383b6f 232 }
233 if (isset($id->pre)) {
e23cc5d2 234 $contents = $id->pre->get_content();
235 $rows = explode("\r\n", $contents);
f7b5c6aa
DM
236 $columns = scorm_get_aicc_columns($rows[0], 'structure_element');
237 $regexp = scorm_forge_cols_regexp($columns->columns, '(.+),');
238 for ($i=1; $i<count($rows); $i++) {
239 if (preg_match($regexp, $rows[$i], $matches)) {
240 $courses[$courseid]->elements[$columns->mastercol+1]->prerequisites = substr(trim($matches[1-$columns->mastercol+1]), 1, -1);
dc383b6f 241 }
242 }
243 }
244 if (isset($id->cmp)) {
e23cc5d2 245 $contents = $id->cmp->get_content();
246 $rows = explode("\r\n", $contents);
dc383b6f 247 }
248 }
dc383b6f 249
9528568b 250 $oldscoes = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id));
d9e6517a 251
dc383b6f 252 $launch = 0;
253 if (isset($courses)) {
254 foreach ($courses as $course) {
39790bd8 255 $sco = new stdClass();
dc383b6f 256 $sco->identifier = $course->id;
9528568b 257 $sco->scorm = $scorm->id;
dc383b6f 258 $sco->organization = '';
259 $sco->title = $course->title;
260 $sco->parent = '/';
261 $sco->launch = '';
262 $sco->scormtype = '';
263
f7b5c6aa
DM
264 if ($ss = $DB->get_record('scorm_scoes', array('scorm'=>$scorm->id,
265 'identifier'=>$sco->identifier))) {
bf347041 266 $id = $ss->id;
d3bf0a95
DM
267 $sco->id = $id;
268 $DB->update_record('scorm_scoes',$sco);
dc383b6f 269 unset($oldscoes[$id]);
270 } else {
f7b5c6aa 271 $id = $DB->insert_record('scorm_scoes', $sco);
dc383b6f 272 }
273
274 if ($launch == 0) {
275 $launch = $id;
276 }
277 if (isset($course->elements)) {
f7b5c6aa 278 foreach ($course->elements as $element) {
dc383b6f 279 unset($sco);
9d507f1d 280 $sco = new stdClass();
dc383b6f 281 $sco->identifier = $element->system_id;
9528568b 282 $sco->scorm = $scorm->id;
dc383b6f 283 $sco->organization = $course->id;
284 $sco->title = $element->title;
d9e6517a 285
d4978e04 286 if (!isset($element->parent)) {
dc383b6f 287 $sco->parent = '/';
d4978e04
DM
288 } else if (strtolower($element->parent) == 'root') {
289 $sco->parent = $course->id;
dc383b6f 290 } else {
291 $sco->parent = $element->parent;
292 }
8ac8aae4
MS
293 $sco->launch = '';
294 $sco->scormtype = '';
295 $sco->previous = 0;
296 $sco->next = 0;
297 $id = null;
298 // Is it an Assignable Unit (AU)?
dc383b6f 299 if (isset($element->file_name)) {
300 $sco->launch = $element->file_name;
301 $sco->scormtype = 'sco';
8ac8aae4
MS
302 }
303 if ($oldscoid = scorm_array_search('identifier', $sco->identifier, $oldscoes)) {
304 $sco->id = $oldscoid;
305 $DB->update_record('scorm_scoes', $sco);
306 $id = $oldscoid;
307 $DB->delete_records('scorm_scoes_data', array('scoid'=>$oldscoid));
308 unset($oldscoes[$oldscoid]);
309 } else {
310 $id = $DB->insert_record('scorm_scoes', $sco);
311 }
312 if (!empty($id)) {
313 $scodata = new stdClass();
314 $scodata->scoid = $id;
315 if (isset($element->web_launch)) {
316 $scodata->name = 'parameters';
317 $scodata->value = $element->web_launch;
318 $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
f6520280 319 }
8ac8aae4
MS
320 if (isset($element->prerequisites)) {
321 $scodata->name = 'prerequisites';
322 $scodata->value = $element->prerequisites;
323 $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
324 }
325 if (isset($element->max_time_allowed)) {
326 $scodata->name = 'max_time_allowed';
327 $scodata->value = $element->max_time_allowed;
328 $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
f6520280 329 }
8ac8aae4
MS
330 if (isset($element->time_limit_action)) {
331 $scodata->name = 'time_limit_action';
332 $scodata->value = $element->time_limit_action;
333 $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
f6520280 334 }
8ac8aae4
MS
335 if (isset($element->mastery_score)) {
336 $scodata->name = 'mastery_score';
337 $scodata->value = $element->mastery_score;
338 $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
339 }
340 if (isset($element->core_vendor)) {
341 $scodata->name = 'datafromlms';
342 $scodata->value = preg_replace('/<cr>/i', "\r\n", $element->core_vendor);
343 $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
344 }
345 }
346 if ($launch==0) {
347 $launch = $id;
dc383b6f 348 }
349 }
350 }
351 }
352 }
353 if (!empty($oldscoes)) {
f7b5c6aa 354 foreach ($oldscoes as $oldsco) {
bf347041 355 $DB->delete_records('scorm_scoes', array('id'=>$oldsco->id));
356 $DB->delete_records('scorm_scoes_track', array('scoid'=>$oldsco->id));
dc383b6f 357 }
358 }
9528568b 359
360 $scorm->version = 'AICC';
361
362 $scorm->launch = $launch;
363
364 return true;
dc383b6f 365}
ba0e91a2
DM
366
367/**
368 * Given a scormid creates an AICC Session record to allow HACP
369 *
370 * @param int $scormid - id from scorm table
371 * @return string hacpsession
372 */
373function scorm_aicc_get_hacp_session($scormid) {
374 global $USER, $DB, $SESSION;
375 $cfg_scorm = get_config('scorm');
376 if (empty($cfg_scorm->allowaicchacp)) {
377 return false;
378 }
379 $now = time();
380
381 $hacpsession = $SESSION->scorm;
382 $hacpsession->scormid = $scormid;
3cfffd8c 383 $hacpsession->hacpsession = random_string(20);
ba0e91a2
DM
384 $hacpsession->userid = $USER->id;
385 $hacpsession->timecreated = $now;
386 $hacpsession->timemodified = $now;
387 $DB->insert_record('scorm_aicc_session', $hacpsession);
388
389 return $hacpsession->hacpsession;
390}
391
392/**
393 * Check the hacp_session for whether it is valid.
394 *
395 * @param string $hacpsession The hacpsession value to check (optional). Normally leave this blank
396 * and this function will do required_param('sesskey', ...).
397 * @return mixed - false if invalid, otherwise returns record from scorm_aicc_session table.
398 */
399function scorm_aicc_confirm_hacp_session($hacpsession) {
400 global $DB;
401 $cfg_scorm = get_config('scorm');
402 if (empty($cfg_scorm->allowaicchacp)) {
403 return false;
404 }
405 $time = time()-($cfg_scorm->aicchacptimeout * 60);
406 $sql = "hacpsession = ? AND timemodified > ?";
407 $hacpsession = $DB->get_record_select('scorm_aicc_session', $sql, array($hacpsession, $time));
408 if (!empty($hacpsession)) { //update timemodified as this is still an active session - resets the timeout.
409 $hacpsession->timemodified = time();
410 $DB->update_record('scorm_aicc_session', $hacpsession);
411 }
412 return $hacpsession;
4388bd45
DM
413}
414
415/**
416 * generate a simple single activity AICC object
417 * structure to wrap around and externally linked
418 * AICC package URL
419 *
420 * @param object $scorm package record
421 */
422function scorm_aicc_generate_simple_sco($scorm) {
423 global $DB;
424 // find the old one
425 $scos = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id));
426 if (!empty($scos)) {
427 $sco = array_shift($scos);
428 } else {
429 $sco = new object();
430 }
431 // get rid of old ones
432 foreach($scos as $oldsco) {
433 $DB->delete_records('scorm_scoes', array('id'=>$oldsco->id));
434 $DB->delete_records('scorm_scoes_track', array('scoid'=>$oldsco->id));
435 }
436
437 $sco->identifier = 'A1';
438 $sco->scorm = $scorm->id;
439 $sco->organization = '';
440 $sco->title = $scorm->name;
441 $sco->parent = '/';
442 // add the HACP signal to the activity launcher
443 if (preg_match('/\?/', $scorm->reference)) {
444 $sco->launch = $scorm->reference.'&CMI=HACP';
445 }
446 else {
447 $sco->launch = $scorm->reference.'?CMI=HACP';
448 }
449 $sco->scormtype = 'sco';
450 if (isset($sco->id)) {
451 $DB->update_record('scorm_scoes', $sco);
452 $id = $sco->id;
453 } else {
454 $id = $DB->insert_record('scorm_scoes', $sco);
455 }
456 return $id;
ba0e91a2 457}