Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 9 May 2022 (12 months).
  • Bug fixes for security issues in 3.11.x will end 14 November 2022 (18 months).
  • 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  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          print_error('cannotfindsco', 'scorm');
    1223      }
    1224      if (!$sco = scorm_get_sco($scoid)) {
    1225          print_error('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  }