Merge branch 'wip-MDL-41106-m26' of git://github.com/samhemelryk/moodle
[moodle.git] / mod / scorm / datamodels / aicclib.php
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/>.
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]);
22     $change = 0;
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     }
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     }
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     }
53     $hours = $aes[0] + $bes[0] + $change;  //Hours
54     if ($hours < 10) {
55         $hours = '0' . $hours;
56     }
58     if ($cents != '0') {
59         return $hours . ":" . $mins . ":" . $secs . '.' . $cents;
60     } else {
61         return $hours . ":" . $mins . ":" . $secs;
62     }
63 }
65 /**
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  */
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();
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 }
92 /**
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  */
100 function scorm_forge_cols_regexp($columns, $remodule='(".*")?,') {
101     $regexp = '/^';
102     foreach ($columns as $column) {
103         $regexp .= $remodule;
104     }
105     $regexp = substr($regexp, 0, -1) . '/';
106     return $regexp;
110 function scorm_parse_aicc($scorm) {
111     global $DB;
113     if ($scorm->scormtype == SCORM_TYPE_AICCURL) {
114         return scorm_aicc_generate_simple_sco($scorm);
115     }
116     if (!isset($scorm->cmid)) {
117         $cm = get_coursemodule_from_instance('scorm', $scorm->id);
118         $scorm->cmid = $cm->id;
119     }
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);
126     $version = 'AICC';
127     $ids = array();
128     $courses = array();
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             if (!isset($ids[$id])) {
138                 $ids[$id] = new stdClass();
139             }
140             $ids[$id]->$extension = $file;
141         }
142     }
144     foreach ($ids as $courseid => $id) {
145         if (!isset($courses[$courseid])) {
146             $courses[$courseid] = new stdClass();
147         }
148         if (isset($id->crs)) {
149             $contents = $id->crs->get_content();
150             $rows = explode("\r\n", $contents);
151             if (is_array($rows)) {
152                 foreach ($rows as $row) {
153                     if (preg_match("/^(.+)=(.+)$/", $row, $matches)) {
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                         }
165                     }
166                 }
167             }
168         }
169         if (isset($id->des)) {
170             $contents = $id->des->get_content();
171             $rows = explode("\r\n", $contents);
172             $columns = scorm_get_aicc_columns($rows[0]);
173             $regexp = scorm_forge_cols_regexp($columns->columns);
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++) {
177                         $column = $columns->columns[$j];
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                         }
181                         $courses[$courseid]->elements[substr(trim($matches[$columns->mastercol+1]), 1 , -1)]->$column = substr(trim($matches[$j+1]), 1, -1);
182                     }
183                 }
184             }
185         }
186         if (isset($id->au)) {
187             $contents = $id->au->get_content();
188             $rows = explode("\r\n", $contents);
189             $columns = scorm_get_aicc_columns($rows[0]);
190             $regexp = scorm_forge_cols_regexp($columns->columns);
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++) {
194                         $column = $columns->columns[$j];
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->cst)) {
201             $contents = $id->cst->get_content();
202             $rows = explode("\r\n", $contents);
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++) {
208                         if ($j != $columns->mastercol) {
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                             }
213                         }
214                     }
215                 }
216             }
217         }
218         if (isset($id->ort)) {
219             $contents = $id->ort->get_content();
220             $rows = explode("\r\n", $contents);
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++) {
226                         if ($j != $columns->mastercol) {
227                             $courses[$courseid]->elements[substr(trim($matches[$j+1]), 1, -1)]->parent = substr(trim($matches[$columns->mastercol+1]), 1, -1);
228                         }
229                     }
230                 }
231             }
232         }
233         if (isset($id->pre)) {
234             $contents = $id->pre->get_content();
235             $rows = explode("\r\n", $contents);
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);
241                 }
242             }
243         }
244         if (isset($id->cmp)) {
245             $contents = $id->cmp->get_content();
246             $rows = explode("\r\n", $contents);
247         }
248     }
250     $oldscoes = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id));
252     $launch = 0;
253     if (isset($courses)) {
254         foreach ($courses as $course) {
255             $sco = new stdClass();
256             $sco->identifier = $course->id;
257             $sco->scorm = $scorm->id;
258             $sco->organization = '';
259             $sco->title = $course->title;
260             $sco->parent = '/';
261             $sco->launch = '';
262             $sco->scormtype = '';
264             if ($ss = $DB->get_record('scorm_scoes', array('scorm'=>$scorm->id,
265                                                            'identifier'=>$sco->identifier))) {
266                 $id = $ss->id;
267                 $sco->id = $id;
268                 $DB->update_record('scorm_scoes',$sco);
269                 unset($oldscoes[$id]);
270             } else {
271                 $id = $DB->insert_record('scorm_scoes', $sco);
272             }
274             if ($launch == 0) {
275                 $launch = $id;
276             }
277             if (isset($course->elements)) {
278                 foreach ($course->elements as $element) {
279                     unset($sco);
280                     $sco = new stdClass();
281                     $sco->identifier = $element->system_id;
282                     $sco->scorm = $scorm->id;
283                     $sco->organization = $course->id;
284                     $sco->title = $element->title;
286                     if (!isset($element->parent)) {
287                         $sco->parent = '/';
288                     } else if (strtolower($element->parent) == 'root') {
289                         $sco->parent = $course->id;
290                     } else {
291                         $sco->parent = $element->parent;
292                     }
293                     $sco->launch = '';
294                     $sco->scormtype = '';
295                     $sco->previous = 0;
296                     $sco->next = 0;
297                     $id = null;
298                     // Is it an Assignable Unit (AU)?
299                     if (isset($element->file_name)) {
300                         $sco->launch = $element->file_name;
301                         $sco->scormtype = 'sco';
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);
319                         }
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);
329                         }
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);
334                         }
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;
348                     }
349                 }
350             }
351         }
352     }
353     if (!empty($oldscoes)) {
354         foreach ($oldscoes as $oldsco) {
355             $DB->delete_records('scorm_scoes', array('id'=>$oldsco->id));
356             $DB->delete_records('scorm_scoes_track', array('scoid'=>$oldsco->id));
357         }
358     }
360     $scorm->version = 'AICC';
362     $scorm->launch = $launch;
364     return true;
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  */
373 function 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();
381     $hacpsession = $SESSION->scorm;
382     $hacpsession->scormid = $scormid;
383     $hacpsession->hacpsession = random_string(20);
384     $hacpsession->userid      = $USER->id;
385     $hacpsession->timecreated = $now;
386     $hacpsession->timemodified = $now;
387     $DB->insert_record('scorm_aicc_session', $hacpsession);
389     return $hacpsession->hacpsession;
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  */
399 function 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;
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  */
422 function 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     }
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;