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