MDL-44753 SCORM: make correct selection of first sco - also fixes MDL-44896
[moodle.git] / mod / scorm / datamodels / scormlib.php
CommitLineData
dc383b6f 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/>.
16
02bff1ad
DM
17/**
18 * functions used by SCORM 1.2/2004 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
dc383b6f 25function scorm_get_resources($blocks) {
26 $resources = array();
27 foreach ($blocks as $block) {
03c34889 28 if ($block['name'] == 'RESOURCES' && isset($block['children'])) {
dc383b6f 29 foreach ($block['children'] as $resource) {
30 if ($resource['name'] == 'RESOURCE') {
294ce987 31 $resources[addslashes_js($resource['attrs']['IDENTIFIER'])] = $resource['attrs'];
dc383b6f 32 }
33 }
34 }
35 }
36 return $resources;
37}
38
f7b5c6aa 39function scorm_get_manifest($blocks, $scoes) {
03c34889 40 global $OUTPUT;
dc383b6f 41 static $parents = array();
42 static $resources;
43
44 static $manifest;
45 static $organization;
46
03c34889 47 $manifestresourcesnotfound = array();
dc383b6f 48 if (count($blocks) > 0) {
49 foreach ($blocks as $block) {
50 switch ($block['name']) {
51 case 'METADATA':
52 if (isset($block['children'])) {
53 foreach ($block['children'] as $metadata) {
54 if ($metadata['name'] == 'SCHEMAVERSION') {
55 if (empty($scoes->version)) {
f7b5c6aa 56 if (isset($metadata['tagData']) && (preg_match("/^(1\.2)$|^(CAM )?(1\.3)$/", $metadata['tagData'], $matches))) {
dc383b6f 57 $scoes->version = 'SCORM_'.$matches[count($matches)-1];
58 } else {
f7b5c6aa 59 if (isset($metadata['tagData']) && (preg_match("/^2004 (3rd|4th) Edition$/", $metadata['tagData'], $matches))) {
d11b69ff 60 $scoes->version = 'SCORM_1.3';
61 } else {
62 $scoes->version = 'SCORM_1.2';
63 }
dc383b6f 64 }
65 }
66 }
67 }
68 }
69 break;
70 case 'MANIFEST':
294ce987 71 $manifest = $block['attrs']['IDENTIFIER'];
dc383b6f 72 $organization = '';
73 $resources = array();
74 $resources = scorm_get_resources($block['children']);
f7b5c6aa 75 $scoes = scorm_get_manifest($block['children'], $scoes);
36149cf0 76 if (empty($scoes->elements) || count($scoes->elements) <= 0) {
dc383b6f 77 foreach ($resources as $item => $resource) {
78 if (!empty($resource['HREF'])) {
79 $sco = new stdClass();
80 $sco->identifier = $item;
81 $sco->title = $item;
82 $sco->parent = '/';
294ce987 83 $sco->launch = $resource['HREF'];
84 $sco->scormtype = $resource['ADLCP:SCORMTYPE'];
dc383b6f 85 $scoes->elements[$manifest][$organization][$item] = $sco;
86 }
87 }
88 }
89 break;
90 case 'ORGANIZATIONS':
ef7f7453 91 if (!isset($scoes->defaultorg) && isset($block['attrs']['DEFAULT'])) {
294ce987 92 $scoes->defaultorg = $block['attrs']['DEFAULT'];
dc383b6f 93 }
36149cf0 94 if (!empty($block['children'])) {
f7b5c6aa 95 $scoes = scorm_get_manifest($block['children'], $scoes);
36149cf0 96 }
dc383b6f 97 break;
98 case 'ORGANIZATION':
294ce987 99 $identifier = $block['attrs']['IDENTIFIER'];
dc383b6f 100 $organization = '';
37ca1722 101 $scoes->elements[$manifest][$organization][$identifier] = new stdClass();
dc383b6f 102 $scoes->elements[$manifest][$organization][$identifier]->identifier = $identifier;
103 $scoes->elements[$manifest][$organization][$identifier]->parent = '/';
104 $scoes->elements[$manifest][$organization][$identifier]->launch = '';
105 $scoes->elements[$manifest][$organization][$identifier]->scormtype = '';
106
107 $parents = array();
108 $parent = new stdClass();
109 $parent->identifier = $identifier;
110 $parent->organization = $organization;
111 array_push($parents, $parent);
112 $organization = $identifier;
113
36149cf0 114 if (!empty($block['children'])) {
f7b5c6aa 115 $scoes = scorm_get_manifest($block['children'], $scoes);
36149cf0 116 }
309ee089 117
dc383b6f 118 array_pop($parents);
119 break;
120 case 'ITEM':
121 $parent = array_pop($parents);
122 array_push($parents, $parent);
123
294ce987 124 $identifier = $block['attrs']['IDENTIFIER'];
37ca1722 125 $scoes->elements[$manifest][$organization][$identifier] = new stdClass();
dc383b6f 126 $scoes->elements[$manifest][$organization][$identifier]->identifier = $identifier;
127 $scoes->elements[$manifest][$organization][$identifier]->parent = $parent->identifier;
128 if (!isset($block['attrs']['ISVISIBLE'])) {
129 $block['attrs']['ISVISIBLE'] = 'true';
130 }
294ce987 131 $scoes->elements[$manifest][$organization][$identifier]->isvisible = $block['attrs']['ISVISIBLE'];
dc383b6f 132 if (!isset($block['attrs']['PARAMETERS'])) {
133 $block['attrs']['PARAMETERS'] = '';
134 }
294ce987 135 $scoes->elements[$manifest][$organization][$identifier]->parameters = $block['attrs']['PARAMETERS'];
dc383b6f 136 if (!isset($block['attrs']['IDENTIFIERREF'])) {
137 $scoes->elements[$manifest][$organization][$identifier]->launch = '';
138 $scoes->elements[$manifest][$organization][$identifier]->scormtype = 'asset';
139 } else {
294ce987 140 $idref = $block['attrs']['IDENTIFIERREF'];
dc383b6f 141 $base = '';
142 if (isset($resources[$idref]['XML:BASE'])) {
143 $base = $resources[$idref]['XML:BASE'];
144 }
03c34889
DM
145 if (!isset($resources[$idref])) {
146 $manifestresourcesnotfound[] = $idref;
147 $scoes->elements[$manifest][$organization][$identifier]->launch = '';
148 } else {
149 $scoes->elements[$manifest][$organization][$identifier]->launch = $base.$resources[$idref]['HREF'];
150 if (empty($resources[$idref]['ADLCP:SCORMTYPE'])) {
151 $resources[$idref]['ADLCP:SCORMTYPE'] = 'asset';
152 }
153 $scoes->elements[$manifest][$organization][$identifier]->scormtype = $resources[$idref]['ADLCP:SCORMTYPE'];
dc383b6f 154 }
dc383b6f 155 }
156
157 $parent = new stdClass();
158 $parent->identifier = $identifier;
159 $parent->organization = $organization;
160 array_push($parents, $parent);
161
36149cf0 162 if (!empty($block['children'])) {
f7b5c6aa 163 $scoes = scorm_get_manifest($block['children'], $scoes);
36149cf0 164 }
309ee089 165
dc383b6f 166 array_pop($parents);
167 break;
168 case 'TITLE':
169 $parent = array_pop($parents);
170 array_push($parents, $parent);
171 if (!isset($block['tagData'])) {
172 $block['tagData'] = '';
173 }
294ce987 174 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->title = $block['tagData'];
dc383b6f 175 break;
176 case 'ADLCP:PREREQUISITES':
177 if ($block['attrs']['TYPE'] == 'aicc_script') {
178 $parent = array_pop($parents);
179 array_push($parents, $parent);
180 if (!isset($block['tagData'])) {
181 $block['tagData'] = '';
182 }
294ce987 183 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->prerequisites = $block['tagData'];
dc383b6f 184 }
185 break;
186 case 'ADLCP:MAXTIMEALLOWED':
187 $parent = array_pop($parents);
188 array_push($parents, $parent);
189 if (!isset($block['tagData'])) {
190 $block['tagData'] = '';
191 }
294ce987 192 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->maxtimeallowed = $block['tagData'];
dc383b6f 193 break;
194 case 'ADLCP:TIMELIMITACTION':
195 $parent = array_pop($parents);
196 array_push($parents, $parent);
197 if (!isset($block['tagData'])) {
198 $block['tagData'] = '';
199 }
294ce987 200 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->timelimitaction = $block['tagData'];
dc383b6f 201 break;
202 case 'ADLCP:DATAFROMLMS':
203 $parent = array_pop($parents);
204 array_push($parents, $parent);
205 if (!isset($block['tagData'])) {
206 $block['tagData'] = '';
207 }
294ce987 208 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->datafromlms = $block['tagData'];
dc383b6f 209 break;
210 case 'ADLCP:MASTERYSCORE':
211 $parent = array_pop($parents);
212 array_push($parents, $parent);
213 if (!isset($block['tagData'])) {
214 $block['tagData'] = '';
215 }
294ce987 216 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->masteryscore = $block['tagData'];
dc383b6f 217 break;
b3659259 218 case 'ADLCP:COMPLETIONTHRESHOLD':
219 $parent = array_pop($parents);
220 array_push($parents, $parent);
b5a26fe1
MG
221 if (!isset($block['attrs']['MINPROGRESSMEASURE'])) {
222 $block['attrs']['MINPROGRESSMEASURE'] = '1.0';
b3659259 223 }
b5a26fe1 224 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->threshold = $block['attrs']['MINPROGRESSMEASURE'];
b3659259 225 break;
226 case 'ADLNAV:PRESENTATION':
227 $parent = array_pop($parents);
228 array_push($parents, $parent);
33b6a8f4 229 if (!empty($block['children'])) {
230 foreach ($block['children'] as $adlnav) {
231 if ($adlnav['name'] == 'ADLNAV:NAVIGATIONINTERFACE') {
232 foreach ($adlnav['children'] as $adlnavInterface) {
233 if ($adlnavInterface['name'] == 'ADLNAV:HIDELMSUI') {
234 if ($adlnavInterface['tagData'] == 'continue') {
309ee089 235 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hidecontinue = 1;
33b6a8f4 236 }
237 if ($adlnavInterface['tagData'] == 'previous') {
309ee089 238 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideprevious = 1;
33b6a8f4 239 }
240 if ($adlnavInterface['tagData'] == 'exit') {
309ee089 241 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideexit = 1;
33b6a8f4 242 }
243 if ($adlnavInterface['tagData'] == 'exitAll') {
309ee089 244 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideexitall = 1;
33b6a8f4 245 }
246 if ($adlnavInterface['tagData'] == 'abandon') {
309ee089 247 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideabandon = 1;
33b6a8f4 248 }
249 if ($adlnavInterface['tagData'] == 'abandonAll') {
309ee089 250 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideabandonall = 1;
33b6a8f4 251 }
252 if ($adlnavInterface['tagData'] == 'suspendAll') {
309ee089 253 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hidesuspendall = 1;
33b6a8f4 254 }
b3659259 255 }
33b6a8f4 256 }
257 }
258 }
259 }
260 break;
261 case 'IMSSS:SEQUENCING':
262 $parent = array_pop($parents);
263 array_push($parents, $parent);
264 if (!empty($block['children'])) {
265 foreach ($block['children'] as $sequencing) {
266 if ($sequencing['name']=='IMSSS:CONTROLMODE') {
267 if (isset($sequencing['attrs']['CHOICE'])) {
268 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->choice = $sequencing['attrs']['CHOICE'] == 'true'?1:0;
269 }
270 if (isset($sequencing['attrs']['CHOICEEXIT'])) {
271 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->choiceexit = $sequencing['attrs']['CHOICEEXIT'] == 'true'?1:0;
272 }
273 if (isset($sequencing['attrs']['FLOW'])) {
274 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->flow = $sequencing['attrs']['FLOW'] == 'true'?1:0;
275 }
276 if (isset($sequencing['attrs']['FORWARDONLY'])) {
277 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->forwardonly = $sequencing['attrs']['FORWARDONLY'] == 'true'?1:0;
278 }
279 if (isset($sequencing['attrs']['USECURRENTATTEMPTOBJECTINFO'])) {
280 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->usecurrentattemptobjectinfo = $sequencing['attrs']['USECURRENTATTEMPTOBJECTINFO'] == 'true'?1:0;
281 }
282 if (isset($sequencing['attrs']['USECURRENTATTEMPTPROGRESSINFO'])) {
283 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->usecurrentattemptprogressinfo = $sequencing['attrs']['USECURRENTATTEMPTPROGRESSINFO'] == 'true'?1:0;
284 }
285 }
41e205cd
MG
286 if ($sequencing['name'] == 'IMSSS:DELIVERYCONTROLS') {
287 if (isset($sequencing['attrs']['TRACKED'])) {
288 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->tracked = $sequencing['attrs']['TRACKED'] == 'true'?1:0;
289 }
290 if (isset($sequencing['attrs']['COMPLETIONSETBYCONTENT'])) {
291 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->completionsetbycontent = $sequencing['attrs']['COMPLETIONSETBYCONTENT'] == 'true'?1:0;
292 }
293 if (isset($sequencing['attrs']['OBJECTIVESETBYCONTENT'])) {
294 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->objectivesetbycontent = $sequencing['attrs']['OBJECTIVESETBYCONTENT'] == 'true'?1:0;
295 }
296 }
33b6a8f4 297 if ($sequencing['name']=='ADLSEQ:CONSTRAINEDCHOICECONSIDERATIONS') {
298 if (isset($sequencing['attrs']['CONSTRAINCHOICE'])) {
299 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->constrainChoice = $sequencing['attrs']['CONSTRAINCHOICE'] == 'true'?1:0;
300 }
301 if (isset($sequencing['attrs']['PREVENTACTIVATION'])) {
302 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->preventactivation = $sequencing['attrs']['PREVENTACTIVATION'] == 'true'?1:0;
303 }
304 }
305 if ($sequencing['name']=='IMSSS:OBJECTIVES') {
306 $objectives = array();
307 foreach ($sequencing['children'] as $objective) {
308 $objectivedata = new stdClass();
309 $objectivedata->primaryobj = 0;
310 switch ($objective['name']) {
311 case 'IMSSS:PRIMARYOBJECTIVE':
312 $objectivedata->primaryobj = 1;
313 case 'IMSSS:OBJECTIVE':
314 $objectivedata->satisfiedbymeasure = 0;
315 if (isset($objective['attrs']['SATISFIEDBYMEASURE'])) {
316 $objectivedata->satisfiedbymeasure = $objective['attrs']['SATISFIEDBYMEASURE']== 'true'?1:0;
317 }
318 $objectivedata->objectiveid = '';
319 if (isset($objective['attrs']['OBJECTIVEID'])) {
320 $objectivedata->objectiveid = $objective['attrs']['OBJECTIVEID'];
321 }
322 $objectivedata->minnormalizedmeasure = 1.0;
323 if (!empty($objective['children'])) {
324 $mapinfos = array();
325 foreach ($objective['children'] as $objectiveparam) {
326 if ($objectiveparam['name']=='IMSSS:MINNORMALIZEDMEASURE') {
7f4a3293 327 if (isset($objectiveparam['tagData'])) {
328 $objectivedata->minnormalizedmeasure = $objectiveparam['tagData'];
329 } else {
330 $objectivedata->minnormalizedmeasure = 0;
331 }
33b6a8f4 332 }
333 if ($objectiveparam['name']=='IMSSS:MAPINFO') {
334 $mapinfo = new stdClass();
335 $mapinfo->targetobjectiveid = '';
336 if (isset($objectiveparam['attrs']['TARGETOBJECTIVEID'])) {
337 $mapinfo->targetobjectiveid = $objectiveparam['attrs']['TARGETOBJECTIVEID'];
338 }
339 $mapinfo->readsatisfiedstatus = 1;
340 if (isset($objectiveparam['attrs']['READSATISFIEDSTATUS'])) {
341 $mapinfo->readsatisfiedstatus = $objectiveparam['attrs']['READSATISFIEDSTATUS'] == 'true'?1:0;
342 }
343 $mapinfo->writesatisfiedstatus = 0;
344 if (isset($objectiveparam['attrs']['WRITESATISFIEDSTATUS'])) {
345 $mapinfo->writesatisfiedstatus = $objectiveparam['attrs']['WRITESATISFIEDSTATUS'] == 'true'?1:0;
346 }
347 $mapinfo->readnormalizemeasure = 1;
348 if (isset($objectiveparam['attrs']['READNORMALIZEDMEASURE'])) {
349 $mapinfo->readnormalizemeasure = $objectiveparam['attrs']['READNORMALIZEDMEASURE'] == 'true'?1:0;
350 }
351 $mapinfo->writenormalizemeasure = 0;
352 if (isset($objectiveparam['attrs']['WRITENORMALIZEDMEASURE'])) {
353 $mapinfo->writenormalizemeasure = $objectiveparam['attrs']['WRITENORMALIZEDMEASURE'] == 'true'?1:0;
354 }
f7b5c6aa 355 array_push($mapinfos, $mapinfo);
33b6a8f4 356 }
357 }
358 if (!empty($mapinfos)) {
359 $objectivesdata->mapinfos = $mapinfos;
360 }
361 }
362 break;
b3659259 363 }
f7b5c6aa 364 array_push($objectives, $objectivedata);
33b6a8f4 365 }
366 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->objectives = $objectives;
367 }
368 if ($sequencing['name']=='IMSSS:LIMITCONDITIONS') {
369 if (isset($sequencing['attrs']['ATTEMPTLIMIT'])) {
309ee089 370 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->attemptLimit = $sequencing['attrs']['ATTEMPTLIMIT'];
33b6a8f4 371 }
372 if (isset($sequencing['attrs']['ATTEMPTABSOLUTEDURATIONLIMIT'])) {
309ee089 373 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->attemptAbsoluteDurationLimit = $sequencing['attrs']['ATTEMPTABSOLUTEDURATIONLIMIT'];
33b6a8f4 374 }
375 }
376 if ($sequencing['name']=='IMSSS:ROLLUPRULES') {
377 if (isset($sequencing['attrs']['ROLLUPOBJECTIVESATISFIED'])) {
0e35ba6f 378 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->rollupobjectivesatisfied = $sequencing['attrs']['ROLLUPOBJECTIVESATISFIED'] == 'true'?1:0;
33b6a8f4 379 }
380 if (isset($sequencing['attrs']['ROLLUPPROGRESSCOMPLETION'])) {
309ee089 381 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->rollupprogresscompletion = $sequencing['attrs']['ROLLUPPROGRESSCOMPLETION'] == 'true'?1:0;
33b6a8f4 382 }
383 if (isset($sequencing['attrs']['OBJECTIVEMEASUREWEIGHT'])) {
309ee089 384 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->objectivemeasureweight = $sequencing['attrs']['OBJECTIVEMEASUREWEIGHT'];
33b6a8f4 385 }
309ee089 386
f7b5c6aa 387 if (!empty($sequencing['children'])) {
33b6a8f4 388 $rolluprules = array();
389 foreach ($sequencing['children'] as $sequencingrolluprule) {
390 if ($sequencingrolluprule['name']=='IMSSS:ROLLUPRULE' ) {
391 $rolluprule = new stdClass();
392 $rolluprule->childactivityset = 'all';
393 if (isset($sequencingrolluprule['attrs']['CHILDACTIVITYSET'])) {
394 $rolluprule->childactivityset = $sequencingrolluprule['attrs']['CHILDACTIVITYSET'];
395 }
396 $rolluprule->minimumcount = 0;
397 if (isset($sequencingrolluprule['attrs']['MINIMUMCOUNT'])) {
398 $rolluprule->minimumcount = $sequencingrolluprule['attrs']['MINIMUMCOUNT'];
399 }
400 $rolluprule->minimumpercent = 0.0000;
401 if (isset($sequencingrolluprule['attrs']['MINIMUMPERCENT'])) {
402 $rolluprule->minimumpercent = $sequencingrolluprule['attrs']['MINIMUMPERCENT'];
403 }
404 if (!empty($sequencingrolluprule['children'])) {
405 foreach ($sequencingrolluprule['children'] as $rolluproleconditions) {
406 if ($rolluproleconditions['name']=='IMSSS:ROLLUPCONDITIONS') {
407 $conditions = array();
408 $rolluprule->conditioncombination = 'all';
409 if (isset($rolluproleconditions['attrs']['CONDITIONCOMBINATION'])) {
410 $rolluprule->conditioncombination = $rolluproleconditions['attrs']['CONDITIONCOMBINATION'];
411 }
412 foreach ($rolluproleconditions['children'] as $rolluprulecondition) {
413 if ($rolluprulecondition['name']=='IMSSS:ROLLUPCONDITION') {
414 $condition = new stdClass();
415 if (isset($rolluprulecondition['attrs']['CONDITION'])) {
416 $condition->cond = $rolluprulecondition['attrs']['CONDITION'];
417 }
418 $condition->operator = 'noOp';
419 if (isset($rolluprulecondition['attrs']['OPERATOR'])) {
420 $condition->operator = $rolluprulecondition['attrs']['OPERATOR'];
421 }
f7b5c6aa 422 array_push($conditions, $condition);
33b6a8f4 423 }
424 }
425 $rolluprule->conditions = $conditions;
426 }
427 if ($rolluproleconditions['name']=='IMSSS:ROLLUPACTION') {
428 $rolluprule->rollupruleaction = $rolluproleconditions['attrs']['ACTION'];
429 }
430 }
431 }
432 array_push($rolluprules, $rolluprule);
433 }
434 }
435 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->rolluprules = $rolluprules;
436 }
437 }
309ee089 438
33b6a8f4 439 if ($sequencing['name']=='IMSSS:SEQUENCINGRULES') {
440 if (!empty($sequencing['children'])) {
441 $sequencingrules = array();
442 foreach ($sequencing['children'] as $conditionrules) {
443 $conditiontype = -1;
444 switch($conditionrules['name']) {
445 case 'IMSSS:PRECONDITIONRULE':
446 $conditiontype = 0;
447 break;
448 case 'IMSSS:POSTCONDITIONRULE':
449 $conditiontype = 1;
450 break;
451 case 'IMSSS:EXITCONDITIONRULE':
452 $conditiontype = 2;
453 break;
454 }
455 if (!empty($conditionrules['children'])) {
456 $sequencingrule = new stdClass();
457 foreach ($conditionrules['children'] as $conditionrule) {
458 if ($conditionrule['name']=='IMSSS:RULECONDITIONS') {
459 $ruleconditions = array();
460 $sequencingrule->conditioncombination = 'all';
461 if (isset($conditionrule['attrs']['CONDITIONCOMBINATION'])) {
462 $sequencingrule->conditioncombination = $conditionrule['attrs']['CONDITIONCOMBINATION'];
463 }
464 foreach ($conditionrule['children'] as $rulecondition) {
465 if ($rulecondition['name']=='IMSSS:RULECONDITION') {
466 $condition = new stdClass();
467 if (isset($rulecondition['attrs']['CONDITION'])) {
468 $condition->cond = $rulecondition['attrs']['CONDITION'];
469 }
470 $condition->operator = 'noOp';
471 if (isset($rulecondition['attrs']['OPERATOR'])) {
472 $condition->operator = $rulecondition['attrs']['OPERATOR'];
473 }
474 $condition->measurethreshold = 0.0000;
475 if (isset($rulecondition['attrs']['MEASURETHRESHOLD'])) {
476 $condition->measurethreshold = $rulecondition['attrs']['MEASURETHRESHOLD'];
477 }
478 $condition->referencedobjective = '';
479 if (isset($rulecondition['attrs']['REFERENCEDOBJECTIVE'])) {
480 $condition->referencedobjective = $rulecondition['attrs']['REFERENCEDOBJECTIVE'];
309ee089 481 }
f7b5c6aa 482 array_push($ruleconditions, $condition);
33b6a8f4 483 }
484 }
485 $sequencingrule->ruleconditions = $ruleconditions;
486 }
487 if ($conditionrule['name']=='IMSSS:RULEACTION') {
488 $sequencingrule->action = $conditionrule['attrs']['ACTION'];
489 }
490 $sequencingrule->type = $conditiontype;
491 }
f7b5c6aa 492 array_push($sequencingrules, $sequencingrule);
33b6a8f4 493 }
494 }
495 $scoes->elements[$manifest][$parent->organization][$parent->identifier]->sequencingrules = $sequencingrules;
b3659259 496 }
b3659259 497 }
498 }
499 }
500 break;
dc383b6f 501 }
502 }
503 }
03c34889
DM
504 if (!empty($manifestresourcesnotfound)) {
505 //throw warning to user to let them know manifest contains references to resources that don't appear to exist.
506 if (!defined('DEBUGGING_PRINTED')) { //prevent redirect and display warning
507 define('DEBUGGING_PRINTED', 1);
508 }
509 echo $OUTPUT->notification(get_string('invalidmanifestresource', 'scorm').' '. implode(', ',$manifestresourcesnotfound));
510 }
dc383b6f 511 return $scoes;
512}
513
02bff1ad
DM
514/**
515 * Sets up SCORM 1.2/2004 packages using the manifest file.
516 * Called whenever SCORM changes
517 * @param object $scorm instance - fields are updated and changes saved into database
518 * @param stored_file|string $manifest - path to manifest file or stored_file.
519 * @return bool
520 */
521function scorm_parse_scorm(&$scorm, $manifest) {
bf347041 522 global $CFG, $DB;
dc383b6f 523
9528568b 524 // load manifest into string
525 if ($manifest instanceof stored_file) {
526 $xmltext = $manifest->get_content();
527 } else {
f7b5c6aa 528 require_once("$CFG->libdir/filelib.php");
9528568b 529 $xmltext = download_file_content($manifest);
530 }
309ee089 531
02bff1ad
DM
532 $defaultorgid = 0;
533 $firstinorg = 0;
b3659259 534
9528568b 535 $pattern = '/&(?!\w{2,6};)/';
536 $replacement = '&amp;';
537 $xmltext = preg_replace($pattern, $replacement, $xmltext);
b3659259 538
9528568b 539 $objXML = new xml2Array();
540 $manifests = $objXML->parse($xmltext);
9528568b 541 $scoes = new stdClass();
542 $scoes->version = '';
f7b5c6aa 543 $scoes = scorm_get_manifest($manifests, $scoes);
02bff1ad 544 $newscoes = array();
b33e4665 545 $sortorder = 0;
9528568b 546 if (count($scoes->elements) > 0) {
547 $olditems = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id));
548 foreach ($scoes->elements as $manifest => $organizations) {
549 foreach ($organizations as $organization => $items) {
550 foreach ($items as $identifier => $item) {
b33e4665 551 $sortorder++;
9528568b 552 // This new db mngt will support all SCORM future extensions
309ee089 553 $newitem = new stdClass();
9528568b 554 $newitem->scorm = $scorm->id;
555 $newitem->manifest = $manifest;
556 $newitem->organization = $organization;
b33e4665 557 $newitem->sortorder = $sortorder;
9528568b 558 $standarddatas = array('parent', 'identifier', 'launch', 'scormtype', 'title');
559 foreach ($standarddatas as $standarddata) {
560 if (isset($item->$standarddata)) {
561 $newitem->$standarddata = $item->$standarddata;
b33e4665
DM
562 } else {
563 $newitem->$standarddata = '';
dc383b6f 564 }
9528568b 565 }
309ee089 566
02bff1ad
DM
567 if (!empty($defaultorgid) && empty($firstinorg) && $newitem->parent == $scoes->defaultorg) {
568 $firstinorg = $sortorder;
569 }
570
f7b5c6aa 571 if (!empty($olditems) && ($olditemid = scorm_array_search('identifier', $newitem->identifier, $olditems))) {
b33e4665
DM
572 $newitem->id = $olditemid;
573 // Update the Sco sortorder but keep id so that user tracks are kept against the same ids.
574 $DB->update_record('scorm_scoes', $newitem);
575 $id = $olditemid;
576 // Remove all old data so we don't duplicate it.
577 $DB->delete_records('scorm_scoes_data', array('scoid'=>$olditemid));
578 $DB->delete_records('scorm_seq_objective', array('scoid'=>$olditemid));
579 $DB->delete_records('scorm_seq_mapinfo', array('scoid'=>$olditemid));
580 $DB->delete_records('scorm_seq_ruleconds', array('scoid'=>$olditemid));
581 $DB->delete_records('scorm_seq_rulecond', array('scoid'=>$olditemid));
582 $DB->delete_records('scorm_seq_rolluprule', array('scoid'=>$olditemid));
583 $DB->delete_records('scorm_seq_rolluprulecond', array('scoid'=>$olditemid));
584
585 // Now remove this SCO from the olditems object as we have dealt with it.
586 unset($olditems[$olditemid]);
587 } else {
588 // Insert the new SCO, and retain the link between the old and new for later adjustment
589 $id = $DB->insert_record('scorm_scoes', $newitem);
9528568b 590 }
02bff1ad 591 $newscoes[$id] = $newitem; // Save this sco in memory so we can use it later.
309ee089 592
f7b5c6aa 593 if ($optionaldatas = scorm_optionals_data($item, $standarddatas)) {
9528568b 594 $data = new stdClass();
595 $data->scoid = $id;
596 foreach ($optionaldatas as $optionaldata) {
597 if (isset($item->$optionaldata)) {
598 $data->name = $optionaldata;
599 $data->value = $item->$optionaldata;
f7b5c6aa 600 $dataid = $DB->insert_record('scorm_scoes_data', $data);
dc383b6f 601 }
33b6a8f4 602 }
9528568b 603 }
dc383b6f 604
9528568b 605 if (isset($item->sequencingrules)) {
f7b5c6aa 606 foreach ($item->sequencingrules as $sequencingrule) {
9528568b 607 $rule = new stdClass();
608 $rule->scoid = $id;
609 $rule->ruletype = $sequencingrule->type;
610 $rule->conditioncombination = $sequencingrule->conditioncombination;
611 $rule->action = $sequencingrule->action;
f7b5c6aa 612 $ruleid = $DB->insert_record('scorm_seq_ruleconds', $rule);
9528568b 613 if (isset($sequencingrule->ruleconditions)) {
f7b5c6aa 614 foreach ($sequencingrule->ruleconditions as $rulecondition) {
9528568b 615 $rulecond = new stdClass();
616 $rulecond->scoid = $id;
617 $rulecond->ruleconditionsid = $ruleid;
618 $rulecond->referencedobjective = $rulecondition->referencedobjective;
619 $rulecond->measurethreshold = $rulecondition->measurethreshold;
41e205cd 620 $rulecond->operator = $rulecondition->operator;
9528568b 621 $rulecond->cond = $rulecondition->cond;
f7b5c6aa 622 $rulecondid = $DB->insert_record('scorm_seq_rulecond', $rulecond);
33b6a8f4 623 }
9528568b 624 }
309ee089 625 }
9528568b 626 }
309ee089 627
9528568b 628 if (isset($item->rolluprules)) {
f7b5c6aa 629 foreach ($item->rolluprules as $rolluprule) {
9528568b 630 $rollup = new stdClass();
631 $rollup->scoid = $id;
632 $rollup->childactivityset = $rolluprule->childactivityset;
633 $rollup->minimumcount = $rolluprule->minimumcount;
634 $rollup->minimumpercent = $rolluprule->minimumpercent;
635 $rollup->rollupruleaction = $rolluprule->rollupruleaction;
636 $rollup->conditioncombination = $rolluprule->conditioncombination;
33b6a8f4 637
f7b5c6aa 638 $rollupruleid = $DB->insert_record('scorm_seq_rolluprule', $rollup);
9528568b 639 if (isset($rollup->conditions)) {
f7b5c6aa 640 foreach ($rollup->conditions as $condition) {
9528568b 641 $cond = new stdClass();
642 $cond->scoid = $rollup->scoid;
643 $cond->rollupruleid = $rollupruleid;
644 $cond->operator = $condition->operator;
645 $cond->cond = $condition->cond;
f7b5c6aa 646 $conditionid = $DB->insert_record('scorm_seq_rolluprulecond', $cond);
33b6a8f4 647 }
9528568b 648 }
309ee089 649 }
9528568b 650 }
33b6a8f4 651
9528568b 652 if (isset($item->objectives)) {
f7b5c6aa 653 foreach ($item->objectives as $objective) {
9528568b 654 $obj = new stdClass();
655 $obj->scoid = $id;
656 $obj->primaryobj = $objective->primaryobj;
657 $obj->satisfiedbumeasure = $objective->satisfiedbymeasure;
658 $obj->objectiveid = $objective->objectiveid;
45999e78 659 $obj->minnormalizedmeasure = trim($objective->minnormalizedmeasure);
f7b5c6aa 660 $objectiveid = $DB->insert_record('scorm_seq_objective', $obj);
9528568b 661 if (isset($objective->mapinfos)) {
f7b5c6aa 662 foreach ($objective->mapinfos as $objmapinfo) {
9528568b 663 $mapinfo = new stdClass();
664 $mapinfo->scoid = $id;
665 $mapinfo->objectiveid = $objectiveid;
666 $mapinfo->targetobjectiveid = $objmapinfo->targetobjectiveid;
667 $mapinfo->readsatisfiedstatus = $objmapinfo->readsatisfiedstatus;
668 $mapinfo->writesatisfiedstatus = $objmapinfo->writesatisfiedstatus;
669 $mapinfo->readnormalizedmeasure = $objmapinfo->readnormalizedmeasure;
670 $mapinfo->writenormalizedmeasure = $objmapinfo->writenormalizedmeasure;
f7b5c6aa 671 $mapinfoid = $DB->insert_record('scorm_seq_mapinfo', $mapinfo);
33b6a8f4 672 }
673 }
674 }
9528568b 675 }
02bff1ad
DM
676 if (empty($defaultorgid) && ((empty($scoes->defaultorg)) || ($scoes->defaultorg == $identifier))) {
677 $defaultorgid = $id;
dc383b6f 678 }
679 }
680 }
9528568b 681 }
682 if (!empty($olditems)) {
f7b5c6aa 683 foreach ($olditems as $olditem) {
9528568b 684 $DB->delete_records('scorm_scoes', array('id'=>$olditem->id));
f7b5c6aa 685 $DB->delete_records('scorm_scoes_data', array('scoid'=>$olditem->id));
f7b5c6aa 686 $DB->delete_records('scorm_scoes_track', array('scoid'=>$olditem->id));
9528568b 687 $DB->delete_records('scorm_seq_objective', array('scoid'=>$olditem->id));
688 $DB->delete_records('scorm_seq_mapinfo', array('scoid'=>$olditem->id));
689 $DB->delete_records('scorm_seq_ruleconds', array('scoid'=>$olditem->id));
690 $DB->delete_records('scorm_seq_rulecond', array('scoid'=>$olditem->id));
691 $DB->delete_records('scorm_seq_rolluprule', array('scoid'=>$olditem->id));
692 $DB->delete_records('scorm_seq_rolluprulecond', array('scoid'=>$olditem->id));
dc383b6f 693 }
dc383b6f 694 }
309ee089 695 if (empty($scoes->version)) {
696 $scoes->version = 'SCORM_1.2';
697 }
02bff1ad 698 $DB->set_field('scorm', 'version', $scoes->version, array('id' => $scorm->id));
9528568b 699 $scorm->version = $scoes->version;
700 }
02bff1ad
DM
701 $scorm->launch = 0;
702 // Check launch sco is valid.
703 if (!empty($defaultorgid) && isset($newscoes[$defaultorgid]) && !empty($newscoes[$defaultorgid]->launch)) {
704 // Launch param is valid - do nothing.
705 $scorm->launch = $defaultorgid;
706 } else if (!empty($defaultorgid) && isset($newscoes[$defaultorgid]) && empty($newscoes[$defaultorgid]->launch)) {
707 // The launch is probably the default org so we need to find the first launchable item inside this org.
e067e6f0 708 $sqlselect = 'scorm = ? AND sortorder >= ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true);
02bff1ad
DM
709 // We use get_records here as we need to pass a limit in the query that works cross db.
710 $scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scorm->id, $firstinorg), 'sortorder', 'id', 0, 1);
711 if (!empty($scoes)) {
712 $sco = reset($scoes); // We only care about the first record - the above query only returns one.
713 $scorm->launch = $sco->id;
714 }
715 }
716 if (empty($scorm->launch)) {
717 // No valid Launch is specified - find the first launchable sco instead.
718 $sqlselect = 'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true);
719 // We use get_records here as we need to pass a limit in the query that works cross db.
720 $scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scorm->id), 'sortorder', 'id', 0, 1);
721 if (!empty($scoes)) {
722 $sco = reset($scoes); // We only care about the first record - the above query only returns one.
723 $scorm->launch = $sco->id;
724 }
725 }
9528568b 726
727 return true;
dc383b6f 728}
729
b3659259 730function scorm_optionals_data($item, $standarddata) {
731 $result = array();
f7b5c6aa 732 $sequencingdata = array('sequencingrules', 'rolluprules', 'objectives');
b3659259 733 foreach ($item as $element => $value) {
734 if (! in_array($element, $standarddata)) {
33b6a8f4 735 if (! in_array($element, $sequencingdata)) {
736 $result[] = $element;
737 }
b3659259 738 }
739 }
740 return $result;
741}
742
10fd330f 743function scorm_is_leaf($sco) {
bf347041 744 global $DB;
745
92be6fa4 746 if ($DB->record_exists('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->identifier))) {
10fd330f 747 return false;
748 }
749 return true;
750}
751
752function scorm_get_parent($sco) {
bf347041 753 global $DB;
309ee089 754
10fd330f 755 if ($sco->parent != '/') {
f7b5c6aa 756 if ($parent = $DB->get_record('scorm_scoes', array('scorm'=>$sco->scorm, 'identifier'=>$sco->parent))) {
10fd330f 757 return scorm_get_sco($parent->id);
758 }
759 }
760 return null;
761}
762
763function scorm_get_children($sco) {
bf347041 764 global $DB;
765
b33e4665
DM
766 $children = $DB->get_records('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->identifier), 'sortorder, id');
767 if (!empty($children)) {
10fd330f 768 return $children;
769 }
770 return null;
771}
772
41e205cd 773function scorm_get_available_children($sco) {
bf347041 774 global $DB;
775
b33e4665 776 $res = $DB->get_records('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->identifier), 'sortorder, id');
f7b5c6aa 777 if (!$res || $res == null) {
a9d71982 778 return false;
f7b5c6aa 779 } else {
41e205cd
MG
780 foreach ($res as $sco) {
781 $result[] = $sco;
782 }
783 return $result;
a9d71982 784 }
60a3aaf9 785}
786
f7b5c6aa
DM
787function scorm_get_available_descendent($descend = array(), $sco) {
788 if ($sco == null) {
a9d71982 789 return $descend;
f7b5c6aa 790 } else {
a9d71982 791 $avchildren = scorm_get_available_children($sco);
f7b5c6aa
DM
792 foreach ($avchildren as $avchild) {
793 array_push($descend, $avchild);
a9d71982 794 }
f7b5c6aa
DM
795 foreach ($avchildren as $avchild) {
796 scorm_get_available_descendent($descend, $avchild);
a9d71982 797 }
798 }
60a3aaf9 799}
800
801function scorm_get_siblings($sco) {
bf347041 802 global $DB;
803
b33e4665 804 if ($siblings = $DB->get_records('scorm_scoes', array('scorm'=>$sco->scorm, 'parent'=>$sco->parent), 'sortorder, id')) {
10fd330f 805 unset($siblings[$sco->id]);
806 if (!empty($siblings)) {
807 return $siblings;
808 }
809 }
810 return null;
811}
c5a51ed5 812//get an array that contains all the parent scos for this sco.
10fd330f 813function scorm_get_ancestors($sco) {
c5a51ed5
DM
814 $ancestors = array();
815 $continue = true;
f7b5c6aa 816 while ($continue) {
c5a51ed5
DM
817 $ancestor = scorm_get_parent($sco);
818 if (!empty($ancestor) && $ancestor->id !== $sco->id) {
819 $sco = $ancestor;
820 $ancestors[] = $ancestor;
821 if ($sco->parent == '/') {
822 $continue = false;
823 }
824 } else {
825 $continue = false;
826 }
10fd330f 827 }
c5a51ed5 828 return $ancestors;
10fd330f 829}
830
41e205cd 831function scorm_get_preorder(&$preorder = array(), $sco = null) {
60a3aaf9 832 if ($sco != null) {
f7b5c6aa 833 array_push($preorder, $sco);
41e205cd
MG
834 if ($children = scorm_get_children($sco)) {
835 foreach ($children as $child) {
836 scorm_get_preorder($preorder, $child);
837 }
a9d71982 838 }
60a3aaf9 839 }
41e205cd 840 return $preorder;
60a3aaf9 841}
842
10fd330f 843function scorm_find_common_ancestor($ancestors, $sco) {
f7b5c6aa 844 $pos = scorm_array_search('identifier', $sco->parent, $ancestors);
10fd330f 845 if ($sco->parent != '/') {
846 if ($pos === false) {
f7b5c6aa 847 return scorm_find_common_ancestor($ancestors, scorm_get_parent($sco));
10fd330f 848 }
849 }
850 return $pos;
851}
852
dc383b6f 853/* Usage
854 Grab some XML data, either from a file, URL, etc. however you want. Assume storage in $strYourXML;
855
856 $objXML = new xml2Array();
857 $arrOutput = $objXML->parse($strYourXML);
858 print_r($arrOutput); //print it out, or do whatever!
309ee089 859
dc383b6f 860*/
861class xml2Array {
309ee089 862
f7b5c6aa
DM
863 var $arrOutput = array();
864 var $resParser;
865 var $strXmlData;
866
867 /**
868 * Convert a utf-8 string to html entities
869 *
870 * @param string $str The UTF-8 string
871 * @return string
872 */
873 function utf8_to_entities($str) {
874 global $CFG;
875
876 $entities = '';
877 $values = array();
878 $lookingfor = 1;
879
880 return $str;
881 }
882
883 /**
884 * Parse an XML text string and create an array tree that rapresent the XML structure
885 *
886 * @param string $strInputXML The XML string
887 * @return array
888 */
889 function parse($strInputXML) {
890 $this->resParser = xml_parser_create ('UTF-8');
891 xml_set_object($this->resParser, $this);
892 xml_set_element_handler($this->resParser, "tagOpen", "tagClosed");
893
894 xml_set_character_data_handler($this->resParser, "tagData");
895
896 $this->strXmlData = xml_parse($this->resParser, $strInputXML );
897 if (!$this->strXmlData) {
898 die(sprintf("XML error: %s at line %d",
899 xml_error_string(xml_get_error_code($this->resParser)),
900 xml_get_current_line_number($this->resParser)));
901 }
902
903 xml_parser_free($this->resParser);
904
905 return $this->arrOutput;
906 }
907
908 function tagOpen($parser, $name, $attrs) {
909 $tag=array("name"=>$name, "attrs"=>$attrs);
910 array_push($this->arrOutput, $tag);
911 }
912
913 function tagData($parser, $tagData) {
914 if (trim($tagData)) {
915 if (isset($this->arrOutput[count($this->arrOutput)-1]['tagData'])) {
916 $this->arrOutput[count($this->arrOutput)-1]['tagData'] .= $this->utf8_to_entities($tagData);
917 } else {
918 $this->arrOutput[count($this->arrOutput)-1]['tagData'] = $this->utf8_to_entities($tagData);
919 }
920 }
921 }
922
923 function tagClosed($parser, $name) {
924 $this->arrOutput[count($this->arrOutput)-2]['children'][] = $this->arrOutput[count($this->arrOutput)-1];
925 array_pop($this->arrOutput);
926 }
dc383b6f 927
928}