2 // This file is part of Moodle - http://moodle.org/
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 defined('MOODLE_INTERNAL') ||
die();
19 require_once($CFG->dirroot
.'/mod/scorm/datamodels/scormlib.php');
20 require_once($CFG->dirroot
.'/mod/scorm/datamodels/sequencinglib.php');
22 function scorm_seq_overall ($scoid, $userid, $request, $attempt) {
23 $seq = scorm_seq_navigation($scoid, $userid, $request, $attempt);
24 if ($seq->navigation
) {
25 if ($seq->termination
!= null) {
26 $seq = scorm_seq_termination($scoid, $userid, $seq);
28 if ($seq->sequencing
!= null) {
29 $seq = scorm_seq_sequencing($scoid, $userid, $seq);
30 if ($seq->sequencing
== 'exit') { // Return the control to the LTS.
34 if ($seq->delivery
!= null) {
35 $seq = scorm_sequencing_delivery($scoid, $userid, $seq);
36 $seq = scorm_content_delivery_environment ($seq, $userid);
39 if ($seq->exception
!= null) {
40 $seq = scorm_sequencing_exception($seq);
45 function scorm_seq_navigation ($scoid, $userid, $request, $attempt=0) {
48 // Sequencing structure.
49 $seq = new stdClass();
50 $seq->currentactivity
= scorm_get_sco($scoid);
51 $seq->traversaldir
= null;
52 $seq->nextactivity
= null;
53 $seq->deliveryvalid
= null;
54 $seq->attempt
= $attempt;
56 $seq->identifiedactivity
= null;
57 $seq->delivery
= null;
58 $seq->deliverable
= false;
59 $seq->active
= scorm_seq_is('active', $scoid, $userid);
60 $seq->suspended
= scorm_seq_is('suspended', $scoid, $userid);
61 $seq->navigation
= null;
62 $seq->termination
= null;
63 $seq->sequencing
= null;
65 $seq->endsession
= null;
66 $seq->exception
= null;
67 $seq->reachable
= true;
70 $sco = scorm_get_sco($scoid);
74 if (empty($seq->currentactivity
)) {
75 $seq->navigation
= true;
76 $seq->sequencing
= 'start';
78 $seq->exception
= 'NB.2.1-1'; // Sequencing session already begun.
82 if (empty($seq->currentactivity
)) {
83 // TODO: I think it's suspend instead of suspendedactivity.
84 if ($track = $DB->get_record('scorm_scoes_track',
85 array('scoid' => $scoid, 'userid' => $userid, 'element' => 'suspendedactivity'))) {
87 $seq->navigation
= true;
88 $seq->sequencing
= 'resumeall';
90 $seq->exception
= 'NB.2.1-3'; // No suspended activity found.
93 $seq->exception
= 'NB.2.1-1'; // Sequencing session already begun.
98 if (!empty($seq->currentactivity
)) {
99 $sco = $seq->currentactivity
;
100 if ($sco->parent
!= '/') {
101 if ($parentsco = scorm_get_parent($sco)) {
103 if (isset($parentsco->flow
) && ($parentsco->flow
== true)) { // I think it's parentsco.
104 // Current activity is active!
105 if (scorm_seq_is('active', $sco->id
, $userid)) {
106 if ($request == 'continue_') {
107 $seq->navigation
= true;
108 $seq->termination
= 'exit';
109 $seq->sequencing
= 'continue';
111 if (!isset($parentsco->forwardonly
) ||
($parentsco->forwardonly
== false)) {
112 $seq->navigation
= true;
113 $seq->termination
= 'exit';
114 $seq->sequencing
= 'previous';
116 $seq->exception
= 'NB.2.1-5'; // Violates control mode.
125 $seq->exception
= 'NB.2.1-2'; // Current activity not defined.
130 $seq->exception
= 'NB.2.1-7'; // None to be done, behavior not defined.
134 if (!empty($seq->currentactivity
)) {
135 // Current activity is active !
136 $seq->navigation
= true;
137 $seq->termination
= substr($request, 0, -1);
138 $seq->sequencing
= 'exit';
140 $seq->exception
= 'NB.2.1-2'; // Current activity not defined.
145 if (!empty($seq->currentactivity
)) {
146 $seq->navigation
= true;
147 $seq->termination
= substr($request, 0, -1);
148 $seq->sequencing
= 'exit';
150 $seq->exception
= 'NB.2.1-2'; // Current activity not defined.
153 default: // Example {target=<STRING>}choice.
154 if ($targetsco = $DB->get_record('scorm_scoes', array('scorm' => $sco->scorm
, 'identifier' => $request))) {
155 if ($targetsco->parent
!= '/') {
156 $seq->target
= $request;
158 if ($parentsco = scorm_get_parent($targetsco)) {
159 if (!isset($parentsco->choice
) ||
($parentsco->choice
== true)) {
160 $seq->target
= $request;
164 if ($seq->target
!= null) {
165 if (empty($seq->currentactivity
)) {
166 $seq->navigation
= true;
167 $seq->sequencing
= 'choice';
169 if (!$sco = scorm_get_sco($scoid)) {
172 if ($sco->parent
!= $targetsco->parent
) {
173 $ancestors = scorm_get_ancestors($sco);
174 $commonpos = scorm_find_common_ancestor($ancestors, $targetsco);
175 if ($commonpos !== false) {
176 if ($activitypath = array_slice($ancestors, 0, $commonpos)) {
177 foreach ($activitypath as $activity) {
178 if (scorm_seq_is('active', $activity->id
, $userid) &&
179 (isset($activity->choiceexit
) && ($activity->choiceexit
== false))) {
180 $seq->navigation
= false;
181 $seq->termination
= null;
182 $seq->sequencing
= null;
184 $seq->exception
= 'NB.2.1-8'; // Violates control mode.
189 $seq->navigation
= false;
190 $seq->termination
= null;
191 $seq->sequencing
= null;
193 $seq->exception
= 'NB.2.1-9';
197 // Current activity is active !
198 $seq->navigation
= true;
199 $seq->sequencing
= 'choice';
202 $seq->exception
= 'NB.2.1-10'; // Violates control mode.
205 $seq->exception
= 'NB.2.1-11'; // Target activity does not exists.
212 function scorm_seq_termination ($seq, $userid) {
213 if (empty($seq->currentactivity
)) {
214 $seq->termination
= false;
215 $seq->exception
= 'TB.2.3-1';
219 $sco = $seq->currentactivity
;
221 if ((($seq->termination
== 'exit') ||
($seq->termination
== 'abandon')) && !$seq->active
) {
222 $seq->termination
= false;
223 $seq->exception
= 'TB.2.3-2';
226 switch ($seq->termination
) {
228 scorm_seq_end_attempt($sco, $userid, $seq);
229 $seq = scorm_seq_exit_action_rules($seq, $userid);
231 $exit = false;// I think this is false. Originally this was true.
232 $seq = scorm_seq_post_cond_rules($seq, $userid);
233 if ($seq->termination
== 'exitparent') {
234 if ($sco->parent
!= '/') {
235 $sco = scorm_get_parent($sco);
236 $seq->currentactivity
= $sco;
237 $seq->active
= scorm_seq_is('active', $sco->id
, $userid);
238 scorm_seq_end_attempt($sco, $userid, $seq);
239 $exit = true; // I think it's true. Originally this was false.
241 $seq->termination
= false;
242 $seq->exception
= 'TB.2.3-4';
246 } while (($exit == false) && ($seq->termination
== 'exit'));
247 if ($seq->termination
== 'exit') {
248 $seq->termination
= true;
253 scorm_seq_end_attempt($sco, $userid, $seq);
255 // Terminate Descendent Attempts Process.
257 if ($ancestors = scorm_get_ancestors($sco)) {
258 foreach ($ancestors as $ancestor) {
259 scorm_seq_end_attempt($ancestor, $userid, $seq);
260 $seq->currentactivity
= $ancestor;
264 $seq->active
= scorm_seq_is('active', $seq->currentactivity
->id
, $userid);
265 $seq->termination
= true;
266 $seq->sequencing
= 'exit';
269 if (($seq->active
) ||
($seq->suspended
)) {
270 scorm_seq_set('suspended', $sco->id
, $userid, $attempt);
272 if ($sco->parent
!= '/') {
273 $parentsco = scorm_get_parent($sco);
274 scorm_seq_set('suspended', $parentsco->id
, $userid, $attempt);
276 $seq->termination
= false;
277 $seq->exception
= 'TB.2.3-3';
280 if ($ancestors = scorm_get_ancestors($sco)) {
281 foreach ($ancestors as $ancestor) {
282 scorm_seq_set('active', $ancestor->id
, $userid, $attempt, false);
283 scorm_seq_set('suspended', $ancestor->id
, $userid, $attempt);
284 $seq->currentactivity
= $ancestor;
286 $seq->termination
= true;
287 $seq->sequencing
= 'exit';
289 $seq->termination
= false;
290 $seq->exception
= 'TB.2.3-5';
294 scorm_seq_set('active', $sco->id
, $userid, $attempt, false);
296 $seq->termination
= true;
299 if ($ancestors = scorm_get_ancestors($sco)) {
300 foreach ($ancestors as $ancestor) {
301 scorm_seq_set('active', $ancestor->id
, $userid, $attempt, false);
302 $seq->currentactivity
= $ancestor;
304 $seq->termination
= true;
305 $seq->sequencing
= 'exit';
307 $seq->termination
= false;
308 $seq->exception
= 'TB.2.3-6';
312 $seq->termination
= false;
313 $seq->exception
= 'TB.2.3-7';
319 function scorm_seq_end_attempt($sco, $userid, $seq) {
321 if (scorm_is_leaf($sco)) {
322 if (!isset($sco->tracked
) ||
($sco->tracked
== 1)) {
323 if (!scorm_seq_is('suspended', $sco->id
, $userid)) {
324 if (!isset($sco->completionsetbycontent
) ||
($sco->completionsetbycontent
== 0)) {
325 if (!scorm_seq_is('attemptprogressstatus', $sco->id
, $userid, $seq->attempt
)) {
326 $incomplete = $DB->get_field('scorm_scoes_track', 'value',
327 array('scoid' => $sco->id
,
329 'element' => 'cmi.completion_status'));
330 if ($incomplete != 'incomplete') {
331 scorm_seq_set('attemptprogressstatus', $sco->id
, $userid, $seq->attempt
);
332 scorm_seq_set('attemptcompletionstatus', $sco->id
, $userid, $seq->attempt
);
336 if (!isset($sco->objectivesetbycontent
) ||
($sco->objectivesetbycontent
== 0)) {
337 if ($objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id
))) {
338 foreach ($objectives as $objective) {
339 if ($objective->primaryobj
) {
340 if (!scorm_seq_is('objectiveprogressstatus', $sco->id
, $userid, $seq->attempt
)) {
341 scorm_seq_set('objectiveprogressstatus', $sco->id
, $userid, $seq->attempt
);
342 scorm_seq_set('objectivesatisfiedstatus', $sco->id
, $userid, $seq->attempt
);
350 } else if ($children = scorm_get_children($sco)) {
352 foreach ($children as $child) {
353 if (scorm_seq_is('suspended', $child, $userid, $seq->attempt
)) {
359 scorm_seq_set('suspended', $sco, $userid, $seq->attempt
);
361 scorm_seq_set('suspended', $sco, $userid, $seq->attempt
, false);
364 scorm_seq_set('active', $sco->id
, $userid, $seq->attempt
, false);
365 scorm_seq_overall_rollup($sco, $userid, $seq);
368 function scorm_seq_is($what, $scoid, $userid, $attempt=0) {
371 // Check if passed activity $what is active.
373 if ($track = $DB->get_record('scorm_scoes_track',
374 array('scoid' => $scoid, 'userid' => $userid, 'attempt' => $attempt, 'element' => $what))) {
380 function scorm_seq_set($what, $scoid, $userid, $attempt=0, $value='true') {
383 $sco = scorm_get_sco($scoid);
385 // Set passed activity to active or not.
386 if ($value == false) {
387 $DB->delete_records('scorm_scoes_track', array('scoid' => $scoid, 'userid' => $userid,
388 'attempt' => $attempt, 'element' => $what));
390 scorm_insert_track($userid, $sco->scorm
, $sco->id
, $attempt, $what, $value);
393 // Update grades in gradebook.
394 $scorm = $DB->get_record('scorm', array('id' => $sco->scorm
));
395 scorm_update_grades($scorm, $userid, true);
398 function scorm_evaluate_condition ($rollupruleconds, $sco, $userid) {
403 if (strpos($rollupruleconds, 'and ')) {
404 $rollupruleconds = array_filter(explode(' and ', $rollupruleconds));
405 $conditioncombination = 'all';
407 $rollupruleconds = array_filter(explode(' or ', $rollupruleconds));
408 $conditioncombination = 'or';
410 foreach ($rollupruleconds as $rolluprulecond) {
412 if (strpos($rolluprulecond, 'not') !== false) {
413 $rolluprulecond = str_replace('not', '', $rolluprulecond);
416 $conditionarray['condition'] = $rolluprulecond;
417 $conditionarray['notflag'] = $notflag;
418 $conditions[] = $conditionarray;
420 foreach ($conditions as $condition) {
423 if ($condition['notflag']) {
426 switch ($condition['condition']) {
428 $r = $DB->get_record('scorm_scoes_track',
429 array('scoid' => $sco->id
, 'userid' => $userid, 'element' => 'objectivesatisfiedstatus'));
430 if ((!isset($r->value
) && !$checknot) ||
(isset($r->value
) && ($r->value
== $checknot))) {
431 $r = $DB->get_record('scorm_scoes_track',
432 array('scoid' => $sco->id
, 'userid' => $userid, 'element' => 'objectiveprogressstatus'));
433 if ((!isset($r->value
) && !$checknot) ||
(isset($r->value
) && ($r->value
== $checknot))) {
439 case 'objectiveStatusKnown':
440 $r = $DB->get_record('scorm_scoes_track',
441 array('scoid' => $sco->id
, 'userid' => $userid, 'element' => 'objectiveprogressstatus'));
442 if ((!isset($r->value
) && !$checknot) ||
(isset($r->value
) && ($r->value
== $checknot))) {
447 case 'notobjectiveStatusKnown':
448 $r = $DB->get_record('scorm_scoes_track',
449 array('scoid' => $sco->id
, 'userid' => $userid, 'element' => 'objectiveprogressstatus'));
450 if ((!isset($r->value
) && !$checknot) ||
(isset($r->value
) && ($r->value
== $checknot))) {
455 case 'objectiveMeasureKnown':
456 $r = $DB->get_record('scorm_scoes_track',
457 array('scoid' => $sco->id
, 'userid' => $userid, 'element' => 'objectivemeasurestatus'));
458 if ((!isset($r->value
) && !$checknot) ||
(isset($r->value
) && ($r->value
== $checknot))) {
463 case 'notobjectiveMeasureKnown':
464 $r = $DB->get_record('scorm_scoes_track',
465 array('scoid' => $sco->id
, 'userid' => $userid, 'element' => 'objectivemeasurestatus'));
466 if ((!isset($r->value
) && !$checknot) ||
(isset($r->value
) && ($r->value
== $checknot))) {
472 $r = $DB->get_record('scorm_scoes_track',
473 array('scoid' => $sco->id
, 'userid' => $userid, 'element' => 'attemptcompletionstatus'));
474 if ((!isset($r->value
) && !$checknot) ||
(isset($r->value
) && ($r->value
== $checknot))) {
475 $r = $DB->get_record('scorm_scoes_track',
476 array('scoid' => $sco->id
, 'userid' => $userid, 'element' => 'attemptprogressstatus'));
477 if ((!isset($r->value
) && !$checknot) ||
(isset($r->value
) && ($r->value
== $checknot))) {
484 $attempt = $DB->get_field('scorm_scoes_track', 'attempt',
485 array('scoid' => $sco->id
, 'userid' => $userid, 'element' => 'x.start.time'));
486 if ($checknot && $attempt > 0) {
488 } else if (!$checknot && $attempt <= 0) {
493 case 'attemptLimitExceeded':
494 $r = $DB->get_record('scorm_scoes_track',
495 array('scoid' => $sco->id
, 'userid' => $userid, 'element' => 'activityprogressstatus'));
496 if ((!isset($r->value
) && !$checknot) ||
(isset($r->value
) && ($r->value
== $checknot))) {
497 $r = $DB->get_record('scorm_scoes_track',
498 array('scoid' => $sco->id
, 'userid' => $userid,
499 'element' => 'limitconditionattemptlimitcontrol'));
500 if ((!isset($r->value
) && !$checknot) ||
(isset($r->value
) && ($r->value
== $checknot))) {
501 if ($r = $DB->get_field('scorm_scoes_track', 'attempt', array('scoid' => $sco->id
, 'userid' => $userid)) &&
502 $r2 = $DB->get_record('scorm_scoes_track', array('scoid' => $sco->id
, 'userid' => $userid,
503 'element' => 'limitconditionattemptlimit')) ) {
505 if ($checknot && ($r->value
>= $r2->value
)) {
507 } else if (!$checknot && ($r->value
< $r2->value
)) {
515 case 'activityProgressKnown':
516 $r = $DB->get_record('scorm_scoes_track',
517 array('scoid' => $sco->id
, 'userid' => $userid, 'element' => 'activityprogressstatus'));
518 if ((!isset($r->value
) && !$checknot) ||
(isset($r->value
) && ($r->value
== $checknot))) {
519 $r = $DB->get_record('scorm_scoes_track',
520 array('scoid' => $sco->id
, 'userid' => $userid, 'element' => 'attemptprogressstatus'));
521 if ((!isset($r->value
) && !$checknot) ||
(isset($r->value
) && ($r->value
== $checknot))) {
528 if ($conditioncombination == 'all' && !$res) {
530 } else if ($conditioncombination == 'or' && $res) {
538 function scorm_check_activity ($activity, $userid) {
539 $act = scorm_seq_rules_check($activity, 'disabled');
543 if (scorm_limit_cond_check ($activity, $userid)) {
549 function scorm_limit_cond_check ($activity, $userid) {
552 if (isset($activity->tracked
) && ($activity->tracked
== 0)) {
556 if (scorm_seq_is('active', $activity->id
, $userid) ||
scorm_seq_is('suspended', $activity->id
, $userid)) {
560 if (!isset($activity->limitcontrol
) ||
($activity->limitcontrol
== 1)) {
561 $r = $DB->get_record('scorm_scoes_track',
562 array('scoid' => $activity->id
, 'userid' => $userid, 'element' => 'activityattemptcount'));
563 if (scorm_seq_is('activityprogressstatus', $activity->id
, $userid) && ($r->value
>= $activity->limitattempt
)) {
568 if (!isset($activity->limitabsdurcontrol
) ||
($activity->limitabsdurcontrol
== 1)) {
569 $r = $DB->get_record('scorm_scoes_track',
570 array('scoid' => $activity->id
, 'userid' => $userid, 'element' => 'activityabsoluteduration'));
571 if (scorm_seq_is('activityprogressstatus', $activity->id
, $userid) && ($r->value
>= $activity->limitabsduration
)) {
576 if (!isset($activity->limitexpdurcontrol
) ||
($activity->limitexpdurcontrol
== 1)) {
577 $r = $DB->get_record('scorm_scoes_track',
578 array('scoid' => $activity->id
, 'userid' => $userid, 'element' => 'activityexperiencedduration'));
579 if (scorm_seq_is('activityprogressstatus', $activity->id
, $userid) && ($r->value
>= $activity->limitexpduration
)) {
584 if (!isset($activity->limitattabsdurcontrol
) ||
($activity->limitattabsdurcontrol
== 1)) {
585 $r = $DB->get_record('scorm_scoes_track',
586 array('scoid' => $activity->id
, 'userid' => $userid, 'element' => 'attemptabsoluteduration'));
587 if (scorm_seq_is('activityprogressstatus', $activity->id
, $userid) && ($r->value
>= $activity->limitattabsduration
)) {
592 if (!isset($activity->limitattexpdurcontrol
) ||
($activity->limitattexpdurcontrol
== 1)) {
593 $r = $DB->get_record('scorm_scoes_track',
594 array('scoid' => $activity->id
, 'userid' => $userid, 'element' => 'attemptexperiencedduration'));
595 if (scorm_seq_is('activityprogressstatus', $activity->id
, $userid) && ($r->value
>= $activity->limitattexpduration
)) {
600 if (!isset($activity->limitbegincontrol
) ||
($activity->limitbegincontrol
== 1)) {
601 $r = $DB->get_record('scorm_scoes_track',
602 array('scoid' => $activity->id
, 'userid' => $userid, 'element' => 'begintime'));
603 if (isset($activity->limitbegintime
) && time() >= $activity->limitbegintime
) {
608 if (!isset($activity->limitbegincontrol
) ||
($activity->limitbegincontrol
== 1)) {
609 if (isset($activity->limitbegintime
) && time() < $activity->limitbegintime
) {
614 if (!isset($activity->limitendcontrol
) ||
($activity->limitendcontrol
== 1)) {
615 if (isset($activity->limitendtime
) && time() > $activity->limitendtime
) {
622 function scorm_seq_rules_check ($sco, $action) {
626 if ($rules = $DB->get_records('scorm_seq_ruleconds', array('scoid' => $sco->id
, 'action' => $action))) {
627 foreach ($rules as $rule) {
628 if ($act = scorm_seq_rule_check($sco, $rule)) {
637 function scorm_seq_rule_check ($sco, $rule) {
642 $ruleconds = $DB->get_records('scorm_seq_rulecond', array('scoid' => $sco->id
, 'ruleconditionsid' => $rule->id
));
643 foreach ($ruleconds as $rulecond) {
644 if ($rulecond->operator
== 'not') {
645 if ($rulecond->cond
!= 'unknown' ) {
646 $rulecond->cond
= 'not'.$rulecond->cond
;
649 $bag[] = $rulecond->cond
;
656 if ($rule->conditioncombination
== 'all') {
657 foreach ($bag as $con) {
658 $cond = $cond.' and '.$con;
661 foreach ($bag as $con) {
662 $cond = $cond.' or '.$con;
668 function scorm_seq_overall_rollup($sco, $userid, $seq) {
669 if ($ancestors = scorm_get_ancestors($sco)) {
670 foreach ($ancestors as $ancestor) {
671 if (!scorm_is_leaf($ancestor)) {
672 scorm_seq_measure_rollup($sco, $userid, $seq->attempt
);
674 scorm_seq_objective_rollup($sco, $userid, $seq->attempt
);
675 scorm_seq_activity_progress_rollup($sco, $userid, $seq);
680 function scorm_seq_measure_rollup($sco, $userid, $attempt = 0) {
683 $totalmeasure = 0; // Check if there is something similar in the database.
684 $valid = false; // Same as in the last line.
685 $countedmeasures = 0; // Same too.
686 $targetobjective = null;
687 $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id
));
689 foreach ($objectives as $objective) {
690 if ($objective->primaryobj
== true) { // Objective contributes to rollup.
691 $targetobjective = $objective;
696 if ($targetobjective != null) {
697 $children = scorm_get_children($sco);
698 if (!empty ($children)) {
699 foreach ($children as $child) {
700 $child = scorm_get_sco ($child);
701 if (!isset($child->tracked
) ||
($child->tracked
== 1)) {
702 $rolledupobjective = null;// We set the rolled up activity to undefined.
703 $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $child->id
));
704 foreach ($objectives as $objective) {
705 if ($objective->primaryobj
== true) {// Objective contributes to rollup I'm using primaryobj field, but not.
706 $rolledupobjective = $objective;
710 if ($rolledupobjective != null) {
711 $child = scorm_get_sco($child->id
);
712 $countedmeasures = $countedmeasures +
($child->measureweight
);
713 if (!scorm_seq_is('objectivemeasurestatus', $sco->id
, $userid, $attempt)) {
714 $normalizedmeasure = $DB->get_record('scorm_scoes_track',
715 array('scoid' => $child->id
, 'userid' => $userid, 'element' => 'objectivenormalizedmeasure'));
716 $totalmeasure = $totalmeasure +
(($normalizedmeasure->value
) * ($child->measureweight
));
725 scorm_seq_set('objectivemeasurestatus', $sco->id
, $userid, $attempt, false);
727 if ($countedmeasures > 0) {
728 scorm_seq_set('objectivemeasurestatus', $sco->id
, $userid, $attempt);
729 $val = $totalmeasure / $countedmeasures;
730 scorm_seq_set('objectivenormalizedmeasure', $sco->id
, $userid, $attempt, $val);
732 scorm_seq_set('objectivemeasurestatus', $sco->id
, $userid, $attempt, false);
738 function scorm_seq_objective_rollup($sco, $userid, $attempt = 0) {
741 scorm_seq_objective_rollup_measure($sco, $userid, $attempt);
742 scorm_seq_objective_rollup_rules($sco, $userid, $attempt);
743 scorm_seq_objective_rollup_default($sco, $userid, $attempt);
746 if ($targetobjective->satisfiedbymeasure) {
747 scorm_seq_objective_rollup_measure($sco, $userid);
750 if ((scorm_seq_rollup_rule_check($sco, $userid, 'incomplete'))
751 || (scorm_seq_rollup_rule_check($sco, $userid, 'completed'))) {
752 scorm_seq_objective_rollup_rules($sco, $userid);
756 $rolluprules = $DB->get_record('scorm_seq_rolluprule', array('scoid'=>$sco->id, 'userid'=>$userid));
757 foreach ($rolluprules as $rolluprule) {
758 $rollupruleconds = $DB->get_records('scorm_seq_rolluprulecond', array('rollupruleid'=>$rolluprule->id));
759 foreach ($rollupruleconds as $rolluprulecond) {
761 switch ($rolluprulecond->cond!='satisfied'
762 && $rolluprulecond->cond!='completed' && $rolluprulecond->cond!='attempted') {
764 scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, false);
776 function scorm_seq_objective_rollup_measure($sco, $userid, $attempt = 0) {
779 $targetobjective = null;
781 $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id
));
782 foreach ($objectives as $objective) {
783 if ($objective->primaryobj
== true) {
784 $targetobjective = $objective;
788 if ($targetobjective != null) {
789 if ($targetobjective->satisfiedbymeasure
) {
790 if (!scorm_seq_is('objectiveprogressstatus', $sco->id
, $userid, $attempt)) {
791 scorm_seq_set('objectiveprogressstatus', $sco->id
, $userid, $attempt, false);
793 if (scorm_seq_is('active', $sco->id
, $userid, $attempt)) {
799 $normalizedmeasure = $DB->get_record('scorm_scoes_track',
800 array('scoid' => $sco->id
, 'userid' => $userid, 'element' => 'objectivenormalizedmeasure'));
802 $sco = scorm_get_sco ($sco->id
);
804 if (!$isactive ||
($isactive &&
805 (!isset($sco->measuresatisfactionifactive
) ||
$sco->measuresatisfactionifactive
== true))) {
806 if (isset($normalizedmeasure->value
) && ($normalizedmeasure->value
>= $targetobjective->minnormalizedmeasure
)) {
807 scorm_seq_set('objectiveprogressstatus', $sco->id
, $userid, $attempt);
808 scorm_seq_set('objectivesatisfiedstatus', $sco->id
, $userid, $attempt);
810 // TODO: handle the case where cmi.success_status is passed and objectivenormalizedmeasure undefined.
811 scorm_seq_set('objectiveprogressstatus', $sco->id
, $userid, $attempt);
814 scorm_seq_set('objectiveprogressstatus', $sco->id
, $userid, $attempt, false);
821 function scorm_seq_objective_rollup_default($sco, $userid, $attempt = 0) {
824 if (!(scorm_seq_rollup_rule_check($sco, $userid, 'incomplete')) && !(scorm_seq_rollup_rule_check($sco, $userid, 'completed'))) {
825 if ($rolluprules = $DB->get_record('scorm_seq_rolluprule', array('scoid' => $sco->id
))) {
826 foreach ($rolluprules as $rolluprule) {
827 $rollupruleconds = $DB->get_records('scorm_seq_rolluprulecond', array('rollupruleid' => $rolluprule->id
));
828 foreach ($rollupruleconds as $rolluprulecond) {
829 if ($rolluprulecond->cond
!= 'satisfied' && $rolluprulecond->cond
!= 'completed' &&
830 $rolluprulecond->cond
!= 'attempted') {
831 scorm_seq_set('objectivesatisfiedstatus', $sco->id
, $userid, $attempt, false);
841 function scorm_seq_objective_rollup_rules($sco, $userid, $attempt = 0) {
844 $targetobjective = null;
846 $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id
));
847 foreach ($objectives as $objective) {
848 if ($objective->primaryobj
== true) {// Objective contributes to rollup I'm using primaryobj field, but not.
849 $targetobjective = $objective;
853 if ($targetobjective != null) {
855 if (scorm_seq_rollup_rule_check($sco, $userid, 'notsatisfied')) {// With not satisfied rollup for the activity.
856 scorm_seq_set('objectiveprogressstatus', $sco->id
, $userid, $attempt);
857 scorm_seq_set('objectivesatisfiedstatus', $sco->id
, $userid, $attempt, false);
859 if (scorm_seq_rollup_rule_check($sco, $userid, 'satisfied')) {// With satisfied rollup for the activity.
860 scorm_seq_set('objectiveprogressstatus', $sco->id
, $userid, $attempt);
861 scorm_seq_set('objectivesatisfiedstatus', $sco->id
, $userid, $attempt);
868 function scorm_seq_activity_progress_rollup ($sco, $userid, $seq) {
870 if (scorm_seq_rollup_rule_check($sco, $userid, 'incomplete')) {
871 // Incomplete rollup action.
872 scorm_seq_set('attemptcompletionstatus', $sco->id
, $userid, $seq->attempt
, false);
873 scorm_seq_set('attemptprogressstatus', $sco->id
, $userid, $seq->attempt
, true);
876 if (scorm_seq_rollup_rule_check($sco, $userid, 'completed')) {
877 // Incomplete rollup action.
878 scorm_seq_set('attemptcompletionstatus', $sco->id
, $userid, $seq->attempt
, true);
879 scorm_seq_set('attemptprogressstatus', $sco->id
, $userid, $seq->attempt
, true);
884 function scorm_seq_rollup_rule_check ($sco, $userid, $action) {
887 if ($rolluprules = $DB->get_record('scorm_seq_rolluprule', array('scoid' => $sco->id
, 'action' => $action))) {
888 $childrenbag = Array ();
889 $children = scorm_get_children ($sco);
891 foreach ($rolluprules as $rolluprule) {
892 foreach ($children as $child) {
894 /*$tracked = $DB->get_records('scorm_scoes_track', array('scoid'=>$child->id, 'userid'=>$userid));
895 if ($tracked && $tracked->attemp != 0) {*/
896 $child = scorm_get_sco ($child);
897 if (!isset($child->tracked
) ||
($child->tracked
== 1)) {
898 if (scorm_seq_check_child ($child, $action, $userid)) {
899 $rollupruleconds = $DB->get_records('scorm_seq_rolluprulecond', array('rollupruleid' => $rolluprule->id
));
900 $evaluate = scorm_seq_evaluate_rollupcond($child, $rolluprule->conditioncombination
,
901 $rollupruleconds, $userid);
902 if ($evaluate == 'unknown') {
903 array_push($childrenbag, 'unknown');
905 if ($evaluate == true) {
906 array_push($childrenbag, true);
908 array_push($childrenbag, false);
917 switch ($rolluprule->childactivityset
) {
920 // I think I can use this condition instead equivalent to OR.
921 if ((array_search(false, $childrenbag) === false) && (array_search('unknown', $childrenbag) === false)) {
927 // I think I can use this condition instead equivalent to OR.
928 if (array_search(true, $childrenbag) !== false) {
934 // I think I can use this condition instead equivalent to OR.
935 if ((array_search(true, $childrenbag) === false) && (array_search('unknown', $childrenbag) === false)) {
941 // I think I can use this condition instead equivalent to OR.
942 foreach ($childrenbag as $itm) {
947 if ($cont >= $rolluprule->minimumcount
) {
954 foreach ($childrenbag as $itm) {// I think I can use this condition instead equivalent to OR.
959 if ($cont >= $rolluprule->minimumcount
) {
965 case 'atleastpercent':
966 foreach ($childrenbag as $itm) {// I think I can use this condition instead equivalent to OR.
971 if (($cont / count($childrenbag)) >= $rolluprule->minimumcount
) {
977 if ($change == true) {
985 function scorm_seq_flow_tree_traversal($activity, $direction, $childrenflag, $prevdirection, $seq, $userid, $skip = false) {
986 $revdirection = false;
987 $parent = scorm_get_parent($activity);
988 if (!empty($parent)) {
989 $children = scorm_get_available_children($parent);
993 $childrensize = count($children);
995 if (($prevdirection != null && $prevdirection == 'backward') && ($children[$childrensize - 1]->id
== $activity->id
)) {
996 $direction = 'backward';
997 $activity = $children[0];
998 $revdirection = true;
1001 if ($direction == 'forward') {
1002 $ancestors = scorm_get_ancestors($activity);
1003 $ancestorsroot = array_reverse($ancestors);
1004 $preorder = array();
1005 $preorder = scorm_get_preorder($preorder, $ancestorsroot[0]);
1006 $preordersize = count($preorder);
1007 if (($activity->id
== $preorder[$preordersize - 1]->id
) ||
(($activity->parent
== '/') && !($childrenflag))) {
1008 $seq->endsession
= true;
1009 $seq->nextactivity
= null;
1012 if (scorm_is_leaf ($activity) ||
!$childrenflag) {
1013 if ($children[$childrensize - 1]->id
== $activity->id
) {
1014 $seq = scorm_seq_flow_tree_traversal ($parent, $direction, false, null, $seq, $userid);
1015 if ($seq->nextactivity
->launch
== null) {
1016 $seq = scorm_seq_flow_tree_traversal ($seq->nextactivity
, $direction, true, null, $seq, $userid);
1021 foreach ($children as $sco) {
1022 if ($sco->id
== $activity->id
) {
1027 if ($position != ($childrensize - 1)) {
1028 $seq->nextactivity
= $children[$position +
1];
1029 $seq->traversaldir
= $direction;
1032 $siblings = scorm_get_siblings($activity);
1033 $children = scorm_get_children($siblings[0]);
1034 $seq->nextactivity
= $children[0];
1039 $children = scorm_get_available_children($activity);
1040 if (!empty($children)) {
1041 $seq->traversaldir
= $direction;
1042 $seq->nextactivity
= $children[0];
1045 $seq->traversaldir
= null;
1046 $seq->nextactivity
= null;
1047 $seq->exception
= 'SB.2.1-2';
1051 } else if ($direction == 'backward') {
1052 if ($activity->parent
== '/') {
1053 $seq->traversaldir
= null;
1054 $seq->nextactivity
= null;
1055 $seq->exception
= 'SB.2.1-3';
1058 if (scorm_is_leaf ($activity) ||
!$childrenflag) {
1059 if (!$revdirection) {
1060 if (isset($parent->forwardonly
) && ($parent->forwardonly
== true && !$skip)) {
1061 $seq->traversaldir
= null;
1062 $seq->nextactivity
= null;
1063 $seq->exception
= 'SB.2.1-4';
1067 if ($children[0]->id
== $activity->id
) {
1068 $seq = scorm_seq_flow_tree_traversal($parent, 'backward', false, null, $seq, $userid);
1071 $ancestors = scorm_get_ancestors($activity);
1072 $ancestorsroot = array_reverse($ancestors);
1073 $preorder = array();
1074 $preorder = scorm_get_preorder($preorder, $ancestorsroot[0]);
1076 foreach ($preorder as $sco) {
1077 if ($sco->id
== $activity->id
) {
1082 if (isset($preorder[$position])) {
1083 $seq->nextactivity
= $preorder[$position - 1];
1084 $seq->traversaldir
= $direction;
1089 $children = scorm_get_available_children($activity);
1090 if (!empty($children)) {
1091 if (isset($parent->flow
) && ($parent->flow
== true)) {
1092 $seq->traversaldir
= 'forward';
1093 $seq->nextactivity
= $children[0];
1096 $seq->traversaldir
= 'backward';
1097 $seq->nextactivity
= $children[count($children) - 1];
1101 $seq->traversaldir
= null;
1102 $seq->nextactivity
= null;
1103 $seq->exception
= 'SB.2.1-2';
1110 // Returns the next activity on the tree, traversal direction, control returned to the LTS, (may) exception.
1111 function scorm_seq_flow_activity_traversal ($activity, $userid, $direction, $childrenflag, $prevdirection, $seq) {
1112 $parent = scorm_get_parent ($activity);
1113 if (!isset($parent->flow
) ||
($parent->flow
== false)) {
1114 $seq->deliverable
= false;
1115 $seq->exception
= 'SB.2.2-1';
1116 $seq->nextactivity
= $activity;
1120 $rulecheck = scorm_seq_rules_check($activity, 'skip');
1121 if ($rulecheck != null) {
1122 $skip = scorm_evaluate_condition ($rulecheck, $activity, $userid);
1124 $seq = scorm_seq_flow_tree_traversal($activity, $direction, false, $prevdirection, $seq, $userid, $skip);
1125 $seq = scorm_seq_flow_activity_traversal($seq->nextactivity
, $userid, $direction,
1126 $childrenflag, $prevdirection, $seq);
1127 } else if (!empty($seq->identifiedactivity
)) {
1128 $seq->nextactivity
= $activity;
1133 $ch = scorm_check_activity ($activity, $userid);
1135 $seq->deliverable
= false;
1136 $seq->exception
= 'SB.2.2-2';
1137 $seq->nextactivity
= $activity;
1141 if (!scorm_is_leaf($activity)) {
1142 $seq = scorm_seq_flow_tree_traversal ($activity, $direction, true, null, $seq, $userid);
1143 if ($seq->identifiedactivity
== null) {
1144 $seq->deliverable
= false;
1145 $seq->nextactivity
= $activity;
1148 if ($direction == 'backward' && $seq->traversaldir
== 'forward') {
1149 $seq = scorm_seq_flow_activity_traversal($seq->identifiedactivity
, $userid,
1150 'forward', $childrenflag, 'backward', $seq);
1152 $seq = scorm_seq_flow_activity_traversal($seq->identifiedactivity
, $userid,
1153 $direction, $childrenflag, null, $seq);
1160 $seq->deliverable
= true;
1161 $seq->nextactivity
= $activity;
1162 $seq->exception
= null;
1167 function scorm_seq_flow ($activity, $direction, $seq, $childrenflag, $userid) {
1168 // TODO: $PREVDIRECTION NOT DEFINED YET.
1169 $prevdirection = null;
1170 $seq = scorm_seq_flow_tree_traversal ($activity, $direction, $childrenflag, $prevdirection, $seq, $userid);
1171 if ($seq->nextactivity
== null) {
1172 $seq->nextactivity
= $activity;
1173 $seq->deliverable
= false;
1176 $activity = $seq->nextactivity
;
1177 $seq = scorm_seq_flow_activity_traversal($activity, $userid, $direction, $childrenflag, null, $seq);
1183 * Sets up $userdata array and default values for SCORM 1.3 .
1185 * @param stdClass $userdata an empty stdClass variable that should be set up with user values
1186 * @param object $scorm package record
1187 * @param string $scoid SCO Id
1188 * @param string $attempt attempt number for the user
1189 * @param string $mode scorm display mode type
1190 * @return array The default values that should be used for SCORM 1.3 package
1192 function get_scorm_default (&$userdata, $scorm, $scoid, $attempt, $mode) {
1195 $userdata->student_id
= $USER->username
;
1196 if (empty(get_config('scorm', 'scormstandard'))) {
1197 $userdata->student_name
= fullname($USER);
1199 $userdata->student_name
= $USER->lastname
.', '. $USER->firstname
;
1202 if ($usertrack = scorm_get_tracks($scoid, $USER->id
, $attempt)) {
1203 // According to SCORM 2004(RTE V1, 4.2.8), only cmi.exit==suspend should allow previous datamodel elements on re-launch.
1204 if (isset($usertrack->{'cmi.exit'}) && ($usertrack->{'cmi.exit'} == 'suspend')) {
1205 foreach ($usertrack as $key => $value) {
1206 $userdata->$key = $value;
1209 $userdata->status
= '';
1210 $userdata->score_raw
= '';
1213 $userdata->status
= '';
1214 $userdata->score_raw
= '';
1217 if ($scodatas = scorm_get_sco($scoid, SCO_DATA
)) {
1218 foreach ($scodatas as $key => $value) {
1219 $userdata->$key = $value;
1222 print_error('cannotfindsco', 'scorm');
1224 if (!$sco = scorm_get_sco($scoid)) {
1225 print_error('cannotfindsco', 'scorm');
1228 if (isset($userdata->status
)) {
1229 if (!isset($userdata->{'cmi.exit'}) ||
$userdata->{'cmi.exit'} == 'time-out' ||
$userdata->{'cmi.exit'} == 'normal') {
1230 $userdata->entry
= 'ab-initio';
1232 if (isset($userdata->{'cmi.exit'}) && ($userdata->{'cmi.exit'} == 'suspend' ||
$userdata->{'cmi.exit'} == 'logout')) {
1233 $userdata->entry
= 'resume';
1235 $userdata->entry
= '';
1240 $userdata->mode
= 'normal';
1241 if (!empty($mode)) {
1242 $userdata->mode
= $mode;
1244 if ($userdata->mode
== 'normal') {
1245 $userdata->credit
= 'credit';
1247 $userdata->credit
= 'no-credit';
1250 $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $scoid));
1252 foreach ($objectives as $objective) {
1253 if (!empty($objective->minnormalizedmeasure
)) {
1254 $userdata->{'cmi.scaled_passing_score'} = $objective->minnormalizedmeasure
;
1256 if (!empty($objective->objectiveid
)) {
1257 $userdata->{'cmi.objectives.N'.$index.'.id'} = $objective->objectiveid
;
1263 $def['cmi.learner_id'] = $userdata->student_id
;
1264 $def['cmi.learner_name'] = $userdata->student_name
;
1265 $def['cmi.mode'] = $userdata->mode
;
1266 $def['cmi.entry'] = $userdata->entry
;
1267 $def['cmi.exit'] = scorm_isset($userdata, 'cmi.exit');
1268 $def['cmi.credit'] = scorm_isset($userdata, 'credit');
1269 $def['cmi.completion_status'] = scorm_isset($userdata, 'cmi.completion_status', 'unknown');
1270 $def['cmi.completion_threshold'] = scorm_isset($userdata, 'threshold');
1271 $def['cmi.learner_preference.audio_level'] = scorm_isset($userdata, 'cmi.learner_preference.audio_level', 1);
1272 $def['cmi.learner_preference.language'] = scorm_isset($userdata, 'cmi.learner_preference.language');
1273 $def['cmi.learner_preference.delivery_speed'] = scorm_isset($userdata, 'cmi.learner_preference.delivery_speed');
1274 $def['cmi.learner_preference.audio_captioning'] = scorm_isset($userdata, 'cmi.learner_preference.audio_captioning', 0);
1275 $def['cmi.location'] = scorm_isset($userdata, 'cmi.location');
1276 $def['cmi.max_time_allowed'] = scorm_isset($userdata, 'attemptAbsoluteDurationLimit');
1277 $def['cmi.progress_measure'] = scorm_isset($userdata, 'cmi.progress_measure');
1278 $def['cmi.scaled_passing_score'] = scorm_isset($userdata, 'cmi.scaled_passing_score');
1279 $def['cmi.score.scaled'] = scorm_isset($userdata, 'cmi.score.scaled');
1280 $def['cmi.score.raw'] = scorm_isset($userdata, 'cmi.score.raw');
1281 $def['cmi.score.min'] = scorm_isset($userdata, 'cmi.score.min');
1282 $def['cmi.score.max'] = scorm_isset($userdata, 'cmi.score.max');
1283 $def['cmi.success_status'] = scorm_isset($userdata, 'cmi.success_status', 'unknown');
1284 $def['cmi.suspend_data'] = scorm_isset($userdata, 'cmi.suspend_data');
1285 $def['cmi.time_limit_action'] = scorm_isset($userdata, 'timelimitaction');
1286 $def['cmi.total_time'] = scorm_isset($userdata, 'cmi.total_time', 'PT0H0M0S');
1287 $def['cmi.launch_data'] = scorm_isset($userdata, 'datafromlms');