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
17function scorm_seq_exit_action_rules($seq, $userid) {
18    $sco = $seq->currentactivity;
19    $ancestors = scorm_get_ancestors($sco);
20    $exittarget = null;
21    foreach (array_reverse($ancestors) as $ancestor) {
22        if (scorm_seq_rules_check($ancestor, 'exit') != null) {
23            $exittarget = $ancestor;
24            break;
25        }
26    }
27    if ($exittarget != null) {
28        $commons = array_slice($ancestors, 0, scorm_find_common_ancestor($ancestors, $exittarget));
29
30        // Terminate Descendent Attempts Process.
31        if ($commons) {
32            foreach ($commons as $ancestor) {
33                scorm_seq_end_attempt($ancestor, $userid, $seq->attempt);
34                $seq->currentactivity = $ancestor;
35            }
36        }
37    }
38    return $seq;
39}
40
41function scorm_seq_post_cond_rules($seq, $userid) {
42    $sco = $seq->currentactivity;
43    if (!$seq->suspended) {
44        if ($action = scorm_seq_rules_check($sco, 'post') != null) {
45            switch($action) {
46                case 'retry':
47                case 'continue':
48                case 'previous':
49                    $seq->sequencing = $action;
50                break;
51                case 'exitparent':
52                case 'exitall':
53                    $seq->termination = $action;
54                break;
55                case 'retryall':
56                    $seq->termination = 'exitall';
57                    $seq->sequencing = 'retry';
58                break;
59            }
60        }
61    }
62    return $seq;
63}
64
65
66function scorm_seq_evaluate_rollupcond($sco, $conditioncombination, $rollupruleconds, $userid) {
67    $bag = Array();
68    $con = "";
69    $val = false;
70    $unk = false;
71    foreach ($rollupruleconds as $rolluprulecond) {
72        $condit = scorm_evaluate_condition($rolluprulecond, $sco, $userid);
73        if ($rolluprulecond->operator == 'not') { // If operator is not, negate the condition.
74            if ($rolluprulecond->cond != 'unknown') {
75                if ($condit) {
76                    $condit = false;
77                } else {
78                    $condit = true;
79                }
80            } else {
81                $condit = 'unknown';
82            }
83            array_push($childrenbag, $condit);
84        }
85    }
86    if (empty($bag)) {
87        return 'unknown';
88    } else {
89        $i = 0;
90        foreach ($bag as $b) {
91            if ($rolluprulecond->conditioncombination == 'all') {
92                $val = true;
93                if ($b == 'unknown') {
94                    $unk = true;
95                }
96                if ($b === false) {
97                    return false;
98                }
99            } else {
100                $val = false;
101
102                if ($b == 'unknown') {
103                    $unk = true;
104                }
105                if ($b === true) {
106                    return true;
107                }
108            }
109        }
110    }
111    if ($unk) {
112        return 'unknown';
113    }
114    return $val;
115}
116
117function scorm_seq_check_child ($sco, $action, $userid) {
118    global $DB;
119
120    $included = false;
121    $sco = scorm_get_sco($sco->id);
122    $r = $DB->get_record('scorm_scoes_track', array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'activityattemptcount'));
123    if ($action == 'satisfied' || $action == 'notsatisfied') {
124        if (!$sco->rollupobjectivesatisfied) {
125            $included = true;
126            if (($action == 'satisfied' && $sco->requiredforsatisfied == 'ifnotsuspended') ||
127                ($action == 'notsatisfied' && $sco->requiredfornotsatisfied == 'ifnotsuspended')) {
128
129                if (!scorm_seq_is('activityprogressstatus', $sco->id, $userid) ||
130                    ((($r->value) > 0) && !scorm_seq_is('suspended', $sco->id, $userid))) {
131                    $included = false;
132                }
133
134            } else {
135                if (($action == 'satisfied' && $sco->requiredforsatisfied == 'ifattempted') ||
136                    ($action == 'notsatisfied' && $sco->requiredfornotsatisfied == 'ifattempted')) {
137                    if (!scorm_seq_is('activityprogressstatus', $sco->id, $userid) || (($r->value) == 0)) {
138                        $included = false;
139                    }
140                } else {
141                    if (($action == 'satisfied' && $sco->requiredforsatisfied == 'ifnotskipped') ||
142                        ($action == 'notsatisfied' && $sco->requiredfornotsatisfied == 'ifnotskipped')) {
143                        $rulch = scorm_seq_rules_check($sco, 'skip');
144                        if ($rulch != null) {
145                            $included = false;
146                        }
147                    }
148                }
149            }
150        }
151    }
152    if ($action == 'completed' || $action == 'incomplete') {
153        if (!$sco->rollupprogresscompletion) {
154            $included = true;
155
156            if (($action == 'completed' && $sco->requiredforcompleted == 'ifnotsuspended') ||
157                ($action == 'incomplete' && $sco->requiredforincomplete == 'ifnotsuspended')) {
158
159                if (!scorm_seq_is('activityprogressstatus', $sco->id, $userid) ||
160                    ((($r->value) > 0)&& !scorm_seq_is('suspended', $sco->id, $userid))) {
161                    $included = false;
162                }
163
164            } else {
165
166                if (($action == 'completed' && $sco->requiredforcompleted == 'ifattempted') ||
167                    ($action == 'incomplete' && $sco->requiredforincomplete == 'ifattempted')) {
168                    if (!scorm_seq_is('activityprogressstatus', $sco->id, $userid) || (($r->value) == 0)) {
169                        $included = false;
170                    }
171
172                } else {
173                    if (($action == 'completed' && $sco->requiredforsatisfied == 'ifnotskipped') ||
174                        ($action == 'incomplete' && $sco->requiredfornotsatisfied == 'ifnotskipped')) {
175                        $rulch = scorm_seq_rules_check($sco, 'skip');
176                        if ($rulch != null) {
177                            $included = false;
178                        }
179                    }
180                }
181            }
182        }
183    }
184    return $included;
185}
186
187function scorm_seq_sequencing ($scoid, $userid, $seq) {
188
189    switch ($seq->sequencing) {
190        case 'start':
191            // We'll see the parameters we have to send, this should update delivery and end.
192            $seq = scorm_seq_start_sequencing($scoid, $userid, $seq);
193            $seq->sequencing = true;
194            break;
195
196        case 'resumeall':
197            // We'll see the parameters we have to send, this should update delivery and end.
198            $seq = scorm_seq_resume_all_sequencing($scoid, $userid, $seq);
199            $seq->sequencing = true;
200            break;
201
202        case 'exit':
203            // We'll see the parameters we have to send, this should update delivery and end.
204            $seq = scorm_seq_exit_sequencing($scoid, $userid, $seq);
205            $seq->sequencing = true;
206            break;
207
208        case 'retry':
209            // We'll see the parameters we have to send, this should update delivery and end.
210            $seq = scorm_seq_retry_sequencing($scoid, $userid, $seq);
211            $seq->sequencing = true;
212            break;
213
214        case 'previous':
215            // We'll see the parameters we have to send, this should update delivery and end.
216            $seq = scorm_seq_previous_sequencing($scoid, $userid, $seq);
217            $seq->sequencing = true;
218            break;
219
220        case 'choice':
221            // We'll see the parameters we have to send, this should update delivery and end.
222            $seq = scorm_seq_choice_sequencing($scoid, $userid, $seq);
223            $seq->sequencing = true;
224            break;
225    }
226
227    if ($seq->exception != null) {
228        $seq->sequencing = false;
229        return $seq;
230    }
231
232    $seq->sequencing = true;
233    return $seq;
234}
235
236function scorm_seq_start_sequencing($scoid, $userid, $seq) {
237    global $DB;
238
239    if (!empty($seq->currentactivity)) {
240        $seq->delivery = null;
241        $seq->exception = 'SB.2.5-1';
242        return $seq;
243    }
244    $sco = $DB->get_record('scorm_scoes', array('scoid' => $scoid, 'userid' => $userid));
245    if (($sco->parent == '/') && scorm_is_leaf($sco)) { // If the activity is the root and is leaf.
246        $seq->delivery = $sco;
247    } else {
248        $ancestors = scorm_get_ancestors($sco);
249        $ancestorsroot = array_reverse($ancestors);
250        $res = scorm_seq_flow($ancestorsroot[0], 'forward', $seq, true, $userid);
251        if ($res) {
252            return $res;
253        }
254    }
255}
256
257function scorm_seq_resume_all_sequencing($scoid, $userid, $seq) {
258    global $DB;
259
260    if (!empty($seq->currentactivity)) {
261        $seq->delivery = null;
262        $seq->exception = 'SB.2.6-1';
263        return $seq;
264    }
265    $track = $DB->get_record('scorm_scoes_track', array('scoid' => $scoid, 'userid' => $userid, 'element' => 'suspendedactivity'));
266    if (!$track) {
267        $seq->delivery = null;
268        $seq->exception = 'SB.2.6-2';
269        return $seq;
270    }
271    // We assign the sco to the delivery.
272    $seq->delivery = $DB->get_record('scorm_scoes', array('scoid' => $scoid, 'userid' => $userid));
273}
274
275function scorm_seq_continue_sequencing($scoid, $userid, $seq) {
276    if (empty($seq->currentactivity)) {
277        $seq->delivery = null;
278        $seq->exception = 'SB.2.7-1';
279        return $seq;
280    }
281    $currentact = $seq->currentactivity;
282    if ($currentact->parent != '/') {
283        // If the activity is the root and is leaf.
284        $parent = scorm_get_parent ($currentact);
285
286        if (!isset($parent->flow) || ($parent->flow == false)) {
287            $seq->delivery = null;
288            $seq->exception = 'SB.2.7-2';
289            return $seq;
290        }
291
292        $res = scorm_seq_flow($currentact, 'forward', $seq, false, $userid);
293        if ($res) {
294            return $res;
295        }
296    }
297}
298
299function scorm_seq_previous_sequencing($scoid, $userid, $seq) {
300    if (empty($seq->currentactivity)) {
301        $seq->delivery = null;
302        $seq->exception = 'SB.2.8-1';
303        return $seq;
304    }
305
306    $currentact = $seq->currentactivity;
307    if ($currentact->parent != '/') { // If the activity is the root and is leaf.
308        $parent = scorm_get_parent ($currentact);
309        if (!isset($parent->flow) || ($parent->flow == false)) {
310            $seq->delivery = null;
311            $seq->exception = 'SB.2.8-2';
312            return $seq;
313        }
314
315        $res = scorm_seq_flow($currentact, 'backward', $seq, false, $userid);
316        if ($res) {
317            return $res;
318        }
319    }
320}
321
322function scorm_seq_exit_sequencing($scoid, $userid, $seq) {
323    if (empty($seq->currentactivity)) {
324        $seq->delivery = null;
325        $seq->exception = 'SB.2.11-1';
326        return $seq;
327    }
328
329    if ($seq->active) {
330        $seq->endsession = false;
331        $seq->exception = 'SB.2.11-2';
332        return $seq;
333    }
334    $currentact = $seq->currentactivity;
335    if ($currentact->parent == '/') {
336        $seq->endsession = true;
337        return $seq;
338    }
339
340    $seq->endsession = false;
341    return $seq;
342}
343
344function scorm_seq_retry_sequencing($scoid, $userid, $seq) {
345    if (empty($seq->currentactivity)) {
346        $seq->delivery = null;
347        $seq->exception = 'SB.2.10-1';
348        return $seq;
349    }
350    if ($seq->active || $seq->suspended) {
351        $seq->delivery = null;
352        $seq->exception = 'SB.2.10-2';
353        return $seq;
354    }
355
356    if (!scorm_is_leaf($seq->currentactivity)) {
357        $res = scorm_seq_flow($seq->currentactivity, 'forward', $seq, true, $userid);
358        if ($res != null) {
359            return $res;
360        } else {
361            // Return deliver.
362            $seq->delivery = null;
363            $seq->exception = 'SB.2.10-3';
364            return $seq;
365        }
366    } else {
367        $seq->delivery = $seq->currentactivity;
368        return $seq;
369    }
370
371}
372
373function scorm_seq_choice_sequencing($sco, $userid, $seq) {
374
375    $avchildren = Array ();
376    $comancestor = null;
377    $traverse = null;
378
379    if ($sco == null) {
380        $seq->delivery = null;
381        $seq->exception = 'SB.2.9-1';
382        return $seq;
383    }
384
385    $ancestors = scorm_get_ancestors($sco);
386    $arrpath = array_reverse($ancestors);
387    array_push ($arrpath, $sco); // Path from the root to the target.
388
389    foreach ($arrpath as $activity) {
390        if ($activity->parent != '/') {
391            $avchildren = scorm_get_available_children (scorm_get_parent($activity));
392            $position = array_search($avchildren, $activity);
393            if ($position !== false) {
394                $seq->delivery = null;
395                $seq->exception = 'SB.2.9-2';
396                return $seq;
397            }
398        }
399
400        if (scorm_seq_rules_check($activity, 'hidefromchoice' != null)) {
401            $seq->delivery = null;
402            $seq->exception = 'SB.2.9-3';
403            return $seq;
404        }
405    }
406
407    if ($sco->parent != '/') {
408        $parent = scorm_sco_get_parent ($sco);
409        if ( isset($parent->choice) && ($parent->choice == false)) {
410            $seq->delivery = null;
411            $seq->exception = 'SB.2.9-4';
412            return $seq;
413        }
414    }
415
416    if ($seq->currentactivity != null) {
417        $commonpos = scorm_find_common_ancestor($ancestors, $seq->currentactivity);
418        $comancestor = $arrpath [$commonpos];
419    } else {
420        $comancestor = $arrpath [0];
421    }
422
423    if ($seq->currentactivity === $sco) {
424        // MDL-51757 - this part of the SCORM 2004 sequencing and navigation was not completed.
425        throw new \coding_exception('Unexpected state encountered');
426    }
427
428    $sib = scorm_get_siblings($seq->currentactivity);
429    $pos = array_search($sib, $sco);
430
431    if ($pos !== false) {
432        $siblings = array_slice($sib, 0, $pos - 1);
433        if (empty($siblings)) {
434            $seq->delivery = null;
435            $seq->exception = 'SB.2.9-5';
436            return $seq;
437        }
438
439        $children = scorm_get_children (scorm_get_parent ($sco));
440        $pos1 = array_search($children, $sco);
441        $pos2 = array_search($seq->currentactivity, $sco);
442        if ($pos1 > $pos2) {
443            $traverse = 'forward';
444        } else {
445            $traverse = 'backward';
446        }
447
448        foreach ($siblings as $sibling) {
449            $seq = scorm_seq_choice_activity_traversal($sibling, $userid, $seq, $traverse);
450            if (!$seq->reachable) {
451                $seq->delivery = null;
452                return $seq;
453            }
454        }
455        // MDL-51757 - this part of the SCORM 2004 sequencing and navigation was not completed.
456        throw new \coding_exception('Unexpected state encountered');
457    }
458
459    if ($seq->currentactivity == null || $seq->currentactivity == $comancestor) {
460        $commonpos = scorm_find_common_ancestor($ancestors, $seq->currentactivity);
461        // Path from the common ancestor to the target activity.
462        $comtarget = array_slice($ancestors, 1, $commonpos - 1);
463        $comtarget = array_reverse($comtarget);
464
465        if (empty($comtarget)) {
466            $seq->delivery = null;
467            $seq->exception = 'SB.2.9-5';
468            return $seq;
469        }
470        foreach ($comtarget as $act) {
471            $seq = scorm_seq_choice_activity_traversal($act, $userid, $seq, 'forward');
472            if (!$seq->reachable) {
473                $seq->delivery = null;
474                return $seq;
475            }
476            $act = scorm_get_sco ($acti->id);
477            if (scorm_seq_is('active', $act->id, $userid) && ($act->id != $comancestor->id && $act->preventactivation)) {
478                $seq->delivery = null;
479                $seq->exception = 'SB.2.9-6';
480                return $seq;
481            }
482        }
483        // MDL-51757 - this part of the SCORM 2004 sequencing and navigation was not completed.
484        throw new \coding_exception('Unexpected state encountered');
485    }
486
487    if ($comancestor->id == $sco->id) {
488
489        $ancestorscurrent = scorm_get_ancestors($seq->currentactivity);
490        $possco = array_search($ancestorscurrent, $sco);
491        // Path from the current activity to the target.
492        $curtarget = array_slice($ancestorscurrent, 0, $possco);
493
494        if (empty($curtarget)) {
495            $seq->delivery = null;
496            $seq->exception = 'SB.2.9-5';
497            return $seq;
498        }
499        $i = 0;
500        foreach ($curtarget as $activ) {
501            $i++;
502            if ($i != count($curtarget)) {
503                if (isset($activ->choiceexit) && ($activ->choiceexit == false)) {
504                    $seq->delivery = null;
505                    $seq->exception = 'SB.2.9-7';
506                    return $seq;
507                }
508            }
509        }
510        // MDL-51757 - this part of the SCORM 2004 sequencing and navigation was not completed.
511        throw new \coding_exception('Unexpected state encountered');
512    }
513
514    if (array_search($ancestors, $comancestor) !== false) {
515        $ancestorscurrent = scorm_get_ancestors($seq->currentactivity);
516        $commonpos = scorm_find_common_ancestor($ancestors, $sco);
517        $curcommon = array_slice($ancestorscurrent, 0, $commonpos - 1);
518        if (empty($curcommon)) {
519            $seq->delivery = null;
520            $seq->exception = 'SB.2.9-5';
521            return $seq;
522        }
523
524        $constrained = null;
525        foreach ($curcommon as $acti) {
526            $acti = scorm_get_sco($acti->id);
527            if (isset($acti->choiceexit) && ($acti->choiceexit == false)) {
528                    $seq->delivery = null;
529                    $seq->exception = 'SB.2.9-7';
530                    return $seq;
531            }
532            if ($constrained == null) {
533                if ($acti->constrainchoice == true) {
534                    $constrained = $acti;
535                }
536            }
537        }
538        if ($constrained != null) {
539            $fwdir = scorm_get_preorder($constrained);
540
541            if (array_search($fwdir, $sco) !== false) {
542                $traverse = 'forward';
543            } else {
544                $traverse = 'backward';
545            }
546            $seq = scorm_seq_choice_flow($constrained, $traverse, $seq);
547            $actconsider = $seq->identifiedactivity;
548            $avdescendents = Array();
549            $avdescendents = scorm_get_available_descendents($actconsider);
550            if (array_search ($avdescendents, $sco) !== false && $sco->id != $actconsider->id && $constrained->id != $sco->id ) {
551                $seq->delivery = null;
552                $seq->exception = 'SB.2.9-8';
553                return $seq;
554            }
555            // CONTINUE 11.5.5 !
556        }
557
558        $commonpos = scorm_find_common_ancestor($ancestors, $seq->currentactivity);
559        $comtarget = array_slice($ancestors, 1, $commonpos - 1);// Path from the common ancestor to the target activity.
560        $comtarget = array_reverse($comtarget);
561
562        if (empty($comtarget)) {
563            $seq->delivery = null;
564            $seq->exception = 'SB.2.9-5';
565            return $seq;
566        }
567
568        $fwdir = scorm_get_preorder($seq->currentactivity);
569
570        if (array_search($fwdir, $sco) !== false) {
571            foreach ($comtarget as $act) {
572                $seq = scorm_seq_choice_activity_traversal($act, $userid, $seq, 'forward');
573                if (!$seq->reachable) {
574                    $seq->delivery = null;
575                    return $seq;
576                }
577                $act = scorm_get_sco($act->id);
578                if (scorm_seq_is('active', $act->id, $userid) && ($act->id != $comancestor->id &&
579                        ($act->preventactivation == true))) {
580                    $seq->delivery = null;
581                    $seq->exception = 'SB.2.9-6';
582                    return $seq;
583                }
584            }
585
586        } else {
587            foreach ($comtarget as $act) {
588                $act = scorm_get_sco($act->id);
589                if (scorm_seq_is('active', $act->id, $userid) && $act->id != $comancestor->id && $act->preventactivation == true) {
590                    $seq->delivery = null;
591                    $seq->exception = 'SB.2.9-6';
592                    return $seq;
593                }
594            }
595        }
596        // MDL-51757 - this part of the SCORM 2004 sequencing and navigation was not completed.
597        throw new \coding_exception('Unexpected state encountered');
598    }
599
600    if (scorm_is_leaf ($sco)) {
601        $seq->delivery = $sco;
602        $seq->exception = 'SB.2.9-6';
603        return $seq;
604    }
605
606    $seq = scorm_seq_flow($sco, 'forward', $seq, true, $userid);
607    if ($seq->deliverable == false) {
608        scorm_terminate_descendent_attempts($comancestor, $userid, $seq);
609        scorm_seq_end_attempt($comancestor, $userid, $seq->attempt);
610        $seq->currentactivity = $sco;
611        $seq->delivery = null;
612        $seq->exception = 'SB.2.9-9';
613        return $seq;
614    } else {
615        return $seq;
616    }
617
618}
619
620function scorm_seq_choice_flow ($constrained, $traverse, $seq) {
621    $seq = scorm_seq_choice_flow_tree ($constrained, $traverse, $seq);
622    if ($seq->identifiedactivity == null) {
623        $seq->identifiedactivity = $constrained;
624        return $seq;
625    } else {
626        return $seq;
627    }
628}
629
630function scorm_seq_choice_flow_tree ($constrained, $traverse, $seq) {
631    $islast = false;
632    $parent = scorm_get_parent ($constrained);
633    if ($traverse == 'forward') {
634        $preord = scorm_get_preorder($constrained);
635        if (count($preorder) == 0 || (count($preorder) == 0 && $preorder[0]->id = $constrained->id)) {
636            // TODO: undefined.
637            $islast = true; // The function is the last activity available.
638        }
639        if ($constrained->parent == '/' || $islast) {
640            $seq->nextactivity = null;
641            return $seq;
642        }
643        $avchildren = scorm_get_available_children($parent); // Available children.
644        if ($avchildren[count($avchildren) - 1]->id == $constrained->id) {
645            $seq = scorm_seq_choice_flow_tree ($parent, 'forward', $seq);
646            return $seq;
647        } else {
648            $i = 0;
649            while ($i < count($avchildren)) {
650                if ($avchildren [$i]->id == $constrained->id) {
651                    $seq->nextactivity = $avchildren [$i + 1];
652                    return $seq;
653                } else {
654                    $i++;
655                }
656            }
657        }
658    }
659
660    if ($traverse == 'backward') {
661        if ($constrained->parent == '/' ) {
662            $seq->nextactivity = null;
663            return $seq;
664        }
665
666        $avchildren = scorm_get_available_children($parent); // Available children.
667        if ($avchildren [0]->id == $constrained->id) {
668            $seq = scorm_seq_choice_flow_tree ($parent, 'backward', $seq);
669            return $seq;
670        } else {
671            $i = count($avchildren) - 1;
672            while ($i >= 0) {
673                if ($avchildren [$i]->id == $constrained->id) {
674                    $seq->nextactivity = $avchildren [$i - 1];
675                    return $seq;
676                } else {
677                    $i--;
678                }
679            }
680        }
681    }
682}
683
684function scorm_seq_choice_activity_traversal($activity, $userid, $seq, $direction) {
685    if ($direction == 'forward') {
686        $act = scorm_seq_rules_check($activity, 'stopforwardtraversal');
687
688        if ($act != null) {
689            $seq->reachable = false;
690            $seq->exception = 'SB.2.4-1';
691            return $seq;
692        }
693        $seq->reachable = false;
694        return $seq;
695    }
696
697    if ($direction == 'backward') {
698        $parentsco = scorm_get_parent($activity);
699        if ($parentsco != null) {
700            if (isset($parentsco->forwardonly) && ($parentsco->forwardonly == true)) {
701                $seq->reachable = false;
702                $seq->exception = 'SB.2.4-2';
703                return $seq;
704            } else {
705                $seq->reachable = false;
706                $seq->exception = 'SB.2.4-3';
707                return $seq;
708            }
709        }
710    }
711    $seq->reachable = true;
712    return $seq;
713}
714
715// Delivery Request Process.
716
717function scorm_sequencing_delivery($scoid, $userid, $seq) {
718
719    if (!scorm_is_leaf($seq->delivery)) {
720        $seq->deliveryvalid = false;
721        $seq->exception = 'DB.1.1-1';
722        return $seq;
723    }
724    $ancestors = scorm_get_ancestors($seq->delivery);
725    $arrpath = array_reverse($ancestors);
726    array_push ($arrpath, $seq->delivery); // Path from the root to the target.
727
728    if (empty($arrpath)) {
729        $seq->deliveryvalid = false;
730        $seq->exception = 'DB.1.1-2';
731        return $seq;
732    }
733
734    foreach ($arrpath as $activity) {
735        if (scorm_check_activity($activity, $userid)) {
736            $seq->deliveryvalid = false;
737            $seq->exception = 'DB.1.1-3';
738            return $seq;
739        }
740    }
741
742    $seq->deliveryvalid = true;
743    return $seq;
744
745}
746
747function scorm_content_delivery_environment($seq, $userid) {
748    global $DB;
749
750    $act = $seq->currentactivity;
751    if (scorm_seq_is('active', $act->id, $userid)) {
752        $seq->exception = 'DB.2-1';
753        return $seq;
754    }
755    $track = $DB->get_record('scorm_scoes_track', array('scoid' => $act->id,
756                                                        'userid' => $userid,
757                                                        'element' => 'suspendedactivity'));
758    if ($track != null) {
759        $seq = scorm_clear_suspended_activity($seq->delivery, $seq, $userid);
760
761    }
762    $seq = scorm_terminate_descendent_attempts ($seq->delivery, $userid, $seq);
763    $ancestors = scorm_get_ancestors($seq->delivery);
764    $arrpath = array_reverse($ancestors);
765    array_push ($arrpath, $seq->delivery);
766    foreach ($arrpath as $activity) {
767        if (!scorm_seq_is('active', $activity->id, $userid)) {
768            if (!isset($activity->tracked) || ($activity->tracked == 1)) {
769                if (!scorm_seq_is('suspended', $activity->id, $userid)) {
770                    $r = $DB->get_record('scorm_scoes_track', array('scoid' => $activity->id,
771                                                                    'userid' => $userid,
772                                                                    'element' => 'activityattemptcount'));
773                    $r->value = ($r->value) + 1;
774                    $DB->update_record('scorm_scoes_track', $r);
775                    if ($r->value == 1) {
776                        scorm_seq_set('activityprogressstatus', $activity->id, $userid, 'true');
777                    }
778                    scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'objectiveprogressstatus', 'false');
779                    scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'objectivesatisfiedstatus', 'false');
780                    scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'objectivemeasurestatus', 'false');
781                    scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'objectivenormalizedmeasure', 0.0);
782
783                    scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'attemptprogressstatus', 'false');
784                    scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'attemptcompletionstatus', 'false');
785                    scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'attemptabsoluteduration', 0.0);
786                    scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'attemptexperiencedduration', 0.0);
787                    scorm_insert_track($userid, $activity->scorm, $activity->id, 0, 'attemptcompletionamount', 0.0);
788                }
789            }
790            scorm_seq_set('active', $activity->id, $userid, 'true');
791        }
792    }
793    $seq->delivery = $seq->currentactivity;
794    scorm_seq_set('suspendedactivity', $activity->id, $userid, 'false');
795
796    // ONCE THE DELIVERY BEGINS (How should I check that?).
797
798    if (isset($activity->tracked) || ($activity->tracked == 0)) {
799        // How should I track the info and what should I do to not record the information for the activity during delivery?
800        $atabsdur = $DB->get_record('scorm_scoes_track', array('scoid' => $activity->id,
801                                                                'userid' => $userid,
802                                                                'element' => 'attemptabsoluteduration'));
803        $atexpdur = $DB->get_record('scorm_scoes_track', array('scoid' => $activity->id,
804                                                                'userid' => $userid,
805                                                                'element' => 'attemptexperiencedduration'));
806    }
807    return $seq;
808}
809
810function scorm_clear_suspended_activity($act, $seq, $userid) {
811    global $DB;
812    $currentact = $seq->currentactivity;
813    $track = $DB->get_record('scorm_scoes_track', array('scoid' => $currentact->id,
814                                                        'userid' => $userid,
815                                                        'element' => 'suspendedactivity'));
816    if ($track != null) {
817        $ancestors = scorm_get_ancestors($act);
818        $commonpos = scorm_find_common_ancestor($ancestors, $currentact);
819        if ($commonpos !== false) {
820            if ($activitypath = array_slice($ancestors, 0, $commonpos)) {
821                if (!empty($activitypath)) {
822
823                    foreach ($activitypath as $activity) {
824                        if (scorm_is_leaf($activity)) {
825                            scorm_seq_set('suspended', $activity->id, $userid, false);
826                        } else {
827                            $children = scorm_get_children($activity);
828                            $bool = false;
829                            foreach ($children as $child) {
830                                if (scorm_seq_is('suspended', $child->id, $userid)) {
831                                    $bool = true;
832                                }
833                            }
834                            if (!$bool) {
835                                scorm_seq_set('suspended', $activity->id, $userid, false);
836                            }
837                        }
838                    }
839                }
840            }
841        }
842        scorm_seq_set('suspendedactivity', $act->id, $userid, false);
843    }
844}
845
846function scorm_select_children_process($scoid, $userid) {
847    global $DB;
848
849    $sco = scorm_get_sco($scoid);
850    if (!scorm_is_leaf($sco)) {
851        if (!scorm_seq_is('suspended', $scoid, $userid) && !scorm_seq_is('active', $scoid, $userid)) {
852            $r = $DB->get_record('scorm_scoes_track', array('scoid' => $scoid,
853                                                            'userid' => $userid,
854                                                            'element' => 'selectiontiming'));
855
856            switch ($r->value) {
857                case 'oneachnewattempt':
858                case 'never':
859                break;
860
861                case 'once':
862                    if (!scorm_seq_is('activityprogressstatus', $scoid, $userid)) {
863                        if (scorm_seq_is('selectioncountsstatus', $scoid, $userid)) {
864                            $childlist = '';
865                            $res = $DB->get_record('scorm_scoes_track', array('scoid' => $scoid,
866                                                                                'userid' => $userid,
867                                                                                'element' => 'selectioncount'));
868                            $i = ($res->value) - 1;
869                            $children = scorm_get_children($sco);
870
871                            while ($i >= 0) {
872                                $pos = array_rand($children);
873                                array_push($childlist, $children[$pos]);
874                                array_splice($children, $pos, 1);
875                                $i--;
876                            }
877                            sort($childlist);
878                            $clist = serialize($childlist);
879                            scorm_seq_set('availablechildren', $scoid, $userid, false);
880                            scorm_seq_set('availablechildren', $scoid, $userid, $clist);
881                        }
882                    }
883                break;
884            }
885        }
886    }
887}
888
889function scorm_randomize_children_process($scoid, $userid) {
890    global $DB;
891
892    $sco = scorm_get_sco($scoid);
893    if (!scorm_is_leaf($sco)) {
894        if (!scorm_seq_is('suspended', $scoid, $userid) && !scorm_seq_is('active', $scoid, $userid)) {
895            $r = $DB->get_record('scorm_scoes_track', array('scoid' => $scoid,
896                                                            'userid' => $userid,
897                                                            'element' => 'randomizationtiming'));
898
899            switch ($r->value) {
900                case 'never':
901                break;
902
903                case 'oneachnewattempt':
904                case 'once':
905                    if (!scorm_seq_is('activityprogressstatus', $scoid, $userid)) {
906                        if (scorm_seq_is('randomizechildren', $scoid, $userid)) {
907                            $childlist = array();
908                            $res = scorm_get_available_children($sco);
909                            $i = count($res) - 1;
910                            $children = $res->value;
911
912                            while ($i >= 0) {
913                                $pos = array_rand($children);
914                                array_push($childlist, $children[$pos]);
915                                array_splice($children, $pos, 1);
916                                $i--;
917                            }
918
919                            $clist = serialize ($childlist);
920                            scorm_seq_set('availablechildren', $scoid, $userid, false);
921                            scorm_seq_set('availablechildren', $scoid, $userid, $clist);
922                        }
923                    }
924                break;
925            }
926        }
927    }
928}
929
930function scorm_terminate_descendent_attempts($activity, $userid, $seq) {
931    $ancestors = scorm_get_ancestors($seq->currentactivity);
932    $commonpos = scorm_find_common_ancestor($ancestors, $activity);
933    if ($commonpos !== false) {
934        if ($activitypath = array_slice($ancestors, 1, $commonpos - 2)) {
935            if (!empty($activitypath)) {
936                foreach ($activitypath as $sco) {
937                    scorm_seq_end_attempt($sco, $userid, $seq->attempt);
938                }
939            }
940        }
941    }
942}
943
944function scorm_sequencing_exception($seq) {
945    global $OUTPUT;
946    if ($seq->exception != null) {
947        switch ($seq->exception) {
948            case 'NB.2.1-1':
949                echo $OUTPUT->notification("Sequencing session has already begun");
950            break;
951            case 'NB.2.1-2':
952                echo $OUTPUT->notification("Sequencing session has not begun");
953            break;
954            case 'NB.2.1-3':
955                echo $OUTPUT->notification("Suspended activity is not defined");
956            break;
957            case 'NB.2.1-4':
958                echo $OUTPUT->notification("Flow Sequencing Control Model Violation");
959            break;
960            case 'NB.2.1-5':
961                echo $OUTPUT->notification("Flow or Forward only Sequencing Control Model Violation");
962            break;
963            case 'NB.2.1-6':
964                echo $OUTPUT->notification("No activity is previous to the root");
965            break;
966            case 'NB.2.1-7':
967                echo $OUTPUT->notification("Unsupported Navigation Request");
968            break;
969            case 'NB.2.1-8':
970                echo $OUTPUT->notification("Choice Exit Sequencing Control Model Violation");
971            break;
972            case 'NB.2.1-9':
973                echo $OUTPUT->notification("No activities to consider");
974            break;
975            case 'NB.2.1-10':
976                echo $OUTPUT->notification("Choice Sequencing Control Model Violation");
977            break;
978            case 'NB.2.1-11':
979                echo $OUTPUT->notification("Target Activity does not exist");
980            break;
981            case 'NB.2.1-12':
982                echo $OUTPUT->notification("Current Activity already terminated");
983            break;
984            case 'NB.2.1-13':
985                echo $OUTPUT->notification("Undefined Navigation Request");
986            break;
987
988            case 'TB.2.3-1':
989                echo $OUTPUT->notification("Current Activity already terminated");
990            break;
991            case 'TB.2.3-2':
992                echo $OUTPUT->notification("Current Activity already terminated");
993            break;
994            case 'TB.2.3-4':
995                echo $OUTPUT->notification("Current Activity already terminated");
996            break;
997            case 'TB.2.3-5':
998                echo $OUTPUT->notification("Nothing to suspend; No active activities");
999            break;
1000            case 'TB.2.3-6':
1001                echo $OUTPUT->notification("Nothing to abandon; No active activities");
1002            break;
1003
1004            case 'SB.2.1-1':
1005                echo $OUTPUT->notification("Last activity in the tree");
1006            break;
1007            case 'SB.2.1-2':
1008                echo $OUTPUT->notification("Cluster has no available children");
1009            break;
1010            case 'SB.2.1-3':
1011                echo $OUTPUT->notification("No activity is previous to the root");
1012            break;
1013            case 'SB.2.1-4':
1014                echo $OUTPUT->notification("Forward Only Sequencing Control Model Violation");
1015            break;
1016
1017            case 'SB.2.2-1':
1018                echo $OUTPUT->notification("Flow Sequencing Control Model Violation");
1019            break;
1020            case 'SB.2.2-2':
1021                echo $OUTPUT->notification("Activity unavailable");
1022            break;
1023
1024            case 'SB.2.3-1':
1025                echo $OUTPUT->notification("Forward Traversal Blocked");
1026            break;
1027            case 'SB.2.3-2':
1028                echo $OUTPUT->notification("Forward Only Sequencing Control Model Violation");
1029            break;
1030            case 'SB.2.3-3':
1031                echo $OUTPUT->notification("No activity is previous to the root");
1032            break;
1033
1034            case 'SB.2.5-1':
1035                echo $OUTPUT->notification("Sequencing session has already begun");
1036            break;
1037
1038            case 'SB.2.6-1':
1039                echo $OUTPUT->notification("Sequencing session has already begun");
1040            break;
1041            case 'SB.2.6-2':
1042                echo $OUTPUT->notification("No Suspended activity is defined");
1043            break;
1044
1045            case 'SB.2.7-1':
1046                echo $OUTPUT->notification("Sequencing session has not begun");
1047            break;
1048            case 'SB.2.7-2':
1049                echo $OUTPUT->notification("Flow Sequencing Control Model Violation");
1050            break;
1051
1052            case 'SB.2.8-1':
1053                echo $OUTPUT->notification("Sequencing session has not begun");
1054            break;
1055            case 'SB.2.8-2':
1056                echo $OUTPUT->notification("Flow Sequencing Control Model Violation");
1057            break;
1058
1059            case 'SB.2.9-1':
1060                echo $OUTPUT->notification("No target for Choice");
1061            break;
1062            case 'SB.2.9-2':
1063                echo $OUTPUT->notification("Target Activity does not exist or is unavailable");
1064            break;
1065            case 'SB.2.9-3':
1066                echo $OUTPUT->notification("Target Activity hidden from choice");
1067            break;
1068            case 'SB.2.9-4':
1069                echo $OUTPUT->notification("Choice Sequencing Control Model Violation");
1070            break;
1071            case 'SB.2.9-5':
1072                echo $OUTPUT->notification("No activities to consider");
1073            break;
1074            case 'SB.2.9-6':
1075                echo $OUTPUT->notification("Unable to activate target; target is not a child of the Current Activity");
1076            break;
1077            case 'SB.2.9-7':
1078                echo $OUTPUT->notification("Choice Exit Sequencing Control Model Violation");
1079            break;
1080            case 'SB.2.9-8':
1081                echo $OUTPUT->notification("Unable to choose target activity - constrained choice");
1082            break;
1083            case 'SB.2.9-9':
1084                echo $OUTPUT->notification("Choice Request Prevented by Flow-only Activity");
1085            break;
1086
1087            case 'SB.2.10-1':
1088                echo $OUTPUT->notification("Sequencing session has not begun");
1089            break;
1090            case 'SB.2.10-2':
1091                echo $OUTPUT->notification("Current Activity is active or suspended");
1092            break;
1093            case 'SB.2.10-3':
1094                echo $OUTPUT->notification("Flow Sequencing Control Model Violation");
1095            break;
1096
1097            case 'SB.2.11-1':
1098                echo $OUTPUT->notification("Sequencing session has not begun");
1099            break;
1100            case 'SB.2.11-2':
1101                echo $OUTPUT->notification("Current Activity has not been terminated");
1102            break;
1103
1104            case 'SB.2.12-2':
1105                echo $OUTPUT->notification("Undefined Sequencing Request");
1106            break;
1107
1108            case 'DB.1.1-1':
1109                echo $OUTPUT->notification("Cannot deliver a non-leaf activity");
1110            break;
1111            case 'DB.1.1-2':
1112                echo $OUTPUT->notification("Nothing to deliver");
1113            break;
1114            case 'DB.1.1-3':
1115                echo $OUTPUT->notification("Activity unavailable");
1116            break;
1117
1118            case 'DB.2-1':
1119                echo $OUTPUT->notification("Identified activity is already active");
1120            break;
1121
1122        }
1123
1124    }
1125}
1126