Search moodle.org's
Developer Documentation

See Release Notes

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

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