Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 402 and 403]

   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  defined('MOODLE_INTERNAL') || die();
  18  
  19  require_once($CFG->dirroot.'/mod/scorm/datamodels/scormlib.php');
  20  require_once($CFG->dirroot.'/mod/scorm/datamodels/sequencinglib.php');
  21  
  22  function scorm_seq_overall ($scoid, $userid, $request, $attempt) {
  23      $seq = scorm_seq_navigation($scoid, $userid, $request, $attempt);
  24      if ($seq->navigation) {
  25          if ($seq->termination != null) {
  26              $seq = scorm_seq_termination($scoid, $userid, $seq);
  27          }
  28          if ($seq->sequencing != null) {
  29              $seq = scorm_seq_sequencing($scoid, $userid, $seq);
  30              if ($seq->sequencing == 'exit') { // Return the control to the LTS.
  31                  return 'true';
  32              }
  33          }
  34          if ($seq->delivery != null) {
  35              $seq = scorm_sequencing_delivery($scoid, $userid, $seq);
  36              $seq = scorm_content_delivery_environment ($seq, $userid);
  37          }
  38      }
  39      if ($seq->exception != null) {
  40          $seq = scorm_sequencing_exception($seq);
  41      }
  42      return 'true';
  43  }
  44  
  45  function scorm_seq_navigation ($scoid, $userid, $request, $attempt=0) {
  46      global $DB;
  47  
  48      // Sequencing structure.
  49      $seq = new stdClass();
  50      $seq->currentactivity = scorm_get_sco($scoid);
  51      $seq->traversaldir = null;
  52      $seq->nextactivity = null;
  53      $seq->deliveryvalid = null;
  54      $seq->attempt = $attempt;
  55  
  56      $seq->identifiedactivity = null;
  57      $seq->delivery = null;
  58      $seq->deliverable = false;
  59      $seq->active = scorm_seq_is('active', $scoid, $userid);
  60      $seq->suspended = scorm_seq_is('suspended', $scoid, $userid);
  61      $seq->navigation = null;
  62      $seq->termination = null;
  63      $seq->sequencing = null;
  64      $seq->target = null;
  65      $seq->endsession = null;
  66      $seq->exception = null;
  67      $seq->reachable = true;
  68      $seq->prevact = true;
  69  
  70      $sco = scorm_get_sco($scoid);
  71  
  72      switch ($request) {
  73          case 'start_':
  74              if (empty($seq->currentactivity)) {
  75                  $seq->navigation = true;
  76                  $seq->sequencing = 'start';
  77              } else {
  78                  $seq->exception = 'NB.2.1-1'; // Sequencing session already begun.
  79              }
  80          break;
  81          case 'resumeall_':
  82              if (empty($seq->currentactivity)) {
  83                  // TODO: I think it's suspend instead of suspendedactivity.
  84                  if ($track = $DB->get_record('scorm_scoes_track',
  85                      array('scoid' => $scoid, 'userid' => $userid, 'element' => 'suspendedactivity'))) {
  86  
  87                      $seq->navigation = true;
  88                      $seq->sequencing = 'resumeall';
  89                  } else {
  90                      $seq->exception = 'NB.2.1-3'; // No suspended activity found.
  91                  }
  92              } else {
  93                  $seq->exception = 'NB.2.1-1'; // Sequencing session already begun.
  94              }
  95          break;
  96          case 'continue_':
  97          case 'previous_':
  98              if (!empty($seq->currentactivity)) {
  99                  $sco = $seq->currentactivity;
 100                  if ($sco->parent != '/') {
 101                      if ($parentsco = scorm_get_parent($sco)) {
 102  
 103                          if (isset($parentsco->flow) && ($parentsco->flow == true)) { // I think it's parentsco.
 104                              // Current activity is active!
 105                              if (scorm_seq_is('active', $sco->id, $userid)) {
 106                                  if ($request == 'continue_') {
 107                                      $seq->navigation = true;
 108                                      $seq->termination = 'exit';
 109                                      $seq->sequencing = 'continue';
 110                                  } else {
 111                                      if (!isset($parentsco->forwardonly) || ($parentsco->forwardonly == false)) {
 112                                          $seq->navigation = true;
 113                                          $seq->termination = 'exit';
 114                                          $seq->sequencing = 'previous';
 115                                      } else {
 116                                          $seq->exception = 'NB.2.1-5'; // Violates control mode.
 117                                      }
 118                                  }
 119                              }
 120                          }
 121  
 122                      }
 123                  }
 124              } else {
 125                  $seq->exception = 'NB.2.1-2'; // Current activity not defined.
 126              }
 127          break;
 128          case 'forward_':
 129          case 'backward_':
 130              $seq->exception = 'NB.2.1-7'; // None to be done, behavior not defined.
 131          break;
 132          case 'exit_':
 133          case 'abandon_':
 134              if (!empty($seq->currentactivity)) {
 135                  // Current activity is active !
 136                  $seq->navigation = true;
 137                  $seq->termination = substr($request, 0, -1);
 138                  $seq->sequencing = 'exit';
 139              } else {
 140                  $seq->exception = 'NB.2.1-2'; // Current activity not defined.
 141              }
 142          case 'exitall_':
 143          case 'abandonall_':
 144          case 'suspendall_':
 145              if (!empty($seq->currentactivity)) {
 146                  $seq->navigation = true;
 147                  $seq->termination = substr($request, 0, -1);
 148                  $seq->sequencing = 'exit';
 149              } else {
 150                  $seq->exception = 'NB.2.1-2'; // Current activity not defined.
 151              }
 152          break;
 153          default: // Example {target=<STRING>}choice.
 154              if ($targetsco = $DB->get_record('scorm_scoes', array('scorm' => $sco->scorm, 'identifier' => $request))) {
 155                  if ($targetsco->parent != '/') {
 156                      $seq->target = $request;
 157                  } else {
 158                      if ($parentsco = scorm_get_parent($targetsco)) {
 159                          if (!isset($parentsco->choice) || ($parentsco->choice == true)) {
 160                              $seq->target = $request;
 161                          }
 162                      }
 163                  }
 164                  if ($seq->target != null) {
 165                      if (empty($seq->currentactivity)) {
 166                          $seq->navigation = true;
 167                          $seq->sequencing = 'choice';
 168                      } else {
 169                          if (!$sco = scorm_get_sco($scoid)) {
 170                              return $seq;
 171                          }
 172                          if ($sco->parent != $targetsco->parent) {
 173                              $ancestors = scorm_get_ancestors($sco);
 174                              $commonpos = scorm_find_common_ancestor($ancestors, $targetsco);
 175                              if ($commonpos !== false) {
 176                                  if ($activitypath = array_slice($ancestors, 0, $commonpos)) {
 177                                      foreach ($activitypath as $activity) {
 178                                          if (scorm_seq_is('active', $activity->id, $userid) &&
 179                                              (isset($activity->choiceexit) && ($activity->choiceexit == false))) {
 180                                              $seq->navigation = false;
 181                                              $seq->termination = null;
 182                                              $seq->sequencing = null;
 183                                              $seq->target = null;
 184                                              $seq->exception = 'NB.2.1-8'; // Violates control mode.
 185                                              return $seq;
 186                                          }
 187                                      }
 188                                  } else {
 189                                      $seq->navigation = false;
 190                                      $seq->termination = null;
 191                                      $seq->sequencing = null;
 192                                      $seq->target = null;
 193                                      $seq->exception = 'NB.2.1-9';
 194                                  }
 195                              }
 196                          }
 197                          // Current activity is active !
 198                          $seq->navigation = true;
 199                          $seq->sequencing = 'choice';
 200                      }
 201                  } else {
 202                      $seq->exception = 'NB.2.1-10';  // Violates control mode.
 203                  }
 204              } else {
 205                  $seq->exception = 'NB.2.1-11';  // Target activity does not exists.
 206              }
 207          break;
 208      }
 209      return $seq;
 210  }
 211  
 212  function scorm_seq_termination ($seq, $userid) {
 213      if (empty($seq->currentactivity)) {
 214          $seq->termination = false;
 215          $seq->exception = 'TB.2.3-1';
 216          return $seq;
 217      }
 218  
 219      $sco = $seq->currentactivity;
 220  
 221      if ((($seq->termination == 'exit') || ($seq->termination == 'abandon')) && !$seq->active) {
 222          $seq->termination = false;
 223          $seq->exception = 'TB.2.3-2';
 224          return $seq;
 225      }
 226      switch ($seq->termination) {
 227          case 'exit':
 228              scorm_seq_end_attempt($sco, $userid, $seq);
 229              $seq = scorm_seq_exit_action_rules($seq, $userid);
 230              do {
 231                  $exit = false;// I think this is false. Originally this was true.
 232                  $seq = scorm_seq_post_cond_rules($seq, $userid);
 233                  if ($seq->termination == 'exitparent') {
 234                      if ($sco->parent != '/') {
 235                          $sco = scorm_get_parent($sco);
 236                          $seq->currentactivity = $sco;
 237                          $seq->active = scorm_seq_is('active', $sco->id, $userid);
 238                          scorm_seq_end_attempt($sco, $userid, $seq);
 239                          $exit = true; // I think it's true. Originally this was false.
 240                      } else {
 241                          $seq->termination = false;
 242                          $seq->exception = 'TB.2.3-4';
 243                          return $seq;
 244                      }
 245                  }
 246              } while (($exit == false) && ($seq->termination == 'exit'));
 247              if ($seq->termination == 'exit') {
 248                  $seq->termination = true;
 249                  return $seq;
 250              }
 251          case 'exitall':
 252              if ($seq->active) {
 253                  scorm_seq_end_attempt($sco, $userid, $seq);
 254              }
 255              // Terminate Descendent Attempts Process.
 256  
 257              if ($ancestors = scorm_get_ancestors($sco)) {
 258                  foreach ($ancestors as $ancestor) {
 259                      scorm_seq_end_attempt($ancestor, $userid, $seq);
 260                      $seq->currentactivity = $ancestor;
 261                  }
 262              }
 263  
 264              $seq->active = scorm_seq_is('active', $seq->currentactivity->id, $userid);
 265              $seq->termination = true;
 266              $seq->sequencing = 'exit';
 267          break;
 268          case 'suspendall':
 269              if (($seq->active) || ($seq->suspended)) {
 270                  scorm_seq_set('suspended', $sco->id, $userid, $attempt);
 271              } else {
 272                  if ($sco->parent != '/') {
 273                      $parentsco = scorm_get_parent($sco);
 274                      scorm_seq_set('suspended', $parentsco->id, $userid, $attempt);
 275                  } else {
 276                      $seq->termination = false;
 277                      $seq->exception = 'TB.2.3-3';
 278                  }
 279              }
 280              if ($ancestors = scorm_get_ancestors($sco)) {
 281                  foreach ($ancestors as $ancestor) {
 282                      scorm_seq_set('active', $ancestor->id, $userid, $attempt, false);
 283                      scorm_seq_set('suspended', $ancestor->id, $userid, $attempt);
 284                      $seq->currentactivity = $ancestor;
 285                  }
 286                  $seq->termination = true;
 287                  $seq->sequencing = 'exit';
 288              } else {
 289                  $seq->termination = false;
 290                  $seq->exception = 'TB.2.3-5';
 291              }
 292          break;
 293          case 'abandon':
 294              scorm_seq_set('active', $sco->id, $userid, $attempt, false);
 295              $seq->active = null;
 296              $seq->termination = true;
 297          break;
 298          case 'abandonall':
 299              if ($ancestors = scorm_get_ancestors($sco)) {
 300                  foreach ($ancestors as $ancestor) {
 301                      scorm_seq_set('active', $ancestor->id, $userid, $attempt, false);
 302                      $seq->currentactivity = $ancestor;
 303                  }
 304                  $seq->termination = true;
 305                  $seq->sequencing = 'exit';
 306              } else {
 307                  $seq->termination = false;
 308                  $seq->exception = 'TB.2.3-6';
 309              }
 310          break;
 311          default:
 312              $seq->termination = false;
 313              $seq->exception = 'TB.2.3-7';
 314          break;
 315      }
 316      return $seq;
 317  }
 318  
 319  function scorm_seq_end_attempt($sco, $userid, $seq) {
 320      global $DB;
 321      if (scorm_is_leaf($sco)) {
 322          if (!isset($sco->tracked) || ($sco->tracked == 1)) {
 323              if (!scorm_seq_is('suspended', $sco->id, $userid)) {
 324                  if (!isset($sco->completionsetbycontent) || ($sco->completionsetbycontent == 0)) {
 325                      if (!scorm_seq_is('attemptprogressstatus', $sco->id, $userid, $seq->attempt)) {
 326                          $incomplete = $DB->get_field('scorm_scoes_track', 'value',
 327                                                          array('scoid' => $sco->id,
 328                                                                  'userid' => $userid,
 329                                                                  'element' => 'cmi.completion_status'));
 330                          if ($incomplete != 'incomplete') {
 331                              scorm_seq_set('attemptprogressstatus', $sco->id, $userid, $seq->attempt);
 332                              scorm_seq_set('attemptcompletionstatus', $sco->id, $userid, $seq->attempt);
 333                          }
 334                      }
 335                  }
 336                  if (!isset($sco->objectivesetbycontent) || ($sco->objectivesetbycontent == 0)) {
 337                      if ($objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id))) {
 338                          foreach ($objectives as $objective) {
 339                              if ($objective->primaryobj) {
 340                                  if (!scorm_seq_is('objectiveprogressstatus', $sco->id, $userid, $seq->attempt)) {
 341                                      scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $seq->attempt);
 342                                      scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $seq->attempt);
 343                                  }
 344                              }
 345                          }
 346                      }
 347                  }
 348              }
 349          }
 350      } else if ($children = scorm_get_children($sco)) {
 351          $suspended = false;
 352          foreach ($children as $child) {
 353              if (scorm_seq_is('suspended', $child, $userid, $seq->attempt)) {
 354                  $suspended = true;
 355                  break;
 356              }
 357          }
 358          if ($suspended) {
 359              scorm_seq_set('suspended', $sco, $userid, $seq->attempt);
 360          } else {
 361              scorm_seq_set('suspended', $sco, $userid, $seq->attempt, false);
 362          }
 363      }
 364      scorm_seq_set('active', $sco->id, $userid, $seq->attempt, false);
 365      scorm_seq_overall_rollup($sco, $userid, $seq);
 366  }
 367  
 368  function scorm_seq_is($what, $scoid, $userid, $attempt=0) {
 369      global $DB;
 370  
 371      // Check if passed activity $what is active.
 372      $active = false;
 373      if ($track = $DB->get_record('scorm_scoes_track',
 374              array('scoid' => $scoid, 'userid' => $userid, 'attempt' => $attempt, 'element' => $what))) {
 375          $active = true;
 376      }
 377      return $active;
 378  }
 379  
 380  function scorm_seq_set($what, $scoid, $userid, $attempt=0, $value='true') {
 381      global $DB;
 382  
 383      $sco = scorm_get_sco($scoid);
 384  
 385      // Set passed activity to active or not.
 386      if ($value == false) {
 387          $DB->delete_records('scorm_scoes_track', array('scoid' => $scoid, 'userid' => $userid,
 388                                                          'attempt' => $attempt, 'element' => $what));
 389      } else {
 390          scorm_insert_track($userid, $sco->scorm, $sco->id, $attempt, $what, $value);
 391      }
 392  
 393      // Update grades in gradebook.
 394      $scorm = $DB->get_record('scorm', array('id' => $sco->scorm));
 395      scorm_update_grades($scorm, $userid, true);
 396  }
 397  
 398  function scorm_evaluate_condition ($rollupruleconds, $sco, $userid) {
 399      global $DB;
 400  
 401      $res = false;
 402  
 403      if (strpos($rollupruleconds, 'and ')) {
 404          $rollupruleconds = array_filter(explode(' and ', $rollupruleconds));
 405          $conditioncombination = 'all';
 406      } else {
 407          $rollupruleconds = array_filter(explode(' or ', $rollupruleconds));
 408          $conditioncombination = 'or';
 409      }
 410      foreach ($rollupruleconds as $rolluprulecond) {
 411          $notflag = false;
 412          if (strpos($rolluprulecond, 'not') !== false) {
 413              $rolluprulecond = str_replace('not', '', $rolluprulecond);
 414              $notflag = true;
 415          }
 416          $conditionarray['condition'] = $rolluprulecond;
 417          $conditionarray['notflag'] = $notflag;
 418          $conditions[] = $conditionarray;
 419      }
 420      foreach ($conditions as $condition) {
 421          $checknot = true;
 422          $res = false;
 423          if ($condition['notflag']) {
 424              $checknot = false;
 425          }
 426          switch ($condition['condition']) {
 427              case 'satisfied':
 428                  $r = $DB->get_record('scorm_scoes_track',
 429                                          array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectivesatisfiedstatus'));
 430                  if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
 431                      $r = $DB->get_record('scorm_scoes_track',
 432                                          array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectiveprogressstatus'));
 433                      if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
 434                          $res = true;
 435                      }
 436                  }
 437                  break;
 438  
 439              case 'objectiveStatusKnown':
 440                  $r = $DB->get_record('scorm_scoes_track',
 441                                          array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectiveprogressstatus'));
 442                  if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
 443                      $res = true;
 444                  }
 445                  break;
 446  
 447              case 'notobjectiveStatusKnown':
 448                  $r = $DB->get_record('scorm_scoes_track',
 449                                          array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectiveprogressstatus'));
 450                  if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
 451                      $res = true;
 452                  }
 453                  break;
 454  
 455              case 'objectiveMeasureKnown':
 456                  $r = $DB->get_record('scorm_scoes_track',
 457                                          array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectivemeasurestatus'));
 458                  if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
 459                      $res = true;
 460                  }
 461                  break;
 462  
 463              case 'notobjectiveMeasureKnown':
 464                  $r = $DB->get_record('scorm_scoes_track',
 465                                          array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectivemeasurestatus'));
 466                  if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
 467                      $res = true;
 468                  }
 469                  break;
 470  
 471              case 'completed':
 472                  $r = $DB->get_record('scorm_scoes_track',
 473                                          array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'attemptcompletionstatus'));
 474                  if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
 475                      $r = $DB->get_record('scorm_scoes_track',
 476                          array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'attemptprogressstatus'));
 477                      if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
 478                          $res = true;
 479                      }
 480                  }
 481                  break;
 482  
 483              case 'attempted':
 484                  $attempt = $DB->get_field('scorm_scoes_track', 'attempt',
 485                                              array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'x.start.time'));
 486                  if ($checknot && $attempt > 0) {
 487                      $res = true;
 488                  } else if (!$checknot && $attempt <= 0) {
 489                      $res = true;
 490                  }
 491                  break;
 492  
 493              case 'attemptLimitExceeded':
 494                  $r = $DB->get_record('scorm_scoes_track',
 495                                          array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'activityprogressstatus'));
 496                  if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
 497                      $r = $DB->get_record('scorm_scoes_track',
 498                                              array('scoid' => $sco->id, 'userid' => $userid,
 499                                                      'element' => 'limitconditionattemptlimitcontrol'));
 500                      if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
 501                          if ($r = $DB->get_field('scorm_scoes_track', 'attempt', array('scoid' => $sco->id, 'userid' => $userid)) &&
 502                              $r2 = $DB->get_record('scorm_scoes_track', array('scoid' => $sco->id, 'userid' => $userid,
 503                                                                                  'element' => 'limitconditionattemptlimit')) ) {
 504  
 505                              if ($checknot && ($r->value >= $r2->value)) {
 506                                  $res = true;
 507                              } else if (!$checknot && ($r->value < $r2->value)) {
 508                                  $res = true;
 509                              }
 510                          }
 511                      }
 512                  }
 513                  break;
 514  
 515              case 'activityProgressKnown':
 516                  $r = $DB->get_record('scorm_scoes_track',
 517                                          array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'activityprogressstatus'));
 518                  if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
 519                      $r = $DB->get_record('scorm_scoes_track',
 520                                              array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'attemptprogressstatus'));
 521                      if ((!isset($r->value) && !$checknot) || (isset($r->value) && ($r->value == $checknot))) {
 522                          $res = true;
 523                      }
 524                  }
 525                  break;
 526          }
 527  
 528          if ($conditioncombination == 'all' && !$res) {
 529              break;
 530          } else if ($conditioncombination == 'or' && $res) {
 531              break;
 532          }
 533      }
 534  
 535      return $res;
 536  }
 537  
 538  function scorm_check_activity ($activity, $userid) {
 539      $act = scorm_seq_rules_check($activity, 'disabled');
 540      if ($act != null) {
 541          return true;
 542      }
 543      if (scorm_limit_cond_check ($activity, $userid)) {
 544          return true;
 545      }
 546      return false;
 547  }
 548  
 549  function scorm_limit_cond_check ($activity, $userid) {
 550      global $DB;
 551  
 552      if (isset($activity->tracked) && ($activity->tracked == 0)) {
 553          return false;
 554      }
 555  
 556      if (scorm_seq_is('active', $activity->id, $userid) || scorm_seq_is('suspended', $activity->id, $userid)) {
 557          return false;
 558      }
 559  
 560      if (!isset($activity->limitcontrol) || ($activity->limitcontrol == 1)) {
 561          $r = $DB->get_record('scorm_scoes_track',
 562                                  array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'activityattemptcount'));
 563          if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitattempt)) {
 564              return true;
 565          }
 566      }
 567  
 568      if (!isset($activity->limitabsdurcontrol) || ($activity->limitabsdurcontrol == 1)) {
 569          $r = $DB->get_record('scorm_scoes_track',
 570                                  array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'activityabsoluteduration'));
 571          if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitabsduration)) {
 572              return true;
 573          }
 574      }
 575  
 576      if (!isset($activity->limitexpdurcontrol) || ($activity->limitexpdurcontrol == 1)) {
 577          $r = $DB->get_record('scorm_scoes_track',
 578                                  array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'activityexperiencedduration'));
 579          if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitexpduration)) {
 580              return true;
 581          }
 582      }
 583  
 584      if (!isset($activity->limitattabsdurcontrol) || ($activity->limitattabsdurcontrol == 1)) {
 585          $r = $DB->get_record('scorm_scoes_track',
 586                                  array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'attemptabsoluteduration'));
 587          if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitattabsduration)) {
 588              return true;
 589          }
 590      }
 591  
 592      if (!isset($activity->limitattexpdurcontrol) || ($activity->limitattexpdurcontrol == 1)) {
 593          $r = $DB->get_record('scorm_scoes_track',
 594                                  array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'attemptexperiencedduration'));
 595          if (scorm_seq_is('activityprogressstatus', $activity->id, $userid) && ($r->value >= $activity->limitattexpduration)) {
 596              return true;
 597          }
 598      }
 599  
 600      if (!isset($activity->limitbegincontrol) || ($activity->limitbegincontrol == 1)) {
 601          $r = $DB->get_record('scorm_scoes_track',
 602                                  array('scoid' => $activity->id, 'userid' => $userid, 'element' => 'begintime'));
 603          if (isset($activity->limitbegintime) && time() >= $activity->limitbegintime) {
 604              return true;
 605          }
 606      }
 607  
 608      if (!isset($activity->limitbegincontrol) || ($activity->limitbegincontrol == 1)) {
 609          if (isset($activity->limitbegintime) && time() < $activity->limitbegintime) {
 610              return true;
 611          }
 612      }
 613  
 614      if (!isset($activity->limitendcontrol) || ($activity->limitendcontrol == 1)) {
 615          if (isset($activity->limitendtime) && time() > $activity->limitendtime) {
 616              return true;
 617          }
 618      }
 619      return false;
 620  }
 621  
 622  function scorm_seq_rules_check ($sco, $action) {
 623      global $DB;
 624      $act = null;
 625  
 626      if ($rules = $DB->get_records('scorm_seq_ruleconds', array('scoid' => $sco->id, 'action' => $action))) {
 627          foreach ($rules as $rule) {
 628              if ($act = scorm_seq_rule_check($sco, $rule)) {
 629                  return $act;
 630              }
 631          }
 632      }
 633      return $act;
 634  
 635  }
 636  
 637  function scorm_seq_rule_check ($sco, $rule) {
 638      global $DB;
 639  
 640      $bag = Array();
 641      $cond = '';
 642      $ruleconds = $DB->get_records('scorm_seq_rulecond', array('scoid' => $sco->id, 'ruleconditionsid' => $rule->id));
 643      foreach ($ruleconds as $rulecond) {
 644          if ($rulecond->operator == 'not') {
 645              if ($rulecond->cond != 'unknown' ) {
 646                  $rulecond->cond = 'not'.$rulecond->cond;
 647              }
 648          }
 649           $bag[] = $rulecond->cond;
 650      }
 651      if (empty($bag)) {
 652          $cond = 'unknown';
 653          return $cond;
 654      }
 655  
 656      if ($rule->conditioncombination == 'all') {
 657          foreach ($bag as $con) {
 658                  $cond = $cond.' and '.$con;
 659          }
 660      } else {
 661          foreach ($bag as $con) {
 662              $cond = $cond.' or '.$con;
 663          }
 664      }
 665      return $cond;
 666  }
 667  
 668  function scorm_seq_overall_rollup($sco, $userid, $seq) {
 669      if ($ancestors = scorm_get_ancestors($sco)) {
 670          foreach ($ancestors as $ancestor) {
 671              if (!scorm_is_leaf($ancestor)) {
 672                  scorm_seq_measure_rollup($sco, $userid, $seq->attempt);
 673              }
 674              scorm_seq_objective_rollup($sco, $userid, $seq->attempt);
 675              scorm_seq_activity_progress_rollup($sco, $userid, $seq);
 676          }
 677      }
 678  }
 679  
 680  function scorm_seq_measure_rollup($sco, $userid, $attempt = 0) {
 681      global $DB;
 682  
 683      $totalmeasure = 0; // Check if there is something similar in the database.
 684      $valid = false; // Same as in the last line.
 685      $countedmeasures = 0; // Same too.
 686      $targetobjective = null;
 687      $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id));
 688  
 689      foreach ($objectives as $objective) {
 690          if ($objective->primaryobj == true) { // Objective contributes to rollup.
 691              $targetobjective = $objective;
 692              break;
 693          }
 694  
 695      }
 696      if ($targetobjective != null) {
 697          $children = scorm_get_children($sco);
 698          if (!empty ($children)) {
 699              foreach ($children as $child) {
 700                  $child = scorm_get_sco ($child);
 701                  if (!isset($child->tracked) || ($child->tracked == 1)) {
 702                      $rolledupobjective = null;// We set the rolled up activity to undefined.
 703                      $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $child->id));
 704                      foreach ($objectives as $objective) {
 705                          if ($objective->primaryobj == true) {// Objective contributes to rollup I'm using primaryobj field, but not.
 706                              $rolledupobjective = $objective;
 707                              break;
 708                          }
 709                      }
 710                      if ($rolledupobjective != null) {
 711                          $child = scorm_get_sco($child->id);
 712                          $countedmeasures = $countedmeasures + ($child->measureweight);
 713                          if (!scorm_seq_is('objectivemeasurestatus', $sco->id, $userid, $attempt)) {
 714                              $normalizedmeasure = $DB->get_record('scorm_scoes_track',
 715                                  array('scoid' => $child->id, 'userid' => $userid, 'element' => 'objectivenormalizedmeasure'));
 716                              $totalmeasure = $totalmeasure + (($normalizedmeasure->value) * ($child->measureweight));
 717                              $valid = true;
 718                          }
 719                      }
 720                  }
 721              }
 722          }
 723  
 724          if (!$valid) {
 725              scorm_seq_set('objectivemeasurestatus', $sco->id, $userid, $attempt, false);
 726          } else {
 727              if ($countedmeasures > 0) {
 728                  scorm_seq_set('objectivemeasurestatus', $sco->id, $userid, $attempt);
 729                  $val = $totalmeasure / $countedmeasures;
 730                  scorm_seq_set('objectivenormalizedmeasure', $sco->id, $userid, $attempt, $val);
 731              } else {
 732                  scorm_seq_set('objectivemeasurestatus', $sco->id, $userid, $attempt, false);
 733              }
 734          }
 735      }
 736  }
 737  
 738  function scorm_seq_objective_rollup($sco, $userid, $attempt = 0) {
 739      global $DB;
 740  
 741      scorm_seq_objective_rollup_measure($sco, $userid, $attempt);
 742      scorm_seq_objective_rollup_rules($sco, $userid, $attempt);
 743      scorm_seq_objective_rollup_default($sco, $userid, $attempt);
 744  
 745      /*
 746      if ($targetobjective->satisfiedbymeasure) {
 747          scorm_seq_objective_rollup_measure($sco, $userid);
 748      }
 749      else{
 750          if ((scorm_seq_rollup_rule_check($sco, $userid, 'incomplete'))
 751               || (scorm_seq_rollup_rule_check($sco, $userid, 'completed'))) {
 752              scorm_seq_objective_rollup_rules($sco, $userid);
 753          }
 754          else{
 755  
 756              $rolluprules = $DB->get_record('scorm_seq_rolluprule', array('scoid'=>$sco->id, 'userid'=>$userid));
 757              foreach ($rolluprules as $rolluprule) {
 758                  $rollupruleconds = $DB->get_records('scorm_seq_rolluprulecond', array('rollupruleid'=>$rolluprule->id));
 759                  foreach ($rollupruleconds as $rolluprulecond) {
 760  
 761                      switch ($rolluprulecond->cond!='satisfied'
 762                              && $rolluprulecond->cond!='completed' && $rolluprulecond->cond!='attempted') {
 763  
 764                             scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, false);
 765  
 766                          break;
 767                      }
 768                  }
 769  
 770  
 771          }
 772      }
 773      */
 774  }
 775  
 776  function scorm_seq_objective_rollup_measure($sco, $userid, $attempt = 0) {
 777      global $DB;
 778  
 779      $targetobjective = null;
 780  
 781      $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id));
 782      foreach ($objectives as $objective) {
 783          if ($objective->primaryobj == true) {
 784              $targetobjective = $objective;
 785              break;
 786          }
 787      }
 788      if ($targetobjective != null) {
 789          if ($targetobjective->satisfiedbymeasure) {
 790              if (!scorm_seq_is('objectiveprogressstatus', $sco->id, $userid, $attempt)) {
 791                  scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt, false);
 792              } else {
 793                  if (scorm_seq_is('active', $sco->id, $userid, $attempt)) {
 794                      $isactive = true;
 795                  } else {
 796                      $isactive = false;
 797                  }
 798  
 799                  $normalizedmeasure = $DB->get_record('scorm_scoes_track',
 800                      array('scoid' => $sco->id, 'userid' => $userid, 'element' => 'objectivenormalizedmeasure'));
 801  
 802                  $sco = scorm_get_sco ($sco->id);
 803  
 804                  if (!$isactive || ($isactive &&
 805                      (!isset($sco->measuresatisfactionifactive) || $sco->measuresatisfactionifactive == true))) {
 806                      if (isset($normalizedmeasure->value) && ($normalizedmeasure->value >= $targetobjective->minnormalizedmeasure)) {
 807                          scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt);
 808                          scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $attempt);
 809                      } else {
 810                          // TODO: handle the case where cmi.success_status is passed and objectivenormalizedmeasure undefined.
 811                          scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt);
 812                      }
 813                  } else {
 814                      scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt, false);
 815                  }
 816              }
 817          }
 818      }
 819  }
 820  
 821  function scorm_seq_objective_rollup_default($sco, $userid, $attempt = 0) {
 822      global $DB;
 823  
 824      if (!(scorm_seq_rollup_rule_check($sco, $userid, 'incomplete')) && !(scorm_seq_rollup_rule_check($sco, $userid, 'completed'))) {
 825          if ($rolluprules = $DB->get_record('scorm_seq_rolluprule', array('scoid' => $sco->id))) {
 826              foreach ($rolluprules as $rolluprule) {
 827                  $rollupruleconds = $DB->get_records('scorm_seq_rolluprulecond', array('rollupruleid' => $rolluprule->id));
 828                  foreach ($rollupruleconds as $rolluprulecond) {
 829                      if ($rolluprulecond->cond != 'satisfied' && $rolluprulecond->cond != 'completed' &&
 830                              $rolluprulecond->cond != 'attempted') {
 831                          scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $attempt, false);
 832                          break;
 833                      }
 834                  }
 835              }
 836          }
 837      }
 838  }
 839  
 840  
 841  function scorm_seq_objective_rollup_rules($sco, $userid, $attempt = 0) {
 842      global $DB;
 843  
 844      $targetobjective = null;
 845  
 846      $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $sco->id));
 847      foreach ($objectives as $objective) {
 848          if ($objective->primaryobj == true) {// Objective contributes to rollup I'm using primaryobj field, but not.
 849              $targetobjective = $objective;
 850              break;
 851          }
 852      }
 853      if ($targetobjective != null) {
 854  
 855          if (scorm_seq_rollup_rule_check($sco, $userid, 'notsatisfied')) {// With not satisfied rollup for the activity.
 856              scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt);
 857              scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $attempt, false);
 858          }
 859          if (scorm_seq_rollup_rule_check($sco, $userid, 'satisfied')) {// With satisfied rollup for the activity.
 860              scorm_seq_set('objectiveprogressstatus', $sco->id, $userid, $attempt);
 861              scorm_seq_set('objectivesatisfiedstatus', $sco->id, $userid, $attempt);
 862          }
 863  
 864      }
 865  
 866  }
 867  
 868  function scorm_seq_activity_progress_rollup ($sco, $userid, $seq) {
 869  
 870      if (scorm_seq_rollup_rule_check($sco, $userid, 'incomplete')) {
 871          // Incomplete rollup action.
 872          scorm_seq_set('attemptcompletionstatus', $sco->id, $userid, $seq->attempt, false);
 873          scorm_seq_set('attemptprogressstatus', $sco->id, $userid, $seq->attempt, true);
 874  
 875      }
 876      if (scorm_seq_rollup_rule_check($sco, $userid, 'completed')) {
 877          // Incomplete rollup action.
 878          scorm_seq_set('attemptcompletionstatus', $sco->id, $userid, $seq->attempt, true);
 879          scorm_seq_set('attemptprogressstatus', $sco->id, $userid, $seq->attempt, true);
 880      }
 881  
 882  }
 883  
 884  function scorm_seq_rollup_rule_check ($sco, $userid, $action) {
 885      global $DB;
 886  
 887      if ($rolluprules = $DB->get_record('scorm_seq_rolluprule', array('scoid' => $sco->id, 'action' => $action))) {
 888          $childrenbag = Array ();
 889          $children = scorm_get_children ($sco);
 890  
 891          foreach ($rolluprules as $rolluprule) {
 892              foreach ($children as $child) {
 893  
 894                  /*$tracked = $DB->get_records('scorm_scoes_track', array('scoid'=>$child->id, 'userid'=>$userid));
 895                  if ($tracked && $tracked->attemp != 0) {*/
 896                  $child = scorm_get_sco ($child);
 897                  if (!isset($child->tracked) || ($child->tracked == 1)) {
 898                      if (scorm_seq_check_child ($child, $action, $userid)) {
 899                          $rollupruleconds = $DB->get_records('scorm_seq_rolluprulecond', array('rollupruleid' => $rolluprule->id));
 900                          $evaluate = scorm_seq_evaluate_rollupcond($child, $rolluprule->conditioncombination,
 901                                                                    $rollupruleconds, $userid);
 902                          if ($evaluate == 'unknown') {
 903                              array_push($childrenbag, 'unknown');
 904                          } else {
 905                              if ($evaluate == true) {
 906                                  array_push($childrenbag, true);
 907                              } else {
 908                                  array_push($childrenbag, false);
 909                              }
 910                          }
 911                      }
 912                  }
 913  
 914              }
 915              $change = false;
 916  
 917              switch ($rolluprule->childactivityset) {
 918  
 919                  case 'all':
 920                      // I think I can use this condition instead equivalent to OR.
 921                      if ((array_search(false, $childrenbag) === false) && (array_search('unknown', $childrenbag) === false)) {
 922                          $change = true;
 923                      }
 924                  break;
 925  
 926                  case 'any':
 927                      // I think I can use this condition instead equivalent to OR.
 928                      if (array_search(true, $childrenbag) !== false) {
 929                          $change = true;
 930                      }
 931                  break;
 932  
 933                  case 'none':
 934                      // I think I can use this condition instead equivalent to OR.
 935                      if ((array_search(true, $childrenbag) === false) && (array_search('unknown', $childrenbag) === false)) {
 936                          $change = true;
 937                      }
 938                  break;
 939  
 940                  case 'atleastcount':
 941                      // I think I can use this condition instead equivalent to OR.
 942                      foreach ($childrenbag as $itm) {
 943                          $cont = 0;
 944                          if ($itm === true) {
 945                              $cont++;
 946                          }
 947                          if ($cont >= $rolluprule->minimumcount) {
 948                              $change = true;
 949                          }
 950                      }
 951                  break;
 952  
 953                  case 'atleastcount':
 954                      foreach ($childrenbag as $itm) {// I think I can use this condition instead equivalent to OR.
 955                          $cont = 0;
 956                          if ($itm === true) {
 957                              $cont++;
 958                          }
 959                          if ($cont >= $rolluprule->minimumcount) {
 960                              $change = true;
 961                          }
 962                      }
 963                  break;
 964  
 965                  case 'atleastpercent':
 966                      foreach ($childrenbag as $itm) {// I think I can use this condition instead equivalent to OR.
 967                          $cont = 0;
 968                          if ($itm === true) {
 969                              $cont++;
 970                          }
 971                          if (($cont / count($childrenbag)) >= $rolluprule->minimumcount) {
 972                              $change = true;
 973                          }
 974                      }
 975                  break;
 976              }
 977              if ($change == true) {
 978                  return true;
 979              }
 980          }
 981      }
 982      return false;
 983  }
 984  
 985  function scorm_seq_flow_tree_traversal($activity, $direction, $childrenflag, $prevdirection, $seq, $userid, $skip = false) {
 986      $revdirection = false;
 987      $parent = scorm_get_parent($activity);
 988      if (!empty($parent)) {
 989          $children = scorm_get_available_children($parent);
 990      } else {
 991          $children = array();
 992      }
 993      $childrensize = count($children);
 994  
 995      if (($prevdirection != null && $prevdirection == 'backward') && ($children[$childrensize - 1]->id == $activity->id)) {
 996          $direction = 'backward';
 997          $activity = $children[0];
 998          $revdirection = true;
 999      }
1000  
1001      if ($direction == 'forward') {
1002          $ancestors = scorm_get_ancestors($activity);
1003          $ancestorsroot = array_reverse($ancestors);
1004          $preorder = array();
1005          $preorder = scorm_get_preorder($preorder, $ancestorsroot[0]);
1006          $preordersize = count($preorder);
1007          if (($activity->id == $preorder[$preordersize - 1]->id) || (($activity->parent == '/') && !($childrenflag))) {
1008              $seq->endsession = true;
1009              $seq->nextactivity = null;
1010              return $seq;
1011          }
1012          if (scorm_is_leaf ($activity) || !$childrenflag) {
1013              if ($children[$childrensize - 1]->id == $activity->id) {
1014                  $seq = scorm_seq_flow_tree_traversal ($parent, $direction, false, null, $seq, $userid);
1015                  if ($seq->nextactivity->launch == null) {
1016                      $seq = scorm_seq_flow_tree_traversal ($seq->nextactivity, $direction, true, null, $seq, $userid);
1017                  }
1018                  return $seq;
1019              } else {
1020                  $position = 0;
1021                  foreach ($children as $sco) {
1022                      if ($sco->id == $activity->id) {
1023                          break;
1024                      }
1025                      $position++;
1026                  }
1027                  if ($position != ($childrensize - 1)) {
1028                      $seq->nextactivity = $children[$position + 1];
1029                      $seq->traversaldir = $direction;
1030                      return $seq;
1031                  } else {
1032                      $siblings = scorm_get_siblings($activity);
1033                      $children = scorm_get_children($siblings[0]);
1034                      $seq->nextactivity = $children[0];
1035                      return $seq;
1036                  }
1037              }
1038          } else {
1039              $children = scorm_get_available_children($activity);
1040              if (!empty($children)) {
1041                  $seq->traversaldir = $direction;
1042                  $seq->nextactivity = $children[0];
1043                  return $seq;
1044              } else {
1045                  $seq->traversaldir = null;
1046                  $seq->nextactivity = null;
1047                  $seq->exception = 'SB.2.1-2';
1048                  return $seq;
1049              }
1050          }
1051      } else if ($direction == 'backward') {
1052          if ($activity->parent == '/') {
1053              $seq->traversaldir = null;
1054              $seq->nextactivity = null;
1055              $seq->exception = 'SB.2.1-3';
1056              return $seq;
1057          }
1058          if (scorm_is_leaf ($activity) || !$childrenflag) {
1059              if (!$revdirection) {
1060                  if (isset($parent->forwardonly) && ($parent->forwardonly == true && !$skip)) {
1061                      $seq->traversaldir = null;
1062                      $seq->nextactivity = null;
1063                      $seq->exception = 'SB.2.1-4';
1064                      return $seq;
1065                  }
1066              }
1067              if ($children[0]->id == $activity->id) {
1068                  $seq = scorm_seq_flow_tree_traversal($parent, 'backward', false, null, $seq, $userid);
1069                  return $seq;
1070              } else {
1071                  $ancestors = scorm_get_ancestors($activity);
1072                  $ancestorsroot = array_reverse($ancestors);
1073                  $preorder = array();
1074                  $preorder = scorm_get_preorder($preorder, $ancestorsroot[0]);
1075                  $position = 0;
1076                  foreach ($preorder as $sco) {
1077                      if ($sco->id == $activity->id) {
1078                          break;
1079                      }
1080                      $position++;
1081                  }
1082                  if (isset($preorder[$position])) {
1083                      $seq->nextactivity = $preorder[$position - 1];
1084                      $seq->traversaldir = $direction;
1085                  }
1086                  return $seq;
1087              }
1088          } else {
1089              $children = scorm_get_available_children($activity);
1090              if (!empty($children)) {
1091                  if (isset($parent->flow) && ($parent->flow == true)) {
1092                      $seq->traversaldir = 'forward';
1093                      $seq->nextactivity = $children[0];
1094                      return $seq;
1095                  } else {
1096                      $seq->traversaldir = 'backward';
1097                      $seq->nextactivity = $children[count($children) - 1];
1098                      return $seq;
1099                  }
1100              } else {
1101                  $seq->traversaldir = null;
1102                  $seq->nextactivity = null;
1103                  $seq->exception = 'SB.2.1-2';
1104                  return $seq;
1105              }
1106          }
1107      }
1108  }
1109  
1110  // Returns the next activity on the tree, traversal direction, control returned to the LTS, (may) exception.
1111  function scorm_seq_flow_activity_traversal ($activity, $userid, $direction, $childrenflag, $prevdirection, $seq) {
1112      $parent = scorm_get_parent ($activity);
1113      if (!isset($parent->flow) || ($parent->flow == false)) {
1114          $seq->deliverable = false;
1115          $seq->exception = 'SB.2.2-1';
1116          $seq->nextactivity = $activity;
1117          return $seq;
1118      }
1119  
1120      $rulecheck = scorm_seq_rules_check($activity, 'skip');
1121      if ($rulecheck != null) {
1122          $skip = scorm_evaluate_condition ($rulecheck, $activity, $userid);
1123          if ($skip) {
1124              $seq = scorm_seq_flow_tree_traversal($activity, $direction, false, $prevdirection, $seq, $userid, $skip);
1125              $seq = scorm_seq_flow_activity_traversal($seq->nextactivity, $userid, $direction,
1126                                                       $childrenflag, $prevdirection, $seq);
1127          } else if (!empty($seq->identifiedactivity)) {
1128              $seq->nextactivity = $activity;
1129          }
1130          return $seq;
1131      }
1132  
1133      $ch = scorm_check_activity ($activity, $userid);
1134      if ($ch) {
1135          $seq->deliverable = false;
1136          $seq->exception = 'SB.2.2-2';
1137          $seq->nextactivity = $activity;
1138          return $seq;
1139      }
1140  
1141      if (!scorm_is_leaf($activity)) {
1142          $seq = scorm_seq_flow_tree_traversal ($activity, $direction, true, null, $seq, $userid);
1143          if ($seq->identifiedactivity == null) {
1144              $seq->deliverable = false;
1145              $seq->nextactivity = $activity;
1146              return $seq;
1147          } else {
1148              if ($direction == 'backward' && $seq->traversaldir == 'forward') {
1149                  $seq = scorm_seq_flow_activity_traversal($seq->identifiedactivity, $userid,
1150                                                           'forward', $childrenflag, 'backward', $seq);
1151              } else {
1152                  $seq = scorm_seq_flow_activity_traversal($seq->identifiedactivity, $userid,
1153                                                           $direction, $childrenflag, null, $seq);
1154              }
1155              return $seq;
1156          }
1157  
1158      }
1159  
1160      $seq->deliverable = true;
1161      $seq->nextactivity = $activity;
1162      $seq->exception = null;
1163      return $seq;
1164  
1165  }
1166  
1167  function scorm_seq_flow ($activity, $direction, $seq, $childrenflag, $userid) {
1168      // TODO: $PREVDIRECTION NOT DEFINED YET.
1169      $prevdirection = null;
1170      $seq = scorm_seq_flow_tree_traversal ($activity, $direction, $childrenflag, $prevdirection, $seq, $userid);
1171      if ($seq->nextactivity == null) {
1172          $seq->nextactivity = $activity;
1173          $seq->deliverable = false;
1174          return $seq;
1175      } else {
1176          $activity = $seq->nextactivity;
1177          $seq = scorm_seq_flow_activity_traversal($activity, $userid, $direction, $childrenflag, null, $seq);
1178          return $seq;
1179      }
1180  }
1181  
1182  /**
1183   * Sets up $userdata array and default values for SCORM 1.3 .
1184   *
1185   * @param stdClass $userdata an empty stdClass variable that should be set up with user values
1186   * @param object $scorm package record
1187   * @param string $scoid SCO Id
1188   * @param string $attempt attempt number for the user
1189   * @param string $mode scorm display mode type
1190   * @return array The default values that should be used for SCORM 1.3 package
1191   */
1192  function get_scorm_default (&$userdata, $scorm, $scoid, $attempt, $mode) {
1193      global $DB, $USER;
1194  
1195      $userdata->student_id = $USER->username;
1196      if (empty(get_config('scorm', 'scormstandard'))) {
1197          $userdata->student_name = fullname($USER);
1198      } else {
1199          $userdata->student_name = $USER->lastname .', '. $USER->firstname;
1200      }
1201  
1202      if ($usertrack = scorm_get_tracks($scoid, $USER->id, $attempt)) {
1203          // According to SCORM 2004(RTE V1, 4.2.8), only cmi.exit==suspend should allow previous datamodel elements on re-launch.
1204          if (isset($usertrack->{'cmi.exit'}) && ($usertrack->{'cmi.exit'} == 'suspend')) {
1205              foreach ($usertrack as $key => $value) {
1206                  $userdata->$key = $value;
1207              }
1208          } else {
1209              $userdata->status = '';
1210              $userdata->score_raw = '';
1211          }
1212      } else {
1213          $userdata->status = '';
1214          $userdata->score_raw = '';
1215      }
1216  
1217      if ($scodatas = scorm_get_sco($scoid, SCO_DATA)) {
1218          foreach ($scodatas as $key => $value) {
1219              $userdata->$key = $value;
1220          }
1221      } else {
1222          throw new \moodle_exception('cannotfindsco', 'scorm');
1223      }
1224      if (!$sco = scorm_get_sco($scoid)) {
1225          throw new \moodle_exception('cannotfindsco', 'scorm');
1226      }
1227  
1228      if (isset($userdata->status)) {
1229          if (!isset($userdata->{'cmi.exit'}) || $userdata->{'cmi.exit'} == 'time-out' || $userdata->{'cmi.exit'} == 'normal') {
1230                  $userdata->entry = 'ab-initio';
1231          } else {
1232              if (isset($userdata->{'cmi.exit'}) && ($userdata->{'cmi.exit'} == 'suspend' || $userdata->{'cmi.exit'} == 'logout')) {
1233                  $userdata->entry = 'resume';
1234              } else {
1235                  $userdata->entry = '';
1236              }
1237          }
1238      }
1239  
1240      $userdata->mode = 'normal';
1241      if (!empty($mode)) {
1242          $userdata->mode = $mode;
1243      }
1244      if ($userdata->mode == 'normal') {
1245          $userdata->credit = 'credit';
1246      } else {
1247          $userdata->credit = 'no-credit';
1248      }
1249  
1250      $objectives = $DB->get_records('scorm_seq_objective', array('scoid' => $scoid));
1251      $index = 0;
1252      foreach ($objectives as $objective) {
1253          if (!empty($objective->minnormalizedmeasure)) {
1254              $userdata->{'cmi.scaled_passing_score'} = $objective->minnormalizedmeasure;
1255          }
1256          if (!empty($objective->objectiveid)) {
1257              $userdata->{'cmi.objectives.N'.$index.'.id'} = $objective->objectiveid;
1258              $index++;
1259          }
1260      }
1261  
1262      $def = array();
1263      $def['cmi.learner_id'] = $userdata->student_id;
1264      $def['cmi.learner_name'] = $userdata->student_name;
1265      $def['cmi.mode'] = $userdata->mode;
1266      $def['cmi.entry'] = $userdata->entry;
1267      $def['cmi.exit'] = scorm_isset($userdata, 'cmi.exit');
1268      $def['cmi.credit'] = scorm_isset($userdata, 'credit');
1269      $def['cmi.completion_status'] = scorm_isset($userdata, 'cmi.completion_status', 'unknown');
1270      $def['cmi.completion_threshold'] = scorm_isset($userdata, 'threshold');
1271      $def['cmi.learner_preference.audio_level'] = scorm_isset($userdata, 'cmi.learner_preference.audio_level', 1);
1272      $def['cmi.learner_preference.language'] = scorm_isset($userdata, 'cmi.learner_preference.language');
1273      $def['cmi.learner_preference.delivery_speed'] = scorm_isset($userdata, 'cmi.learner_preference.delivery_speed');
1274      $def['cmi.learner_preference.audio_captioning'] = scorm_isset($userdata, 'cmi.learner_preference.audio_captioning', 0);
1275      $def['cmi.location'] = scorm_isset($userdata, 'cmi.location');
1276      $def['cmi.max_time_allowed'] = scorm_isset($userdata, 'attemptAbsoluteDurationLimit');
1277      $def['cmi.progress_measure'] = scorm_isset($userdata, 'cmi.progress_measure');
1278      $def['cmi.scaled_passing_score'] = scorm_isset($userdata, 'cmi.scaled_passing_score');
1279      $def['cmi.score.scaled'] = scorm_isset($userdata, 'cmi.score.scaled');
1280      $def['cmi.score.raw'] = scorm_isset($userdata, 'cmi.score.raw');
1281      $def['cmi.score.min'] = scorm_isset($userdata, 'cmi.score.min');
1282      $def['cmi.score.max'] = scorm_isset($userdata, 'cmi.score.max');
1283      $def['cmi.success_status'] = scorm_isset($userdata, 'cmi.success_status', 'unknown');
1284      $def['cmi.suspend_data'] = scorm_isset($userdata, 'cmi.suspend_data');
1285      $def['cmi.time_limit_action'] = scorm_isset($userdata, 'timelimitaction');
1286      $def['cmi.total_time'] = scorm_isset($userdata, 'cmi.total_time', 'PT0H0M0S');
1287      $def['cmi.launch_data'] = scorm_isset($userdata, 'datafromlms');
1288  
1289      return $def;
1290  }