2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
17 function scorm_add_time($a, $b) {
18 $aes = explode(':', $a);
19 $bes = explode(':', $b);
20 $aseconds = explode('.', $aes[2]);
21 $bseconds = explode('.', $bes[2]);
25 if (count($aseconds) > 1) {
26 $acents = $aseconds[1];
29 if (count($bseconds) > 1) {
30 $bcents = $bseconds[1];
32 $cents = $acents + $bcents;
33 $change = floor($cents / 100);
34 $cents = $cents - ($change * 100);
35 if (floor($cents) < 10) {
39 $secs = $aseconds[0] + $bseconds[0] + $change; //Seconds
40 $change = floor($secs / 60);
41 $secs = $secs - ($change * 60);
42 if (floor($secs) < 10) {
46 $mins = $aes[1] + $bes[1] + $change; //Minutes
47 $change = floor($mins / 60);
48 $mins = $mins - ($change * 60);
53 $hours = $aes[0] + $bes[0] + $change; //Hours
55 $hours = '0' . $hours;
59 return $hours . ":" . $mins . ":" . $secs . '.' . $cents;
61 return $hours . ":" . $mins . ":" . $secs;
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.
70 * @param string $row AICC header row
71 * @param string $mastername AICC sco identifier column
74 function scorm_get_aicc_columns($row, $mastername='system_id') {
75 $tok = strtok(strtolower($row), "\",\n\r");
76 $result = new stdClass();
77 $result->columns = array();
81 $result->columns[] = $tok;
82 if ($tok == $mastername) {
83 $result->mastercol = $i;
87 $tok = strtok("\",\n\r");
93 * Given a colums array return a string containing the regular
94 * expression to match the columns in a text row.
96 * @param array $column The header columns
97 * @param string $remodule The regular expression module for a single column
100 function scorm_forge_cols_regexp($columns, $remodule='(".*")?,') {
102 foreach ($columns as $column) {
103 $regexp .= $remodule;
105 $regexp = substr($regexp, 0, -1) . '/';
110 function scorm_parse_aicc($scorm) {
113 if ($scorm->scormtype == SCORM_TYPE_AICCURL) {
114 return scorm_aicc_generate_simple_sco($scorm);
116 if (!isset($scorm->cmid)) {
117 $cm = get_coursemodule_from_instance('scorm', $scorm->id);
118 $scorm->cmid = $cm->id;
120 $context = context_module::instance($scorm->cmid);
122 $fs = get_file_storage();
124 $files = $fs->get_area_files($context->id, 'mod_scorm', 'content', 0, 'sortorder, itemid, filepath, filename', false);
129 $extaiccfiles = array('crs', 'des', 'au', 'cst', 'ort', 'pre', 'cmp');
131 foreach ($files as $file) {
132 $filename = $file->get_filename();
133 $ext = substr($filename, strrpos($filename, '.'));
134 $extension = strtolower(substr($ext, 1));
135 if (in_array($extension, $extaiccfiles)) {
136 $id = strtolower(basename($filename, $ext));
137 $ids[$id]->$extension = $file;
141 foreach ($ids as $courseid => $id) {
142 if (isset($id->crs)) {
143 $contents = $id->crs->get_content();
144 $rows = explode("\r\n", $contents);
145 if (is_array($rows)) {
146 foreach ($rows as $row) {
147 if (preg_match("/^(.+)=(.+)$/", $row, $matches)) {
148 switch (strtolower(trim($matches[1]))) {
150 $courses[$courseid]->id = trim($matches[2]);
153 $courses[$courseid]->title = trim($matches[2]);
156 $courses[$courseid]->version = 'AICC_'.trim($matches[2]);
163 if (isset($id->des)) {
164 $contents = $id->des->get_content();
165 $rows = explode("\r\n", $contents);
166 $columns = scorm_get_aicc_columns($rows[0]);
167 $regexp = scorm_forge_cols_regexp($columns->columns);
168 for ($i=1; $i<count($rows); $i++) {
169 if (preg_match($regexp, $rows[$i], $matches)) {
170 for ($j=0; $j<count($columns->columns); $j++) {
171 $column = $columns->columns[$j];
172 $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]), 1 , -1)]->$column = substr(trim($matches[$j+1]), 1, -1);
177 if (isset($id->au)) {
178 $contents = $id->au->get_content();
179 $rows = explode("\r\n", $contents);
180 $columns = scorm_get_aicc_columns($rows[0]);
181 $regexp = scorm_forge_cols_regexp($columns->columns);
182 for ($i=1; $i<count($rows); $i++) {
183 if (preg_match($regexp, $rows[$i], $matches)) {
184 for ($j=0; $j<count($columns->columns); $j++) {
185 $column = $columns->columns[$j];
186 $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]), 1, -1)]->$column = substr(trim($matches[$j+1]), 1, -1);
191 if (isset($id->cst)) {
192 $contents = $id->cst->get_content();
193 $rows = explode("\r\n", $contents);
194 $columns = scorm_get_aicc_columns($rows[0], 'block');
195 $regexp = scorm_forge_cols_regexp($columns->columns, '(.+)?,');
196 for ($i=1; $i<count($rows); $i++) {
197 if (preg_match($regexp, $rows[$i], $matches)) {
198 for ($j=0; $j<count($columns->columns); $j++) {
199 if ($j != $columns->mastercol) {
200 $element = substr(trim($matches[$j+1]), 1 , -1);
201 if (!empty($element)) {
202 $courses[$courseid]->elements[$element]->parent = substr(trim($matches[$columns->mastercol+1]), 1, -1);
209 if (isset($id->ort)) {
210 $contents = $id->ort->get_content();
211 $rows = explode("\r\n", $contents);
212 $columns = scorm_get_aicc_columns($rows[0], 'course_element');
213 $regexp = scorm_forge_cols_regexp($columns->columns, '(.+)?,');
214 for ($i=1; $i<count($rows); $i++) {
215 if (preg_match($regexp, $rows[$i], $matches)) {
216 for ($j=0; $j<count($matches)-1; $j++) {
217 if ($j != $columns->mastercol) {
218 $courses[$courseid]->elements[substr(trim($matches[$j+1]), 1, -1)]->parent = substr(trim($matches[$columns->mastercol+1]), 1, -1);
224 if (isset($id->pre)) {
225 $contents = $id->pre->get_content();
226 $rows = explode("\r\n", $contents);
227 $columns = scorm_get_aicc_columns($rows[0], 'structure_element');
228 $regexp = scorm_forge_cols_regexp($columns->columns, '(.+),');
229 for ($i=1; $i<count($rows); $i++) {
230 if (preg_match($regexp, $rows[$i], $matches)) {
231 $courses[$courseid]->elements[$columns->mastercol+1]->prerequisites = substr(trim($matches[1-$columns->mastercol+1]), 1, -1);
235 if (isset($id->cmp)) {
236 $contents = $id->cmp->get_content();
237 $rows = explode("\r\n", $contents);
241 $oldscoes = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id));
244 if (isset($courses)) {
245 foreach ($courses as $course) {
246 $sco = new stdClass();
247 $sco->identifier = $course->id;
248 $sco->scorm = $scorm->id;
249 $sco->organization = '';
250 $sco->title = $course->title;
253 $sco->scormtype = '';
255 if ($ss = $DB->get_record('scorm_scoes', array('scorm'=>$scorm->id,
256 'identifier'=>$sco->identifier))) {
259 $DB->update_record('scorm_scoes',$sco);
260 unset($oldscoes[$id]);
262 $id = $DB->insert_record('scorm_scoes', $sco);
268 if (isset($course->elements)) {
269 foreach ($course->elements as $element) {
271 $sco->identifier = $element->system_id;
272 $sco->scorm = $scorm->id;
273 $sco->organization = $course->id;
274 $sco->title = $element->title;
276 if (!isset($element->parent) || strtolower($element->parent) == 'root') {
279 $sco->parent = $element->parent;
282 $sco->scormtype = '';
286 // Is it an Assignable Unit (AU)?
287 if (isset($element->file_name)) {
288 $sco->launch = $element->file_name;
289 $sco->scormtype = 'sco';
291 if ($oldscoid = scorm_array_search('identifier', $sco->identifier, $oldscoes)) {
292 $sco->id = $oldscoid;
293 $DB->update_record('scorm_scoes', $sco);
295 $DB->delete_records('scorm_scoes_data', array('scoid'=>$oldscoid));
296 unset($oldscoes[$oldscoid]);
298 $id = $DB->insert_record('scorm_scoes', $sco);
301 $scodata = new stdClass();
302 $scodata->scoid = $id;
303 if (isset($element->web_launch)) {
304 $scodata->name = 'parameters';
305 $scodata->value = $element->web_launch;
306 $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
308 if (isset($element->prerequisites)) {
309 $scodata->name = 'prerequisites';
310 $scodata->value = $element->prerequisites;
311 $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
313 if (isset($element->max_time_allowed)) {
314 $scodata->name = 'max_time_allowed';
315 $scodata->value = $element->max_time_allowed;
316 $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
318 if (isset($element->time_limit_action)) {
319 $scodata->name = 'time_limit_action';
320 $scodata->value = $element->time_limit_action;
321 $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
323 if (isset($element->mastery_score)) {
324 $scodata->name = 'mastery_score';
325 $scodata->value = $element->mastery_score;
326 $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
328 if (isset($element->core_vendor)) {
329 $scodata->name = 'datafromlms';
330 $scodata->value = preg_replace('/<cr>/i', "\r\n", $element->core_vendor);
331 $dataid = $DB->insert_record('scorm_scoes_data', $scodata);
341 if (!empty($oldscoes)) {
342 foreach ($oldscoes as $oldsco) {
343 $DB->delete_records('scorm_scoes', array('id'=>$oldsco->id));
344 $DB->delete_records('scorm_scoes_track', array('scoid'=>$oldsco->id));
348 $scorm->version = 'AICC';
350 $scorm->launch = $launch;
356 * Given a scormid creates an AICC Session record to allow HACP
358 * @param int $scormid - id from scorm table
359 * @return string hacpsession
361 function scorm_aicc_get_hacp_session($scormid) {
362 global $USER, $DB, $SESSION;
363 $cfg_scorm = get_config('scorm');
364 if (empty($cfg_scorm->allowaicchacp)) {
369 $hacpsession = $SESSION->scorm;
370 $hacpsession->scormid = $scormid;
371 $hacpsession->hacpsession = random_string(20);
372 $hacpsession->userid = $USER->id;
373 $hacpsession->timecreated = $now;
374 $hacpsession->timemodified = $now;
375 $DB->insert_record('scorm_aicc_session', $hacpsession);
377 return $hacpsession->hacpsession;
381 * Check the hacp_session for whether it is valid.
383 * @param string $hacpsession The hacpsession value to check (optional). Normally leave this blank
384 * and this function will do required_param('sesskey', ...).
385 * @return mixed - false if invalid, otherwise returns record from scorm_aicc_session table.
387 function scorm_aicc_confirm_hacp_session($hacpsession) {
389 $cfg_scorm = get_config('scorm');
390 if (empty($cfg_scorm->allowaicchacp)) {
393 $time = time()-($cfg_scorm->aicchacptimeout * 60);
394 $sql = "hacpsession = ? AND timemodified > ?";
395 $hacpsession = $DB->get_record_select('scorm_aicc_session', $sql, array($hacpsession, $time));
396 if (!empty($hacpsession)) { //update timemodified as this is still an active session - resets the timeout.
397 $hacpsession->timemodified = time();
398 $DB->update_record('scorm_aicc_session', $hacpsession);
404 * generate a simple single activity AICC object
405 * structure to wrap around and externally linked
408 * @param object $scorm package record
410 function scorm_aicc_generate_simple_sco($scorm) {
413 $scos = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id));
415 $sco = array_shift($scos);
419 // get rid of old ones
420 foreach($scos as $oldsco) {
421 $DB->delete_records('scorm_scoes', array('id'=>$oldsco->id));
422 $DB->delete_records('scorm_scoes_track', array('scoid'=>$oldsco->id));
425 $sco->identifier = 'A1';
426 $sco->scorm = $scorm->id;
427 $sco->organization = '';
428 $sco->title = $scorm->name;
430 // add the HACP signal to the activity launcher
431 if (preg_match('/\?/', $scorm->reference)) {
432 $sco->launch = $scorm->reference.'&CMI=HACP';
435 $sco->launch = $scorm->reference.'?CMI=HACP';
437 $sco->scormtype = 'sco';
438 if (isset($sco->id)) {
439 $DB->update_record('scorm_scoes', $sco);
442 $id = $DB->insert_record('scorm_scoes', $sco);