Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
   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  function 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  
  41  function 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  
  66  function 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  
 117  function 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  
 187  function 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  
 236  function 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  
 257  function 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  
 275  function 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  
 299  function 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  
 322  function 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  
 344  function 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  
 373  function 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  
 620  function 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  
 630  function 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  
 684  function 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  
 717  function 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  
 747  function 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  
 810  function 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  
 846  function 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  
 889  function 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  
 930  function 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  
 944  function 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  }