[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/mod/scorm/ -> locallib.php (source)

   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  /**
  18   * Library of internal classes and functions for module SCORM
  19   *
  20   * @package    mod_scorm
  21   * @copyright  1999 onwards Roberto Pinna
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  require_once("$CFG->dirroot/mod/scorm/lib.php");
  26  require_once("$CFG->libdir/filelib.php");
  27  
  28  // Constants and settings for module scorm.
  29  define('SCORM_UPDATE_NEVER', '0');
  30  define('SCORM_UPDATE_EVERYDAY', '2');
  31  define('SCORM_UPDATE_EVERYTIME', '3');
  32  
  33  define('SCORM_SKIPVIEW_NEVER', '0');
  34  define('SCORM_SKIPVIEW_FIRST', '1');
  35  define('SCORM_SKIPVIEW_ALWAYS', '2');
  36  
  37  define('SCO_ALL', 0);
  38  define('SCO_DATA', 1);
  39  define('SCO_ONLY', 2);
  40  
  41  define('GRADESCOES', '0');
  42  define('GRADEHIGHEST', '1');
  43  define('GRADEAVERAGE', '2');
  44  define('GRADESUM', '3');
  45  
  46  define('HIGHESTATTEMPT', '0');
  47  define('AVERAGEATTEMPT', '1');
  48  define('FIRSTATTEMPT', '2');
  49  define('LASTATTEMPT', '3');
  50  
  51  define('TOCJSLINK', 1);
  52  define('TOCFULLURL', 2);
  53  
  54  // Local Library of functions for module scorm.
  55  
  56  /**
  57   * @package   mod_scorm
  58   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  59   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  60   */
  61  class scorm_package_file_info extends file_info_stored {
  62      public function get_parent() {
  63          if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') {
  64              return $this->browser->get_file_info($this->context);
  65          }
  66          return parent::get_parent();
  67      }
  68      public function get_visible_name() {
  69          if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') {
  70              return $this->topvisiblename;
  71          }
  72          return parent::get_visible_name();
  73      }
  74  }
  75  
  76  /**
  77   * Returns an array of the popup options for SCORM and each options default value
  78   *
  79   * @return array an array of popup options as the key and their defaults as the value
  80   */
  81  function scorm_get_popup_options_array() {
  82      $cfgscorm = get_config('scorm');
  83  
  84      return array('scrollbars' => isset($cfgscorm->scrollbars) ? $cfgscorm->scrollbars : 0,
  85                   'directories' => isset($cfgscorm->directories) ? $cfgscorm->directories : 0,
  86                   'location' => isset($cfgscorm->location) ? $cfgscorm->location : 0,
  87                   'menubar' => isset($cfgscorm->menubar) ? $cfgscorm->menubar : 0,
  88                   'toolbar' => isset($cfgscorm->toolbar) ? $cfgscorm->toolbar : 0,
  89                   'status' => isset($cfgscorm->status) ? $cfgscorm->status : 0);
  90  }
  91  
  92  /**
  93   * Returns an array of the array of what grade options
  94   *
  95   * @return array an array of what grade options
  96   */
  97  function scorm_get_grade_method_array() {
  98      return array (GRADESCOES => get_string('gradescoes', 'scorm'),
  99                    GRADEHIGHEST => get_string('gradehighest', 'scorm'),
 100                    GRADEAVERAGE => get_string('gradeaverage', 'scorm'),
 101                    GRADESUM => get_string('gradesum', 'scorm'));
 102  }
 103  
 104  /**
 105   * Returns an array of the array of what grade options
 106   *
 107   * @return array an array of what grade options
 108   */
 109  function scorm_get_what_grade_array() {
 110      return array (HIGHESTATTEMPT => get_string('highestattempt', 'scorm'),
 111                    AVERAGEATTEMPT => get_string('averageattempt', 'scorm'),
 112                    FIRSTATTEMPT => get_string('firstattempt', 'scorm'),
 113                    LASTATTEMPT => get_string('lastattempt', 'scorm'));
 114  }
 115  
 116  /**
 117   * Returns an array of the array of skip view options
 118   *
 119   * @return array an array of skip view options
 120   */
 121  function scorm_get_skip_view_array() {
 122      return array(SCORM_SKIPVIEW_NEVER => get_string('never'),
 123                   SCORM_SKIPVIEW_FIRST => get_string('firstaccess', 'scorm'),
 124                   SCORM_SKIPVIEW_ALWAYS => get_string('always'));
 125  }
 126  
 127  /**
 128   * Returns an array of the array of hide table of contents options
 129   *
 130   * @return array an array of hide table of contents options
 131   */
 132  function scorm_get_hidetoc_array() {
 133       return array(SCORM_TOC_SIDE => get_string('sided', 'scorm'),
 134                    SCORM_TOC_HIDDEN => get_string('hidden', 'scorm'),
 135                    SCORM_TOC_POPUP => get_string('popupmenu', 'scorm'),
 136                    SCORM_TOC_DISABLED => get_string('disabled', 'scorm'));
 137  }
 138  
 139  /**
 140   * Returns an array of the array of update frequency options
 141   *
 142   * @return array an array of update frequency options
 143   */
 144  function scorm_get_updatefreq_array() {
 145      return array(SCORM_UPDATE_NEVER => get_string('never'),
 146                   SCORM_UPDATE_EVERYDAY => get_string('everyday', 'scorm'),
 147                   SCORM_UPDATE_EVERYTIME => get_string('everytime', 'scorm'));
 148  }
 149  
 150  /**
 151   * Returns an array of the array of popup display options
 152   *
 153   * @return array an array of popup display options
 154   */
 155  function scorm_get_popup_display_array() {
 156      return array(0 => get_string('currentwindow', 'scorm'),
 157                   1 => get_string('popup', 'scorm'));
 158  }
 159  
 160  /**
 161   * Returns an array of the array of navigation buttons display options
 162   *
 163   * @return array an array of navigation buttons display options
 164   */
 165  function scorm_get_navigation_display_array() {
 166      return array(SCORM_NAV_DISABLED => get_string('no'),
 167                   SCORM_NAV_UNDER_CONTENT => get_string('undercontent', 'scorm'),
 168                   SCORM_NAV_FLOATING => get_string('floating', 'scorm'));
 169  }
 170  
 171  /**
 172   * Returns an array of the array of attempt options
 173   *
 174   * @return array an array of attempt options
 175   */
 176  function scorm_get_attempts_array() {
 177      $attempts = array(0 => get_string('nolimit', 'scorm'),
 178                        1 => get_string('attempt1', 'scorm'));
 179  
 180      for ($i = 2; $i <= 6; $i++) {
 181          $attempts[$i] = get_string('attemptsx', 'scorm', $i);
 182      }
 183  
 184      return $attempts;
 185  }
 186  
 187  /**
 188   * Returns an array of the attempt status options
 189   *
 190   * @return array an array of attempt status options
 191   */
 192  function scorm_get_attemptstatus_array() {
 193      return array(SCORM_DISPLAY_ATTEMPTSTATUS_NO => get_string('no'),
 194                   SCORM_DISPLAY_ATTEMPTSTATUS_ALL => get_string('attemptstatusall', 'scorm'),
 195                   SCORM_DISPLAY_ATTEMPTSTATUS_MY => get_string('attemptstatusmy', 'scorm'),
 196                   SCORM_DISPLAY_ATTEMPTSTATUS_ENTRY => get_string('attemptstatusentry', 'scorm'));
 197  }
 198  
 199  /**
 200   * Extracts scrom package, sets up all variables.
 201   * Called whenever scorm changes
 202   * @param object $scorm instance - fields are updated and changes saved into database
 203   * @param bool $full force full update if true
 204   * @return void
 205   */
 206  function scorm_parse($scorm, $full) {
 207      global $CFG, $DB;
 208      $cfgscorm = get_config('scorm');
 209  
 210      if (!isset($scorm->cmid)) {
 211          $cm = get_coursemodule_from_instance('scorm', $scorm->id);
 212          $scorm->cmid = $cm->id;
 213      }
 214      $context = context_module::instance($scorm->cmid);
 215      $newhash = $scorm->sha1hash;
 216  
 217      if ($scorm->scormtype === SCORM_TYPE_LOCAL or $scorm->scormtype === SCORM_TYPE_LOCALSYNC) {
 218  
 219          $fs = get_file_storage();
 220          $packagefile = false;
 221          $packagefileimsmanifest = false;
 222  
 223          if ($scorm->scormtype === SCORM_TYPE_LOCAL) {
 224              if ($packagefile = $fs->get_file($context->id, 'mod_scorm', 'package', 0, '/', $scorm->reference)) {
 225                  if ($packagefile->is_external_file()) { // Get zip file so we can check it is correct.
 226                      $packagefile->import_external_file_contents();
 227                  }
 228                  $newhash = $packagefile->get_contenthash();
 229                  if (strtolower($packagefile->get_filename()) == 'imsmanifest.xml') {
 230                      $packagefileimsmanifest = true;
 231                  }
 232              } else {
 233                  $newhash = null;
 234              }
 235          } else {
 236              if (!$cfgscorm->allowtypelocalsync) {
 237                  // Sorry - localsync disabled.
 238                  return;
 239              }
 240              if ($scorm->reference !== '' and (!$full or $scorm->sha1hash !== sha1($scorm->reference))) {
 241                  $fs->delete_area_files($context->id, 'mod_scorm', 'package');
 242                  $filerecord = array('contextid' => $context->id, 'component' => 'mod_scorm', 'filearea' => 'package',
 243                                      'itemid' => 0, 'filepath' => '/');
 244                  if ($packagefile = $fs->create_file_from_url($filerecord, $scorm->reference, array('calctimeout' => true))) {
 245                      $newhash = sha1($scorm->reference);
 246                  } else {
 247                      $newhash = null;
 248                  }
 249              }
 250          }
 251  
 252          if ($packagefile) {
 253              if (!$full and $packagefile and $scorm->sha1hash === $newhash) {
 254                  if (strpos($scorm->version, 'SCORM') !== false) {
 255                      if ($packagefileimsmanifest || $fs->get_file($context->id, 'mod_scorm', 'content', 0, '/', 'imsmanifest.xml')) {
 256                          // No need to update.
 257                          return;
 258                      }
 259                  } else if (strpos($scorm->version, 'AICC') !== false) {
 260                      // TODO: add more sanity checks - something really exists in scorm_content area.
 261                      return;
 262                  }
 263              }
 264              if (!$packagefileimsmanifest) {
 265                  // Now extract files.
 266                  $fs->delete_area_files($context->id, 'mod_scorm', 'content');
 267  
 268                  $packer = get_file_packer('application/zip');
 269                  $packagefile->extract_to_storage($packer, $context->id, 'mod_scorm', 'content', 0, '/');
 270              }
 271  
 272          } else if (!$full) {
 273              return;
 274          }
 275          if ($packagefileimsmanifest) {
 276              require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php");
 277              // Direct link to imsmanifest.xml file.
 278              if (!scorm_parse_scorm($scorm, $packagefile)) {
 279                  $scorm->version = 'ERROR';
 280              }
 281  
 282          } else if ($manifest = $fs->get_file($context->id, 'mod_scorm', 'content', 0, '/', 'imsmanifest.xml')) {
 283              require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php");
 284              // SCORM.
 285              if (!scorm_parse_scorm($scorm, $manifest)) {
 286                  $scorm->version = 'ERROR';
 287              }
 288          } else {
 289              require_once("$CFG->dirroot/mod/scorm/datamodels/aicclib.php");
 290              // AICC.
 291              $result = scorm_parse_aicc($scorm);
 292              if (!$result) {
 293                  $scorm->version = 'ERROR';
 294              } else {
 295                  $scorm->version = 'AICC';
 296              }
 297          }
 298  
 299      } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL and $cfgscorm->allowtypeexternal) {
 300          require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php");
 301          // SCORM only, AICC can not be external.
 302          if (!scorm_parse_scorm($scorm, $scorm->reference)) {
 303              $scorm->version = 'ERROR';
 304          }
 305          $newhash = sha1($scorm->reference);
 306  
 307      } else if ($scorm->scormtype === SCORM_TYPE_AICCURL  and $cfgscorm->allowtypeexternalaicc) {
 308          require_once("$CFG->dirroot/mod/scorm/datamodels/aicclib.php");
 309          // AICC.
 310          $result = scorm_parse_aicc($scorm);
 311          if (!$result) {
 312              $scorm->version = 'ERROR';
 313          } else {
 314              $scorm->version = 'AICC';
 315          }
 316  
 317      } else {
 318          // Sorry, disabled type.
 319          return;
 320      }
 321  
 322      $scorm->revision++;
 323      $scorm->sha1hash = $newhash;
 324      $DB->update_record('scorm', $scorm);
 325  }
 326  
 327  
 328  function scorm_array_search($item, $needle, $haystacks, $strict=false) {
 329      if (!empty($haystacks)) {
 330          foreach ($haystacks as $key => $element) {
 331              if ($strict) {
 332                  if ($element->{$item} === $needle) {
 333                      return $key;
 334                  }
 335              } else {
 336                  if ($element->{$item} == $needle) {
 337                      return $key;
 338                  }
 339              }
 340          }
 341      }
 342      return false;
 343  }
 344  
 345  function scorm_repeater($what, $times) {
 346      if ($times <= 0) {
 347          return null;
 348      }
 349      $return = '';
 350      for ($i = 0; $i < $times; $i++) {
 351          $return .= $what;
 352      }
 353      return $return;
 354  }
 355  
 356  function scorm_external_link($link) {
 357      // Check if a link is external.
 358      $result = false;
 359      $link = strtolower($link);
 360      if (substr($link, 0, 7) == 'http://') {
 361          $result = true;
 362      } else if (substr($link, 0, 8) == 'https://') {
 363          $result = true;
 364      } else if (substr($link, 0, 4) == 'www.') {
 365          $result = true;
 366      }
 367      return $result;
 368  }
 369  
 370  /**
 371   * Returns an object containing all datas relative to the given sco ID
 372   *
 373   * @param integer $id The sco ID
 374   * @return mixed (false if sco id does not exists)
 375   */
 376  function scorm_get_sco($id, $what=SCO_ALL) {
 377      global $DB;
 378  
 379      if ($sco = $DB->get_record('scorm_scoes', array('id' => $id))) {
 380          $sco = ($what == SCO_DATA) ? new stdClass() : $sco;
 381          if (($what != SCO_ONLY) && ($scodatas = $DB->get_records('scorm_scoes_data', array('scoid' => $id)))) {
 382              foreach ($scodatas as $scodata) {
 383                  $sco->{$scodata->name} = $scodata->value;
 384              }
 385          } else if (($what != SCO_ONLY) && (!($scodatas = $DB->get_records('scorm_scoes_data', array('scoid' => $id))))) {
 386              $sco->parameters = '';
 387          }
 388          return $sco;
 389      } else {
 390          return false;
 391      }
 392  }
 393  
 394  /**
 395   * Returns an object (array) containing all the scoes data related to the given sco ID
 396   *
 397   * @param integer $id The sco ID
 398   * @param integer $organisation an organisation ID - defaults to false if not required
 399   * @return mixed (false if there are no scoes or an array)
 400   */
 401  function scorm_get_scoes($id, $organisation=false) {
 402      global $DB;
 403  
 404      $queryarray = array('scorm' => $id);
 405      if (!empty($organisation)) {
 406          $queryarray['organization'] = $organisation;
 407      }
 408      if ($scoes = $DB->get_records('scorm_scoes', $queryarray, 'sortorder, id')) {
 409          // Drop keys so that it is a simple array as expected.
 410          $scoes = array_values($scoes);
 411          foreach ($scoes as $sco) {
 412              if ($scodatas = $DB->get_records('scorm_scoes_data', array('scoid' => $sco->id))) {
 413                  foreach ($scodatas as $scodata) {
 414                      $sco->{$scodata->name} = $scodata->value;
 415                  }
 416              }
 417          }
 418          return $scoes;
 419      } else {
 420          return false;
 421      }
 422  }
 423  
 424  function scorm_insert_track($userid, $scormid, $scoid, $attempt, $element, $value, $forcecompleted=false, $trackdata = null) {
 425      global $DB, $CFG;
 426  
 427      $id = null;
 428  
 429      if ($forcecompleted) {
 430          // TODO - this could be broadened to encompass SCORM 2004 in future.
 431          if (($element == 'cmi.core.lesson_status') && ($value == 'incomplete')) {
 432              if ($track = $DB->get_record_select('scorm_scoes_track',
 433                                                  'userid=? AND scormid=? AND scoid=? AND attempt=? '.
 434                                                  'AND element=\'cmi.core.score.raw\'',
 435                                                  array($userid, $scormid, $scoid, $attempt))) {
 436                  $value = 'completed';
 437              }
 438          }
 439          if ($element == 'cmi.core.score.raw') {
 440              if ($tracktest = $DB->get_record_select('scorm_scoes_track',
 441                                                      'userid=? AND scormid=? AND scoid=? AND attempt=? '.
 442                                                      'AND element=\'cmi.core.lesson_status\'',
 443                                                      array($userid, $scormid, $scoid, $attempt))) {
 444                  if ($tracktest->value == "incomplete") {
 445                      $tracktest->value = "completed";
 446                      $DB->update_record('scorm_scoes_track', $tracktest);
 447                  }
 448              }
 449          }
 450          if (($element == 'cmi.success_status') && ($value == 'passed' || $value == 'failed')) {
 451              if ($DB->get_record('scorm_scoes_data', array('scoid' => $scoid, 'name' => 'objectivesetbycontent'))) {
 452                  $objectiveprogressstatus = true;
 453                  $objectivesatisfiedstatus = false;
 454                  if ($value == 'passed') {
 455                      $objectivesatisfiedstatus = true;
 456                  }
 457  
 458                  if ($track = $DB->get_record('scorm_scoes_track', array('userid' => $userid,
 459                                                                          'scormid' => $scormid,
 460                                                                          'scoid' => $scoid,
 461                                                                          'attempt' => $attempt,
 462                                                                          'element' => 'objectiveprogressstatus'))) {
 463                      $track->value = $objectiveprogressstatus;
 464                      $track->timemodified = time();
 465                      $DB->update_record('scorm_scoes_track', $track);
 466                      $id = $track->id;
 467                  } else {
 468                      $track = new stdClass();
 469                      $track->userid = $userid;
 470                      $track->scormid = $scormid;
 471                      $track->scoid = $scoid;
 472                      $track->attempt = $attempt;
 473                      $track->element = 'objectiveprogressstatus';
 474                      $track->value = $objectiveprogressstatus;
 475                      $track->timemodified = time();
 476                      $id = $DB->insert_record('scorm_scoes_track', $track);
 477                  }
 478                  if ($objectivesatisfiedstatus) {
 479                      if ($track = $DB->get_record('scorm_scoes_track', array('userid' => $userid,
 480                                                                              'scormid' => $scormid,
 481                                                                              'scoid' => $scoid,
 482                                                                              'attempt' => $attempt,
 483                                                                              'element' => 'objectivesatisfiedstatus'))) {
 484                          $track->value = $objectivesatisfiedstatus;
 485                          $track->timemodified = time();
 486                          $DB->update_record('scorm_scoes_track', $track);
 487                          $id = $track->id;
 488                      } else {
 489                          $track = new stdClass();
 490                          $track->userid = $userid;
 491                          $track->scormid = $scormid;
 492                          $track->scoid = $scoid;
 493                          $track->attempt = $attempt;
 494                          $track->element = 'objectivesatisfiedstatus';
 495                          $track->value = $objectivesatisfiedstatus;
 496                          $track->timemodified = time();
 497                          $id = $DB->insert_record('scorm_scoes_track', $track);
 498                          ob_start();
 499                          $filepath = $CFG->dataroot."\\temp\\tempfile.txt";
 500                          $fh = fopen($filepath, "a+");
 501                          var_dump($track);
 502                          $string = ob_get_clean();
 503                          fwrite($fh, $string);
 504                          fclose($fh);
 505                      }
 506                  }
 507              }
 508          }
 509  
 510      }
 511  
 512      $track = null;
 513      if ($trackdata !== null) {
 514          if (isset($trackdata[$element])) {
 515              $track = $trackdata[$element];
 516          }
 517      } else {
 518          $track = $DB->get_record('scorm_scoes_track', array('userid' => $userid,
 519                                                              'scormid' => $scormid,
 520                                                              'scoid' => $scoid,
 521                                                              'attempt' => $attempt,
 522                                                              'element' => $element));
 523      }
 524      if ($track) {
 525          if ($element != 'x.start.time' ) { // Don't update x.start.time - keep the original value.
 526              if ($track->value != $value) {
 527                  $track->value = $value;
 528                  $track->timemodified = time();
 529                  $DB->update_record('scorm_scoes_track', $track);
 530              }
 531              $id = $track->id;
 532          }
 533      } else {
 534          $track = new stdClass();
 535          $track->userid = $userid;
 536          $track->scormid = $scormid;
 537          $track->scoid = $scoid;
 538          $track->attempt = $attempt;
 539          $track->element = $element;
 540          $track->value = $value;
 541          $track->timemodified = time();
 542          $id = $DB->insert_record('scorm_scoes_track', $track);
 543      }
 544  
 545      if (strstr($element, '.score.raw') ||
 546          (in_array($element, array('cmi.completion_status', 'cmi.core.lesson_status', 'cmi.success_status'))
 547           && in_array($track->value, array('completed', 'passed')))) {
 548          $scorm = $DB->get_record('scorm', array('id' => $scormid));
 549          include_once($CFG->dirroot.'/mod/scorm/lib.php');
 550          scorm_update_grades($scorm, $userid);
 551      }
 552  
 553      return $id;
 554  }
 555  
 556  /**
 557   * simple quick function to return true/false if this user has tracks in this scorm
 558   *
 559   * @param integer $scormid The scorm ID
 560   * @param integer $userid the users id
 561   * @return boolean (false if there are no tracks)
 562   */
 563  function scorm_has_tracks($scormid, $userid) {
 564      global $DB;
 565      return $DB->record_exists('scorm_scoes_track', array('userid' => $userid, 'scormid' => $scormid));
 566  }
 567  
 568  function scorm_get_tracks($scoid, $userid, $attempt='') {
 569      // Gets all tracks of specified sco and user.
 570      global $DB;
 571  
 572      if (empty($attempt)) {
 573          if ($scormid = $DB->get_field('scorm_scoes', 'scorm', array('id' => $scoid))) {
 574              $attempt = scorm_get_last_attempt($scormid, $userid);
 575          } else {
 576              $attempt = 1;
 577          }
 578      }
 579      if ($tracks = $DB->get_records('scorm_scoes_track', array('userid' => $userid, 'scoid' => $scoid,
 580                                                                'attempt' => $attempt), 'element ASC')) {
 581          $usertrack = scorm_format_interactions($tracks);
 582          $usertrack->userid = $userid;
 583          $usertrack->scoid = $scoid;
 584  
 585          return $usertrack;
 586      } else {
 587          return false;
 588      }
 589  }
 590  /**
 591   * helper function to return a formatted list of interactions for reports.
 592   *
 593   * @param array $trackdata the records from scorm_scoes_track table
 594   * @return object formatted list of interactions
 595   */
 596  function scorm_format_interactions($trackdata) {
 597      $usertrack = new stdClass();
 598  
 599      // Defined in order to unify scorm1.2 and scorm2004.
 600      $usertrack->score_raw = '';
 601      $usertrack->status = '';
 602      $usertrack->total_time = '00:00:00';
 603      $usertrack->session_time = '00:00:00';
 604      $usertrack->timemodified = 0;
 605  
 606      foreach ($trackdata as $track) {
 607          $element = $track->element;
 608          $usertrack->{$element} = $track->value;
 609          switch ($element) {
 610              case 'cmi.core.lesson_status':
 611              case 'cmi.completion_status':
 612                  if ($track->value == 'not attempted') {
 613                      $track->value = 'notattempted';
 614                  }
 615                  $usertrack->status = $track->value;
 616                  break;
 617              case 'cmi.core.score.raw':
 618              case 'cmi.score.raw':
 619                  $usertrack->score_raw = (float) sprintf('%2.2f', $track->value);
 620                  break;
 621              case 'cmi.core.session_time':
 622              case 'cmi.session_time':
 623                  $usertrack->session_time = $track->value;
 624                  break;
 625              case 'cmi.core.total_time':
 626              case 'cmi.total_time':
 627                  $usertrack->total_time = $track->value;
 628                  break;
 629          }
 630          if (isset($track->timemodified) && ($track->timemodified > $usertrack->timemodified)) {
 631              $usertrack->timemodified = $track->timemodified;
 632          }
 633      }
 634  
 635      return $usertrack;
 636  }
 637  /* Find the start and finsh time for a a given SCO attempt
 638   *
 639   * @param int $scormid SCORM Id
 640   * @param int $scoid SCO Id
 641   * @param int $userid User Id
 642   * @param int $attemt Attempt Id
 643   *
 644   * @return object start and finsh time EPOC secods
 645   *
 646   */
 647  function scorm_get_sco_runtime($scormid, $scoid, $userid, $attempt=1) {
 648      global $DB;
 649  
 650      $timedata = new stdClass();
 651      $params = array('userid' => $userid, 'scormid' => $scormid, 'attempt' => $attempt);
 652      if (!empty($scoid)) {
 653          $params['scoid'] = $scoid;
 654      }
 655      $tracks = $DB->get_records('scorm_scoes_track', $params, "timemodified ASC");
 656      if ($tracks) {
 657          $tracks = array_values($tracks);
 658      }
 659  
 660      if ($tracks) {
 661          $timedata->start = $tracks[0]->timemodified;
 662      } else {
 663          $timedata->start = false;
 664      }
 665      if ($tracks && $track = array_pop($tracks)) {
 666          $timedata->finish = $track->timemodified;
 667      } else {
 668          $timedata->finish = $timedata->start;
 669      }
 670      return $timedata;
 671  }
 672  
 673  function scorm_grade_user_attempt($scorm, $userid, $attempt=1) {
 674      global $DB;
 675      $attemptscore = new stdClass();
 676      $attemptscore->scoes = 0;
 677      $attemptscore->values = 0;
 678      $attemptscore->max = 0;
 679      $attemptscore->sum = 0;
 680      $attemptscore->lastmodify = 0;
 681  
 682      if (!$scoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id), 'sortorder, id')) {
 683          return null;
 684      }
 685  
 686      foreach ($scoes as $sco) {
 687          if ($userdata = scorm_get_tracks($sco->id, $userid, $attempt)) {
 688              if (($userdata->status == 'completed') || ($userdata->status == 'passed')) {
 689                  $attemptscore->scoes++;
 690              }
 691              if (!empty($userdata->score_raw) || (isset($scorm->type) && $scorm->type == 'sco' && isset($userdata->score_raw))) {
 692                  $attemptscore->values++;
 693                  $attemptscore->sum += $userdata->score_raw;
 694                  $attemptscore->max = ($userdata->score_raw > $attemptscore->max) ? $userdata->score_raw : $attemptscore->max;
 695                  if (isset($userdata->timemodified) && ($userdata->timemodified > $attemptscore->lastmodify)) {
 696                      $attemptscore->lastmodify = $userdata->timemodified;
 697                  } else {
 698                      $attemptscore->lastmodify = 0;
 699                  }
 700              }
 701          }
 702      }
 703      switch ($scorm->grademethod) {
 704          case GRADEHIGHEST:
 705              $score = (float) $attemptscore->max;
 706          break;
 707          case GRADEAVERAGE:
 708              if ($attemptscore->values > 0) {
 709                  $score = $attemptscore->sum / $attemptscore->values;
 710              } else {
 711                  $score = 0;
 712              }
 713          break;
 714          case GRADESUM:
 715              $score = $attemptscore->sum;
 716          break;
 717          case GRADESCOES:
 718              $score = $attemptscore->scoes;
 719          break;
 720          default:
 721              $score = $attemptscore->max;   // Remote Learner GRADEHIGHEST is default.
 722      }
 723  
 724      return $score;
 725  }
 726  
 727  function scorm_grade_user($scorm, $userid) {
 728  
 729      // Ensure we dont grade user beyond $scorm->maxattempt settings.
 730      $lastattempt = scorm_get_last_attempt($scorm->id, $userid);
 731      if ($scorm->maxattempt != 0 && $lastattempt >= $scorm->maxattempt) {
 732          $lastattempt = $scorm->maxattempt;
 733      }
 734  
 735      switch ($scorm->whatgrade) {
 736          case FIRSTATTEMPT:
 737              return scorm_grade_user_attempt($scorm, $userid, 1);
 738          break;
 739          case LASTATTEMPT:
 740              return scorm_grade_user_attempt($scorm, $userid, scorm_get_last_completed_attempt($scorm->id, $userid));
 741          break;
 742          case HIGHESTATTEMPT:
 743              $maxscore = 0;
 744              for ($attempt = 1; $attempt <= $lastattempt; $attempt++) {
 745                  $attemptscore = scorm_grade_user_attempt($scorm, $userid, $attempt);
 746                  $maxscore = $attemptscore > $maxscore ? $attemptscore : $maxscore;
 747              }
 748              return $maxscore;
 749  
 750          break;
 751          case AVERAGEATTEMPT:
 752              $attemptcount = scorm_get_attempt_count($userid, $scorm, true, true);
 753              if (empty($attemptcount)) {
 754                  return 0;
 755              } else {
 756                  $attemptcount = count($attemptcount);
 757              }
 758              $lastattempt = scorm_get_last_attempt($scorm->id, $userid);
 759              $sumscore = 0;
 760              for ($attempt = 1; $attempt <= $lastattempt; $attempt++) {
 761                  $attemptscore = scorm_grade_user_attempt($scorm, $userid, $attempt);
 762                  $sumscore += $attemptscore;
 763              }
 764  
 765              return round($sumscore / $attemptcount);
 766          break;
 767      }
 768  }
 769  
 770  function scorm_count_launchable($scormid, $organization='') {
 771      global $DB;
 772  
 773      $sqlorganization = '';
 774      $params = array($scormid);
 775      if (!empty($organization)) {
 776          $sqlorganization = " AND organization=?";
 777          $params[] = $organization;
 778      }
 779      return $DB->count_records_select('scorm_scoes', "scorm = ? $sqlorganization AND ".
 780                                          $DB->sql_isnotempty('scorm_scoes', 'launch', false, true),
 781                                          $params);
 782  }
 783  
 784  /**
 785   * Returns the last attempt used - if no attempts yet, returns 1 for first attempt
 786   *
 787   * @param int $scormid the id of the scorm.
 788   * @param int $userid the id of the user.
 789   *
 790   * @return int The attempt number to use.
 791   */
 792  function scorm_get_last_attempt($scormid, $userid) {
 793      global $DB;
 794  
 795      // Find the last attempt number for the given user id and scorm id.
 796      $sql = "SELECT MAX(attempt)
 797                FROM {scorm_scoes_track}
 798               WHERE userid = ? AND scormid = ?";
 799      $lastattempt = $DB->get_field_sql($sql, array($userid, $scormid));
 800      if (empty($lastattempt)) {
 801          return '1';
 802      } else {
 803          return $lastattempt;
 804      }
 805  }
 806  
 807  /**
 808   * Returns the last completed attempt used - if no completed attempts yet, returns 1 for first attempt
 809   *
 810   * @param int $scormid the id of the scorm.
 811   * @param int $userid the id of the user.
 812   *
 813   * @return int The attempt number to use.
 814   */
 815  function scorm_get_last_completed_attempt($scormid, $userid) {
 816      global $DB;
 817  
 818      // Find the last completed attempt number for the given user id and scorm id.
 819      $sql = "SELECT MAX(attempt)
 820                FROM {scorm_scoes_track}
 821               WHERE userid = ? AND scormid = ?
 822                 AND (value='completed' OR value='passed')";
 823      $lastattempt = $DB->get_field_sql($sql, array($userid, $scormid));
 824      if (empty($lastattempt)) {
 825          return '1';
 826      } else {
 827          return $lastattempt;
 828      }
 829  }
 830  
 831  /**
 832   * Returns the full list of attempts a user has made.
 833   *
 834   * @param int $scormid the id of the scorm.
 835   * @param int $userid the id of the user.
 836   *
 837   * @return array array of attemptids
 838   */
 839  function scorm_get_all_attempts($scormid, $userid) {
 840      global $DB;
 841      $attemptids = array();
 842      $sql = "SELECT DISTINCT attempt FROM {scorm_scoes_track} WHERE userid = ? AND scormid = ? ORDER BY attempt";
 843      $attempts = $DB->get_records_sql($sql, array($userid, $scormid));
 844      foreach ($attempts as $attempt) {
 845          $attemptids[] = $attempt->attempt;
 846      }
 847      return $attemptids;
 848  }
 849  
 850  function scorm_view_display ($user, $scorm, $action, $cm) {
 851      global $CFG, $DB, $PAGE, $OUTPUT, $COURSE;
 852  
 853      if ($scorm->scormtype != SCORM_TYPE_LOCAL && $scorm->updatefreq == SCORM_UPDATE_EVERYTIME) {
 854          scorm_parse($scorm, false);
 855      }
 856  
 857      $organization = optional_param('organization', '', PARAM_INT);
 858  
 859      if ($scorm->displaycoursestructure == 1) {
 860          echo $OUTPUT->box_start('generalbox boxaligncenter toc', 'toc');
 861          echo html_writer::div(get_string('contents', 'scorm'), 'structurehead');
 862      }
 863      if (empty($organization)) {
 864          $organization = $scorm->launch;
 865      }
 866      if ($orgs = $DB->get_records_select_menu('scorm_scoes', 'scorm = ? AND '.
 867                                           $DB->sql_isempty('scorm_scoes', 'launch', false, true).' AND '.
 868                                           $DB->sql_isempty('scorm_scoes', 'organization', false, false),
 869                                           array($scorm->id), 'sortorder, id', 'id,title')) {
 870          if (count($orgs) > 1) {
 871              $select = new single_select(new moodle_url($action), 'organization', $orgs, $organization, null);
 872              $select->label = get_string('organizations', 'scorm');
 873              $select->class = 'scorm-center';
 874              echo $OUTPUT->render($select);
 875          }
 876      }
 877      $orgidentifier = '';
 878      if ($sco = scorm_get_sco($organization, SCO_ONLY)) {
 879          if (($sco->organization == '') && ($sco->launch == '')) {
 880              $orgidentifier = $sco->identifier;
 881          } else {
 882              $orgidentifier = $sco->organization;
 883          }
 884      }
 885  
 886      $scorm->version = strtolower(clean_param($scorm->version, PARAM_SAFEDIR));   // Just to be safe.
 887      if (!file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php')) {
 888          $scorm->version = 'scorm_12';
 889      }
 890      require_once($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php');
 891  
 892      $result = scorm_get_toc($user, $scorm, $cm->id, TOCFULLURL, $orgidentifier);
 893      $incomplete = $result->incomplete;
 894  
 895      // Do we want the TOC to be displayed?
 896      if ($scorm->displaycoursestructure == 1) {
 897          echo $result->toc;
 898          echo $OUTPUT->box_end();
 899      }
 900  
 901      // Is this the first attempt ?
 902      $attemptcount = scorm_get_attempt_count($user->id, $scorm);
 903  
 904      // Do not give the player launch FORM if the SCORM object is locked after the final attempt.
 905      if ($scorm->lastattemptlock == 0 || $result->attemptleft > 0) {
 906              echo html_writer::start_div('scorm-center');
 907              echo html_writer::start_tag('form', array('id' => 'scormviewform',
 908                                                          'method' => 'post',
 909                                                          'action' => $CFG->wwwroot.'/mod/scorm/player.php'));
 910          if ($scorm->hidebrowse == 0) {
 911              print_string('mode', 'scorm');
 912              echo ': '.html_writer::empty_tag('input', array('type' => 'radio', 'id' => 'b', 'name' => 'mode', 'value' => 'browse')).
 913                          html_writer::label(get_string('browse', 'scorm'), 'b');
 914              echo html_writer::empty_tag('input', array('type' => 'radio',
 915                                                          'id' => 'n', 'name' => 'mode',
 916                                                          'value' => 'normal', 'checked' => 'checked')).
 917                      html_writer::label(get_string('normal', 'scorm'), 'n');
 918  
 919          } else {
 920              echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'mode', 'value' => 'normal'));
 921          }
 922          if ($scorm->forcenewattempt == 1) {
 923              if ($incomplete === false) {
 924                  echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'newattempt', 'value' => 'on'));
 925              }
 926          } else if (!empty($attemptcount) && ($incomplete === false) && (($result->attemptleft > 0)||($scorm->maxattempt == 0))) {
 927                  echo html_writer::empty_tag('br');
 928                  echo html_writer::checkbox('newattempt', 'on', false, '', array('id' => 'a'));
 929                  echo html_writer::label(get_string('newattempt', 'scorm'), 'a');
 930          }
 931          if (!empty($scorm->popup)) {
 932              echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'display', 'value' => 'popup'));
 933          }
 934  
 935          echo html_writer::empty_tag('br');
 936          echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'scoid', 'value' => $scorm->launch));
 937          echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'cm', 'value' => $cm->id));
 938          echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'currentorg', 'value' => $orgidentifier));
 939          echo html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('enter', 'scorm')));
 940          echo html_writer::end_tag('form');
 941          echo html_writer::end_div();
 942      }
 943  }
 944  
 945  function scorm_simple_play($scorm, $user, $context, $cmid) {
 946      global $DB;
 947  
 948      $result = false;
 949  
 950      if (has_capability('mod/scorm:viewreport', $context)) {
 951          // If this user can view reports, don't skipview so they can see links to reports.
 952          return $result;
 953      }
 954  
 955      if ($scorm->scormtype != SCORM_TYPE_LOCAL && $scorm->updatefreq == SCORM_UPDATE_EVERYTIME) {
 956          scorm_parse($scorm, false);
 957      }
 958      $scoes = $DB->get_records_select('scorm_scoes', 'scorm = ? AND '.
 959          $DB->sql_isnotempty('scorm_scoes', 'launch', false, true), array($scorm->id), 'sortorder, id', 'id');
 960  
 961      if ($scoes) {
 962          $orgidentifier = '';
 963          if ($sco = scorm_get_sco($scorm->launch, SCO_ONLY)) {
 964              if (($sco->organization == '') && ($sco->launch == '')) {
 965                  $orgidentifier = $sco->identifier;
 966              } else {
 967                  $orgidentifier = $sco->organization;
 968              }
 969          }
 970          if ($scorm->skipview >= SCORM_SKIPVIEW_FIRST) {
 971              $sco = current($scoes);
 972              $url = new moodle_url('/mod/scorm/player.php', array('a' => $scorm->id,
 973                                                                  'currentorg' => $orgidentifier,
 974                                                                  'scoid' => $sco->id));
 975              if ($scorm->skipview == SCORM_SKIPVIEW_ALWAYS || !scorm_has_tracks($scorm->id, $user->id)) {
 976                  if (!empty($scorm->forcenewattempt)) {
 977                      $result = scorm_get_toc($user, $scorm, $cmid, TOCFULLURL, $orgidentifier);
 978                      if ($result->incomplete === false) {
 979                          $url->param('newattempt', 'on');
 980                      }
 981                  }
 982                  redirect($url);
 983              }
 984          }
 985      }
 986      return $result;
 987  }
 988  
 989  function scorm_get_count_users($scormid, $groupingid=null) {
 990      global $CFG, $DB;
 991  
 992      if (!empty($groupingid)) {
 993          $sql = "SELECT COUNT(DISTINCT st.userid)
 994                  FROM {scorm_scoes_track} st
 995                      INNER JOIN {groups_members} gm ON st.userid = gm.userid
 996                      INNER JOIN {groupings_groups} gg ON gm.groupid = gg.groupid
 997                  WHERE st.scormid = ? AND gg.groupingid = ?
 998                  ";
 999          $params = array($scormid, $groupingid);
1000      } else {
1001          $sql = "SELECT COUNT(DISTINCT st.userid)
1002                  FROM {scorm_scoes_track} st
1003                  WHERE st.scormid = ?
1004                  ";
1005          $params = array($scormid);
1006      }
1007  
1008      return ($DB->count_records_sql($sql, $params));
1009  }
1010  
1011  /**
1012   * Build up the JavaScript representation of an array element
1013   *
1014   * @param string $sversion SCORM API version
1015   * @param array $userdata User track data
1016   * @param string $elementname Name of array element to get values for
1017   * @param array $children list of sub elements of this array element that also need instantiating
1018   * @return Javascript array elements
1019   */
1020  function scorm_reconstitute_array_element($sversion, $userdata, $elementname, $children) {
1021      // Reconstitute comments_from_learner and comments_from_lms.
1022      $current = '';
1023      $currentsubelement = '';
1024      $currentsub = '';
1025      $count = 0;
1026      $countsub = 0;
1027      $scormseperator = '_';
1028      $return = '';
1029      if (scorm_version_check($sversion, SCORM_13)) { // Scorm 1.3 elements use a . instead of an _ .
1030          $scormseperator = '.';
1031      }
1032      // Filter out the ones we want.
1033      $elementlist = array();
1034      foreach ($userdata as $element => $value) {
1035          if (substr($element, 0, strlen($elementname)) == $elementname) {
1036              $elementlist[$element] = $value;
1037          }
1038      }
1039  
1040      // Sort elements in .n array order.
1041      uksort($elementlist, "scorm_element_cmp");
1042  
1043      // Generate JavaScript.
1044      foreach ($elementlist as $element => $value) {
1045          if (scorm_version_check($sversion, SCORM_13)) {
1046              $element = preg_replace('/\.(\d+)\./', ".N\$1.", $element);
1047              preg_match('/\.(N\d+)\./', $element, $matches);
1048          } else {
1049              $element = preg_replace('/\.(\d+)\./', "_\$1.", $element);
1050              preg_match('/\_(\d+)\./', $element, $matches);
1051          }
1052          if (count($matches) > 0 && $current != $matches[1]) {
1053              if ($countsub > 0) {
1054                  $return .= '    '.$elementname.$scormseperator.$current.'.'.$currentsubelement.'._count = '.$countsub.";\n";
1055              }
1056              $current = $matches[1];
1057              $count++;
1058              $currentsubelement = '';
1059              $currentsub = '';
1060              $countsub = 0;
1061              $end = strpos($element, $matches[1]) + strlen($matches[1]);
1062              $subelement = substr($element, 0, $end);
1063              $return .= '    '.$subelement." = new Object();\n";
1064              // Now add the children.
1065              foreach ($children as $child) {
1066                  $return .= '    '.$subelement.".".$child." = new Object();\n";
1067                  $return .= '    '.$subelement.".".$child."._children = ".$child."_children;\n";
1068              }
1069          }
1070  
1071          // Now - flesh out the second level elements if there are any.
1072          if (scorm_version_check($sversion, SCORM_13)) {
1073              $element = preg_replace('/(.*?\.N\d+\..*?)\.(\d+)\./', "\$1.N\$2.", $element);
1074              preg_match('/.*?\.N\d+\.(.*?)\.(N\d+)\./', $element, $matches);
1075          } else {
1076              $element = preg_replace('/(.*?\_\d+\..*?)\.(\d+)\./', "\$1_\$2.", $element);
1077              preg_match('/.*?\_\d+\.(.*?)\_(\d+)\./', $element, $matches);
1078          }
1079  
1080          // Check the sub element type.
1081          if (count($matches) > 0 && $currentsubelement != $matches[1]) {
1082              if ($countsub > 0) {
1083                  $return .= '    '.$elementname.$scormseperator.$current.'.'.$currentsubelement.'._count = '.$countsub.";\n";
1084              }
1085              $currentsubelement = $matches[1];
1086              $currentsub = '';
1087              $countsub = 0;
1088              $end = strpos($element, $matches[1]) + strlen($matches[1]);
1089              $subelement = substr($element, 0, $end);
1090              $return .= '    '.$subelement." = new Object();\n";
1091          }
1092  
1093          // Now check the subelement subscript.
1094          if (count($matches) > 0 && $currentsub != $matches[2]) {
1095              $currentsub = $matches[2];
1096              $countsub++;
1097              $end = strrpos($element, $matches[2]) + strlen($matches[2]);
1098              $subelement = substr($element, 0, $end);
1099              $return .= '    '.$subelement." = new Object();\n";
1100          }
1101  
1102          $return .= '    '.$element.' = \''.$value."';\n";
1103      }
1104      if ($countsub > 0) {
1105          $return .= '    '.$elementname.$scormseperator.$current.'.'.$currentsubelement.'._count = '.$countsub.";\n";
1106      }
1107      if ($count > 0) {
1108          $return .= '    '.$elementname.'._count = '.$count.";\n";
1109      }
1110      return $return;
1111  }
1112  
1113  /**
1114   * Build up the JavaScript representation of an array element
1115   *
1116   * @param string $a left array element
1117   * @param string $b right array element
1118   * @return comparator - 0,1,-1
1119   */
1120  function scorm_element_cmp($a, $b) {
1121      preg_match('/.*?(\d+)\./', $a, $matches);
1122      $left = intval($matches[1]);
1123      preg_match('/.?(\d+)\./', $b, $matches);
1124      $right = intval($matches[1]);
1125      if ($left < $right) {
1126          return -1; // Smaller.
1127      } else if ($left > $right) {
1128          return 1;  // Bigger.
1129      } else {
1130          // Look for a second level qualifier eg cmi.interactions_0.correct_responses_0.pattern.
1131          if (preg_match('/.*?(\d+)\.(.*?)\.(\d+)\./', $a, $matches)) {
1132              $leftterm = intval($matches[2]);
1133              $left = intval($matches[3]);
1134              if (preg_match('/.*?(\d+)\.(.*?)\.(\d+)\./', $b, $matches)) {
1135                  $rightterm = intval($matches[2]);
1136                  $right = intval($matches[3]);
1137                  if ($leftterm < $rightterm) {
1138                      return -1; // Smaller.
1139                  } else if ($leftterm > $rightterm) {
1140                      return 1;  // Bigger.
1141                  } else {
1142                      if ($left < $right) {
1143                          return -1; // Smaller.
1144                      } else if ($left > $right) {
1145                          return 1;  // Bigger.
1146                      }
1147                  }
1148              }
1149          }
1150          // Fall back for no second level matches or second level matches are equal.
1151          return 0;  // Equal to.
1152      }
1153  }
1154  
1155  /**
1156   * Generate the user attempt status string
1157   *
1158   * @param object $user Current context user
1159   * @param object $scorm a moodle scrom object - mdl_scorm
1160   * @return string - Attempt status string
1161   */
1162  function scorm_get_attempt_status($user, $scorm, $cm='') {
1163      global $DB, $PAGE, $OUTPUT;
1164  
1165      $attempts = scorm_get_attempt_count($user->id, $scorm, true);
1166      if (empty($attempts)) {
1167          $attemptcount = 0;
1168      } else {
1169          $attemptcount = count($attempts);
1170      }
1171  
1172      $result = html_writer::start_tag('p').get_string('noattemptsallowed', 'scorm').': ';
1173      if ($scorm->maxattempt > 0) {
1174          $result .= $scorm->maxattempt . html_writer::empty_tag('br');
1175      } else {
1176          $result .= get_string('unlimited').html_writer::empty_tag('br');
1177      }
1178      $result .= get_string('noattemptsmade', 'scorm').': ' . $attemptcount . html_writer::empty_tag('br');
1179  
1180      if ($scorm->maxattempt == 1) {
1181          switch ($scorm->grademethod) {
1182              case GRADEHIGHEST:
1183                  $grademethod = get_string('gradehighest', 'scorm');
1184              break;
1185              case GRADEAVERAGE:
1186                  $grademethod = get_string('gradeaverage', 'scorm');
1187              break;
1188              case GRADESUM:
1189                  $grademethod = get_string('gradesum', 'scorm');
1190              break;
1191              case GRADESCOES:
1192                  $grademethod = get_string('gradescoes', 'scorm');
1193              break;
1194          }
1195      } else {
1196          switch ($scorm->whatgrade) {
1197              case HIGHESTATTEMPT:
1198                  $grademethod = get_string('highestattempt', 'scorm');
1199              break;
1200              case AVERAGEATTEMPT:
1201                  $grademethod = get_string('averageattempt', 'scorm');
1202              break;
1203              case FIRSTATTEMPT:
1204                  $grademethod = get_string('firstattempt', 'scorm');
1205              break;
1206              case LASTATTEMPT:
1207                  $grademethod = get_string('lastattempt', 'scorm');
1208              break;
1209          }
1210      }
1211  
1212      if (!empty($attempts)) {
1213          $i = 1;
1214          foreach ($attempts as $attempt) {
1215              $gradereported = scorm_grade_user_attempt($scorm, $user->id, $attempt->attemptnumber);
1216              if ($scorm->grademethod !== GRADESCOES && !empty($scorm->maxgrade)) {
1217                  $gradereported = $gradereported / $scorm->maxgrade;
1218                  $gradereported = number_format($gradereported * 100, 0) .'%';
1219              }
1220              $result .= get_string('gradeforattempt', 'scorm').' ' . $i . ': ' . $gradereported .html_writer::empty_tag('br');
1221              $i++;
1222          }
1223      }
1224      $calculatedgrade = scorm_grade_user($scorm, $user->id);
1225      if ($scorm->grademethod !== GRADESCOES && !empty($scorm->maxgrade)) {
1226          $calculatedgrade = $calculatedgrade / $scorm->maxgrade;
1227          $calculatedgrade = number_format($calculatedgrade * 100, 0) .'%';
1228      }
1229      $result .= get_string('grademethod', 'scorm'). ': ' . $grademethod;
1230      if (empty($attempts)) {
1231          $result .= html_writer::empty_tag('br').get_string('gradereported', 'scorm').
1232                      ': '.get_string('none').html_writer::empty_tag('br');
1233      } else {
1234          $result .= html_writer::empty_tag('br').get_string('gradereported', 'scorm').
1235                      ': '.$calculatedgrade.html_writer::empty_tag('br');
1236      }
1237      $result .= html_writer::end_tag('p');
1238      if ($attemptcount >= $scorm->maxattempt and $scorm->maxattempt > 0) {
1239          $result .= html_writer::tag('p', get_string('exceededmaxattempts', 'scorm'), array('class' => 'exceededmaxattempts'));
1240      }
1241      if (!empty($cm)) {
1242          $context = context_module::instance($cm->id);
1243          if (has_capability('mod/scorm:deleteownresponses', $context) &&
1244              $DB->record_exists('scorm_scoes_track', array('userid' => $user->id, 'scormid' => $scorm->id))) {
1245              // Check to see if any data is stored for this user.
1246              $deleteurl = new moodle_url($PAGE->url, array('action' => 'delete', 'sesskey' => sesskey()));
1247              $result .= $OUTPUT->single_button($deleteurl, get_string('deleteallattempts', 'scorm'));
1248          }
1249      }
1250  
1251      return $result;
1252  }
1253  
1254  /**
1255   * Get SCORM attempt count
1256   *
1257   * @param object $user Current context user
1258   * @param object $scorm a moodle scrom object - mdl_scorm
1259   * @param bool $returnobjects if true returns a object with attempts, if false returns count of attempts.
1260   * @param bool $ignoremissingcompletion - ignores attempts that haven't reported a grade/completion.
1261   * @return int - no. of attempts so far
1262   */
1263  function scorm_get_attempt_count($userid, $scorm, $returnobjects = false, $ignoremissingcompletion = false) {
1264      global $DB;
1265  
1266      // Historically attempts that don't report these elements haven't been included in the average attempts grading method
1267      // we may want to change this in future, but to avoid unexpected grade decreases we're leaving this in. MDL-43222 .
1268      if (scorm_version_check($scorm->version, SCORM_13)) {
1269          $element = 'cmi.score.raw';
1270      } else if ($scorm->grademethod == GRADESCOES) {
1271          $element = 'cmi.core.lesson_status';
1272      } else {
1273          $element = 'cmi.core.score.raw';
1274      }
1275  
1276      if ($returnobjects) {
1277          $params = array('userid' => $userid, 'scormid' => $scorm->id);
1278          if ($ignoremissingcompletion) { // Exclude attempts that don't have the completion element requested.
1279              $params['element'] = $element;
1280          }
1281          $attempts = $DB->get_records('scorm_scoes_track', $params, 'attempt', 'DISTINCT attempt AS attemptnumber');
1282          return $attempts;
1283      } else {
1284          $params = array($userid, $scorm->id);
1285          $sql = "SELECT COUNT(DISTINCT attempt)
1286                    FROM {scorm_scoes_track}
1287                   WHERE userid = ? AND scormid = ?";
1288          if ($ignoremissingcompletion) { // Exclude attempts that don't have the completion element requested.
1289              $sql .= ' AND element = ?';
1290              $params[] = $element;
1291          }
1292  
1293          $attemptscount = $DB->count_records_sql($sql, $params);
1294          return $attemptscount;
1295      }
1296  }
1297  
1298  /**
1299   * Figure out with this is a debug situation
1300   *
1301   * @param object $scorm a moodle scrom object - mdl_scorm
1302   * @return boolean - debugging true/false
1303   */
1304  function scorm_debugging($scorm) {
1305      global $CFG, $USER;
1306      $cfgscorm = get_config('scorm');
1307  
1308      if (!$cfgscorm->allowapidebug) {
1309          return false;
1310      }
1311      $identifier = $USER->username.':'.$scorm->name;
1312      $test = $cfgscorm->apidebugmask;
1313      // Check the regex is only a short list of safe characters.
1314      if (!preg_match('/^[\w\s\*\.\?\+\:\_\\\]+$/', $test)) {
1315          return false;
1316      }
1317      $res = false;
1318      eval('$res = preg_match(\'/^'.$test.'/\', $identifier) ? true : false;');
1319      return $res;
1320  }
1321  
1322  /**
1323   * Delete Scorm tracks for selected users
1324   *
1325   * @param array $attemptids list of attempts that need to be deleted
1326   * @param stdClass $scorm instance
1327   *
1328   * @return bool true deleted all responses, false failed deleting an attempt - stopped here
1329   */
1330  function scorm_delete_responses($attemptids, $scorm) {
1331      if (!is_array($attemptids) || empty($attemptids)) {
1332          return false;
1333      }
1334  
1335      foreach ($attemptids as $num => $attemptid) {
1336          if (empty($attemptid)) {
1337              unset($attemptids[$num]);
1338          }
1339      }
1340  
1341      foreach ($attemptids as $attempt) {
1342          $keys = explode(':', $attempt);
1343          if (count($keys) == 2) {
1344              $userid = clean_param($keys[0], PARAM_INT);
1345              $attemptid = clean_param($keys[1], PARAM_INT);
1346              if (!$userid || !$attemptid || !scorm_delete_attempt($userid, $scorm, $attemptid)) {
1347                      return false;
1348              }
1349          } else {
1350              return false;
1351          }
1352      }
1353      return true;
1354  }
1355  
1356  /**
1357   * Delete Scorm tracks for selected users
1358   *
1359   * @param int $userid ID of User
1360   * @param stdClass $scorm Scorm object
1361   * @param int $attemptid user attempt that need to be deleted
1362   *
1363   * @return bool true suceeded
1364   */
1365  function scorm_delete_attempt($userid, $scorm, $attemptid) {
1366      global $DB;
1367  
1368      $DB->delete_records('scorm_scoes_track', array('userid' => $userid, 'scormid' => $scorm->id, 'attempt' => $attemptid));
1369      $cm = get_coursemodule_from_instance('scorm', $scorm->id);
1370  
1371      // Trigger instances list viewed event.
1372      $event = \mod_scorm\event\attempt_deleted::create(array(
1373           'other' => array('attemptid' => $attemptid),
1374           'context' => context_module::instance($cm->id),
1375           'relateduserid' => $userid
1376      ));
1377      $event->add_record_snapshot('course_modules', $cm);
1378      $event->add_record_snapshot('scorm', $scorm);
1379      $event->trigger();
1380  
1381      include_once ('lib.php');
1382      scorm_update_grades($scorm, $userid, true);
1383      return true;
1384  }
1385  
1386  /**
1387   * Converts SCORM duration notation to human-readable format
1388   * The function works with both SCORM 1.2 and SCORM 2004 time formats
1389   * @param $duration string SCORM duration
1390   * @return string human-readable date/time
1391   */
1392  function scorm_format_duration($duration) {
1393      // Fetch date/time strings.
1394      $stryears = get_string('years');
1395      $strmonths = get_string('nummonths');
1396      $strdays = get_string('days');
1397      $strhours = get_string('hours');
1398      $strminutes = get_string('minutes');
1399      $strseconds = get_string('seconds');
1400  
1401      if ($duration[0] == 'P') {
1402          // If timestamp starts with 'P' - it's a SCORM 2004 format
1403          // this regexp discards empty sections, takes Month/Minute ambiguity into consideration,
1404          // and outputs filled sections, discarding leading zeroes and any format literals
1405          // also saves the only zero before seconds decimals (if there are any) and discards decimals if they are zero.
1406          $pattern = array( '#([A-Z])0+Y#', '#([A-Z])0+M#', '#([A-Z])0+D#', '#P(|\d+Y)0*(\d+)M#',
1407                              '#0*(\d+)Y#', '#0*(\d+)D#', '#P#', '#([A-Z])0+H#', '#([A-Z])[0.]+S#',
1408                              '#\.0+S#', '#T(|\d+H)0*(\d+)M#', '#0*(\d+)H#', '#0+\.(\d+)S#',
1409                              '#0*([\d.]+)S#', '#T#' );
1410          $replace = array( '$1', '$1', '$1', '$1$2 '.$strmonths.' ', '$1 '.$stryears.' ', '$1 '.$strdays.' ',
1411                              '', '$1', '$1', 'S', '$1$2 '.$strminutes.' ', '$1 '.$strhours.' ',
1412                              '0.$1 '.$strseconds, '$1 '.$strseconds, '');
1413      } else {
1414          // Else we have SCORM 1.2 format there
1415          // first convert the timestamp to some SCORM 2004-like format for conveniency.
1416          $duration = preg_replace('#^(\d+):(\d+):([\d.]+)$#', 'T$1H$2M$3S', $duration);
1417          // Then convert in the same way as SCORM 2004.
1418          $pattern = array( '#T0+H#', '#([A-Z])0+M#', '#([A-Z])[0.]+S#', '#\.0+S#', '#0*(\d+)H#',
1419                              '#0*(\d+)M#', '#0+\.(\d+)S#', '#0*([\d.]+)S#', '#T#' );
1420          $replace = array( 'T', '$1', '$1', 'S', '$1 '.$strhours.' ', '$1 '.$strminutes.' ',
1421                              '0.$1 '.$strseconds, '$1 '.$strseconds, '' );
1422      }
1423  
1424      $result = preg_replace($pattern, $replace, $duration);
1425  
1426      return $result;
1427  }
1428  
1429  function scorm_get_toc_object($user, $scorm, $currentorg='', $scoid='', $mode='normal', $attempt='',
1430                                  $play=false, $organizationsco=null) {
1431      global $CFG, $DB, $PAGE, $OUTPUT;
1432  
1433      // Always pass the mode even if empty as that is what is done elsewhere and the urls have to match.
1434      $modestr = '&mode=';
1435      if ($mode != 'normal') {
1436          $modestr = '&mode='.$mode;
1437      }
1438  
1439      $result = array();
1440      $incomplete = false;
1441  
1442      if (!empty($organizationsco)) {
1443          $result[0] = $organizationsco;
1444          $result[0]->isvisible = true;
1445          $result[0]->statusicon = '';
1446          $result[0]->url = '';
1447      }
1448  
1449      if ($scoes = scorm_get_scoes($scorm->id, $currentorg)) {
1450          // Retrieve user tracking data for each learning object.
1451          $usertracks = array();
1452          foreach ($scoes as $sco) {
1453              if (!empty($sco->launch)) {
1454                  if ($usertrack = scorm_get_tracks($sco->id, $user->id, $attempt)) {
1455                      if ($usertrack->status == '') {
1456                          $usertrack->status = 'notattempted';
1457                      }
1458                      $usertracks[$sco->identifier] = $usertrack;
1459                  }
1460              }
1461          }
1462          foreach ($scoes as $sco) {
1463              if (!isset($sco->isvisible)) {
1464                  $sco->isvisible = true;
1465              }
1466  
1467              if (empty($sco->title)) {
1468                  $sco->title = $sco->identifier;
1469              }
1470  
1471              if (scorm_version_check($scorm->version, SCORM_13)) {
1472                  $sco->prereq = true;
1473              } else {
1474                  $sco->prereq = empty($sco->prerequisites) || scorm_eval_prerequisites($sco->prerequisites, $usertracks);
1475              }
1476  
1477              if ($sco->isvisible) {
1478                  if (!empty($sco->launch)) {
1479                      if (empty($scoid) && ($mode != 'normal')) {
1480                          $scoid = $sco->id;
1481                      }
1482  
1483                      if (isset($usertracks[$sco->identifier])) {
1484                          $usertrack = $usertracks[$sco->identifier];
1485                          $strstatus = get_string($usertrack->status, 'scorm');
1486  
1487                          if ($sco->scormtype == 'sco') {
1488                              $statusicon = html_writer::img($OUTPUT->pix_url($usertrack->status, 'scorm'), $strstatus,
1489                                                              array('title' => $strstatus));
1490                          } else {
1491                              $statusicon = html_writer::img($OUTPUT->pix_url('asset', 'scorm'), get_string('assetlaunched', 'scorm'),
1492                                                              array('title' => get_string('assetlaunched', 'scorm')));
1493                          }
1494  
1495                          if (($usertrack->status == 'notattempted') ||
1496                                  ($usertrack->status == 'incomplete') ||
1497                                  ($usertrack->status == 'browsed')) {
1498                              $incomplete = true;
1499                              if ($play && empty($scoid)) {
1500                                  $scoid = $sco->id;
1501                              }
1502                          }
1503  
1504                          $strsuspended = get_string('suspended', 'scorm');
1505  
1506                          $exitvar = 'cmi.core.exit';
1507  
1508                          if (scorm_version_check($scorm->version, SCORM_13)) {
1509                              $exitvar = 'cmi.exit';
1510                          }
1511  
1512                          if ($incomplete && isset($usertrack->{$exitvar}) && ($usertrack->{$exitvar} == 'suspend')) {
1513                              $statusicon = html_writer::img($OUTPUT->pix_url('suspend', 'scorm'), $strstatus.' - '.$strsuspended,
1514                                                              array('title' => $strstatus.' - '.$strsuspended));
1515                          }
1516  
1517                      } else {
1518                          if ($play && empty($scoid)) {
1519                              $scoid = $sco->id;
1520                          }
1521  
1522                          $incomplete = true;
1523  
1524                          if ($sco->scormtype == 'sco') {
1525                              $statusicon = html_writer::img($OUTPUT->pix_url('notattempted', 'scorm'),
1526                                                              get_string('notattempted', 'scorm'),
1527                                                              array('title' => get_string('notattempted', 'scorm')));
1528                          } else {
1529                              $statusicon = html_writer::img($OUTPUT->pix_url('asset', 'scorm'), get_string('asset', 'scorm'),
1530                                                              array('title' => get_string('asset', 'scorm')));
1531                          }
1532                      }
1533                  }
1534              }
1535  
1536              if (empty($statusicon)) {
1537                  $sco->statusicon = html_writer::img($OUTPUT->pix_url('notattempted', 'scorm'), get_string('notattempted', 'scorm'),
1538                                                      array('title' => get_string('notattempted', 'scorm')));
1539              } else {
1540                  $sco->statusicon = $statusicon;
1541              }
1542  
1543              $sco->url = 'a='.$scorm->id.'&scoid='.$sco->id.'&currentorg='.$currentorg.$modestr.'&attempt='.$attempt;
1544              $sco->incomplete = $incomplete;
1545  
1546              if (!in_array($sco->id, array_keys($result))) {
1547                  $result[$sco->id] = $sco;
1548              }
1549          }
1550      }
1551  
1552      // Get the parent scoes!
1553      $result = scorm_get_toc_get_parent_child($result, $currentorg);
1554  
1555      // Be safe, prevent warnings from showing up while returning array.
1556      if (!isset($scoid)) {
1557          $scoid = '';
1558      }
1559  
1560      return array('scoes' => $result, 'usertracks' => $usertracks, 'scoid' => $scoid);
1561  }
1562  
1563  function scorm_get_toc_get_parent_child(&$result, $currentorg) {
1564      $final = array();
1565      $level = 0;
1566      // Organization is always the root, prevparent.
1567      if (!empty($currentorg)) {
1568          $prevparent = $currentorg;
1569      } else {
1570          $prevparent = '/';
1571      }
1572  
1573      foreach ($result as $sco) {
1574          if ($sco->parent == '/') {
1575              $final[$level][$sco->identifier] = $sco;
1576              $prevparent = $sco->identifier;
1577              unset($result[$sco->id]);
1578          } else {
1579              if ($sco->parent == $prevparent) {
1580                  $final[$level][$sco->identifier] = $sco;
1581                  $prevparent = $sco->identifier;
1582                  unset($result[$sco->id]);
1583              } else {
1584                  if (!empty($final[$level])) {
1585                      $found = false;
1586                      foreach ($final[$level] as $fin) {
1587                          if ($sco->parent == $fin->identifier) {
1588                              $found = true;
1589                          }
1590                      }
1591  
1592                      if ($found) {
1593                          $final[$level][$sco->identifier] = $sco;
1594                          unset($result[$sco->id]);
1595                          $found = false;
1596                      } else {
1597                          $level++;
1598                          $final[$level][$sco->identifier] = $sco;
1599                          unset($result[$sco->id]);
1600                      }
1601                  }
1602              }
1603          }
1604      }
1605  
1606      for ($i = 0; $i <= $level; $i++) {
1607          $prevparent = '';
1608          foreach ($final[$i] as $ident => $sco) {
1609              if (empty($prevparent)) {
1610                  $prevparent = $ident;
1611              }
1612              if (!isset($final[$i][$prevparent]->children)) {
1613                  $final[$i][$prevparent]->children = array();
1614              }
1615              if ($sco->parent == $prevparent) {
1616                  $final[$i][$prevparent]->children[] = $sco;
1617                  $prevparent = $ident;
1618              } else {
1619                  $parent = false;
1620                  foreach ($final[$i] as $identifier => $scoobj) {
1621                      if ($identifier == $sco->parent) {
1622                          $parent = $identifier;
1623                      }
1624                  }
1625  
1626                  if ($parent !== false) {
1627                      $final[$i][$parent]->children[] = $sco;
1628                  }
1629              }
1630          }
1631      }
1632  
1633      $results = array();
1634      for ($i = 0; $i <= $level; $i++) {
1635          $keys = array_keys($final[$i]);
1636          $results[] = $final[$i][$keys[0]];
1637      }
1638  
1639      return $results;
1640  }
1641  
1642  function scorm_format_toc_for_treeview($user, $scorm, $scoes, $usertracks, $cmid, $toclink=TOCJSLINK, $currentorg='',
1643                                          $attempt='', $play=false, $organizationsco=null, $children=false) {
1644      global $CFG;
1645  
1646      $result = new stdClass();
1647      $result->prerequisites = true;
1648      $result->incomplete = true;
1649      $result->toc = '';
1650  
1651      if (!$children) {
1652          $attemptsmade = scorm_get_attempt_count($user->id, $scorm);
1653          $result->attemptleft = $scorm->maxattempt == 0 ? 1 : $scorm->maxattempt - $attemptsmade;
1654      }
1655  
1656      if (!$children) {
1657          $result->toc = html_writer::start_tag('ul');
1658  
1659          if (!$play && !empty($organizationsco)) {
1660              $result->toc .= html_writer::start_tag('li').$organizationsco->title.html_writer::end_tag('li');
1661          }
1662      }
1663  
1664      $prevsco = '';
1665      if (!empty($scoes)) {
1666          foreach ($scoes as $sco) {
1667              $result->toc .= html_writer::start_tag('li');
1668              $scoid = $sco->id;
1669  
1670              $sco->isvisible = true;
1671  
1672              if ($sco->isvisible) {
1673                  $score = '';
1674  
1675                  if (isset($usertracks[$sco->identifier])) {
1676                      $viewscore = has_capability('mod/scorm:viewscores', context_module::instance($cmid));
1677                      if (isset($usertracks[$sco->identifier]->score_raw) && $viewscore) {
1678                          if ($usertracks[$sco->identifier]->score_raw != '') {
1679                              $score = '('.get_string('score', 'scorm').':&nbsp;'.$usertracks[$sco->identifier]->score_raw.')';
1680                          }
1681                      }
1682                  }
1683  
1684                  if (!empty($sco->prereq)) {
1685                      if ($sco->id == $scoid) {
1686                          $result->prerequisites = true;
1687                      }
1688  
1689                      if (!empty($prevsco) && scorm_version_check($scorm->version, SCORM_13) && !empty($prevsco->hidecontinue)) {
1690                          if ($sco->scormtype == 'sco') {
1691                              $result->toc .= html_writer::span($sco->statusicon.'&nbsp;'.format_string($sco->title));
1692                          } else {
1693                              $result->toc .= html_writer::span('&nbsp;'.format_string($sco->title));
1694                          }
1695                      } else if ($toclink == TOCFULLURL) {
1696                          $url = $CFG->wwwroot.'/mod/scorm/player.php?'.$sco->url;
1697                          if (!empty($sco->launch)) {
1698                              if ($sco->scormtype == 'sco') {
1699                                  $result->toc .= $sco->statusicon.'&nbsp;';
1700                                  $result->toc .= html_writer::link($url, format_string($sco->title)).$score;
1701                              } else {
1702                                  $result->toc .= '&nbsp;'.html_writer::link($url, format_string($sco->title),
1703                                                                              array('data-scoid' => $sco->id)).$score;
1704                              }
1705                          } else {
1706                              if ($sco->scormtype == 'sco') {
1707                                  $result->toc .= $sco->statusicon.'&nbsp;'.format_string($sco->title).$score;
1708                              } else {
1709                                  $result->toc .= '&nbsp;'.format_string($sco->title).$score;
1710                              }
1711                          }
1712                      } else {
1713                          if (!empty($sco->launch)) {
1714                              if ($sco->scormtype == 'sco') {
1715                                  $result->toc .= html_writer::tag('a', $sco->statusicon.'&nbsp;'.
1716                                                                      format_string($sco->title).'&nbsp;'.$score,
1717                                                                      array('data-scoid' => $sco->id, 'title' => $sco->url));
1718                              } else {
1719                                  $result->toc .= html_writer::tag('a', '&nbsp;'.format_string($sco->title).'&nbsp;'.$score,
1720                                                                      array('data-scoid' => $sco->id, 'title' => $sco->url));
1721                              }
1722                          } else {
1723                              if ($sco->scormtype == 'sco') {
1724                                  $result->toc .= html_writer::span($sco->statusicon.'&nbsp;'.format_string($sco->title));
1725                              } else {
1726                                  $result->toc .= html_writer::span('&nbsp;'.format_string($sco->title));
1727                              }
1728                          }
1729                      }
1730  
1731                  } else {
1732                      if ($play) {
1733                          if ($sco->scormtype == 'sco') {
1734                              $result->toc .= html_writer::span($sco->statusicon.'&nbsp;'.format_string($sco->title));
1735                          } else {
1736                              $result->toc .= '&nbsp;'.format_string($sco->title).html_writer::end_span();
1737                          }
1738                      } else {
1739                          if ($sco->scormtype == 'sco') {
1740                              $result->toc .= $sco->statusicon.'&nbsp;'.format_string($sco->title);
1741                          } else {
1742                              $result->toc .= '&nbsp;'.format_string($sco->title);
1743                          }
1744                      }
1745                  }
1746  
1747              } else {
1748                  $result->toc .= "&nbsp;".format_string($sco->title);
1749              }
1750  
1751              if (!empty($sco->children)) {
1752                  $result->toc .= html_writer::start_tag('ul');
1753                  $childresult = scorm_format_toc_for_treeview($user, $scorm, $sco->children, $usertracks, $cmid,
1754                                                                  $toclink, $currentorg, $attempt, $play, $organizationsco, true);
1755                  $result->toc .= $childresult->toc;
1756                  $result->toc .= html_writer::end_tag('ul');
1757                  $result->toc .= html_writer::end_tag('li');
1758              } else {
1759                  $result->toc .= html_writer::end_tag('li');
1760              }
1761              $prevsco = $sco;
1762          }
1763          $result->incomplete = $sco->incomplete;
1764      }
1765  
1766      if (!$children) {
1767          $result->toc .= html_writer::end_tag('ul');
1768      }
1769  
1770      return $result;
1771  }
1772  
1773  function scorm_format_toc_for_droplist($scorm, $scoes, $usertracks, $currentorg='', $organizationsco=null,
1774                                          $children=false, $level=0, $tocmenus=array()) {
1775      if (!empty($scoes)) {
1776          if (!empty($organizationsco) && !$children) {
1777              $tocmenus[$organizationsco->id] = $organizationsco->title;
1778          }
1779  
1780          $parents[$level] = '/';
1781          foreach ($scoes as $sco) {
1782              if ($parents[$level] != $sco->parent) {
1783                  if ($newlevel = array_search($sco->parent, $parents)) {
1784                      $level = $newlevel;
1785                  } else {
1786                      $i = $level;
1787                      while (($i > 0) && ($parents[$level] != $sco->parent)) {
1788                          $i--;
1789                      }
1790  
1791                      if (($i == 0) && ($sco->parent != $currentorg)) {
1792                          $level++;
1793                      } else {
1794                          $level = $i;
1795                      }
1796  
1797                      $parents[$level] = $sco->parent;
1798                  }
1799              }
1800  
1801              if ($sco->prereq) {
1802                  if ($sco->scormtype == 'sco') {
1803                      $tocmenus[$sco->id] = scorm_repeater('&minus;', $level) . '&gt;' . format_string($sco->title);
1804                  }
1805              } else {
1806                  if ($sco->scormtype == 'sco') {
1807                      $tocmenus[$sco->id] = scorm_repeater('&minus;', $level) . '&gt;' . format_string($sco->title);
1808                  }
1809              }
1810  
1811              if (!empty($sco->children)) {
1812                  $tocmenus = scorm_format_toc_for_droplist($scorm, $sco->children, $usertracks, $currentorg,
1813                                                              $organizationsco, true, $level, $tocmenus);
1814              }
1815          }
1816      }
1817  
1818      return $tocmenus;
1819  }
1820  
1821  function scorm_get_toc($user, $scorm, $cmid, $toclink=TOCJSLINK, $currentorg='', $scoid='', $mode='normal',
1822                          $attempt='', $play=false, $tocheader=false) {
1823      global $CFG, $DB, $OUTPUT;
1824  
1825      if (empty($attempt)) {
1826          $attempt = scorm_get_last_attempt($scorm->id, $user->id);
1827      }
1828  
1829      $result = new stdClass();
1830      $organizationsco = null;
1831  
1832      if ($tocheader) {
1833          $result->toc = html_writer::start_div('yui3-g-r', array('id' => 'scorm_layout'));
1834          $result->toc .= html_writer::start_div('yui3-u-1-5', array('id' => 'scorm_toc'));
1835          $result->toc .= html_writer::div('', '', array('id' => 'scorm_toc_title'));
1836          $result->toc .= html_writer::start_div('', array('id' => 'scorm_tree'));
1837      }
1838  
1839      if (!empty($currentorg)) {
1840          $organizationsco = $DB->get_record('scorm_scoes', array('scorm' => $scorm->id, 'identifier' => $currentorg));
1841          if (!empty($organizationsco->title)) {
1842              if ($play) {
1843                  $result->toctitle = $organizationsco->title;
1844              }
1845          }
1846      }
1847  
1848      $scoes = scorm_get_toc_object($user, $scorm, $currentorg, $scoid, $mode, $attempt, $play, $organizationsco);
1849  
1850      $treeview = scorm_format_toc_for_treeview($user, $scorm, $scoes['scoes'][0]->children, $scoes['usertracks'], $cmid,
1851                                                  $toclink, $currentorg, $attempt, $play, $organizationsco, false);
1852  
1853      if ($tocheader) {
1854          $result->toc .= $treeview->toc;
1855      } else {
1856          $result->toc = $treeview->toc;
1857      }
1858  
1859      if (!empty($scoes['scoid'])) {
1860          $scoid = $scoes['scoid'];
1861      }
1862  
1863      if (empty($scoid)) {
1864          // If this is a normal package with an org sco and child scos get the first child.
1865          if (!empty($scoes['scoes'][0]->children)) {
1866              $result->sco = $scoes['scoes'][0]->children[0];
1867          } else { // This package only has one sco - it may be a simple external AICC package.
1868              $result->sco = $scoes['scoes'][0];
1869          }
1870  
1871      } else {
1872          $result->sco = scorm_get_sco($scoid);
1873      }
1874  
1875      if ($scorm->hidetoc == SCORM_TOC_POPUP) {
1876          $tocmenu = scorm_format_toc_for_droplist($scorm, $scoes['scoes'][0]->children, $scoes['usertracks'],
1877                                                      $currentorg, $organizationsco);
1878  
1879          $modestr = '';
1880          if ($mode != 'normal') {
1881              $modestr = '&mode='.$mode;
1882          }
1883  
1884          $url = new moodle_url('/mod/scorm/player.php?a='.$scorm->id.'&currentorg='.$currentorg.$modestr);
1885          $result->tocmenu = $OUTPUT->single_select($url, 'scoid', $tocmenu, $result->sco->id, null, "tocmenu");
1886      }
1887  
1888      $result->prerequisites = $treeview->prerequisites;
1889      $result->incomplete = $treeview->incomplete;
1890      $result->attemptleft = $treeview->attemptleft;
1891  
1892      if ($tocheader) {
1893          $result->toc .= html_writer::end_div().html_writer::end_div();
1894          $result->toc .= html_writer::start_div('', array('id' => 'scorm_toc_toggle'));
1895          $result->toc .= html_writer::tag('button', '', array('id' => 'scorm_toc_toggle_btn')).html_writer::end_div();
1896          $result->toc .= html_writer::start_div('', array('id' => 'scorm_content'));
1897          $result->toc .= html_writer::div('', '', array('id' => 'scorm_navpanel'));
1898          $result->toc .= html_writer::end_div().html_writer::end_div();
1899      }
1900  
1901      return $result;
1902  }
1903  
1904  function scorm_get_adlnav_json ($scoes, &$adlnav = array(), $parentscoid = null) {
1905      if (is_object($scoes)) {
1906          $sco = $scoes;
1907          if (isset($sco->url)) {
1908              $adlnav[$sco->id]['identifier'] = $sco->identifier;
1909              $adlnav[$sco->id]['launch'] = $sco->launch;
1910              $adlnav[$sco->id]['title'] = $sco->title;
1911              $adlnav[$sco->id]['url'] = $sco->url;
1912              $adlnav[$sco->id]['parent'] = $sco->parent;
1913              if (isset($sco->choice)) {
1914                  $adlnav[$sco->id]['choice'] = $sco->choice;
1915              }
1916              if (isset($sco->flow)) {
1917                  $adlnav[$sco->id]['flow'] = $sco->flow;
1918              } else if (isset($parentscoid) && isset($adlnav[$parentscoid]['flow'])) {
1919                  $adlnav[$sco->id]['flow'] = $adlnav[$parentscoid]['flow'];
1920              }
1921              if (isset($sco->isvisible)) {
1922                  $adlnav[$sco->id]['isvisible'] = $sco->isvisible;
1923              }
1924              if (isset($sco->parameters)) {
1925                  $adlnav[$sco->id]['parameters'] = $sco->parameters;
1926              }
1927              if (isset($sco->hidecontinue)) {
1928                  $adlnav[$sco->id]['hidecontinue'] = $sco->hidecontinue;
1929              }
1930              if (isset($sco->hideprevious)) {
1931                  $adlnav[$sco->id]['hideprevious'] = $sco->hideprevious;
1932              }
1933              if (isset($sco->hidesuspendall)) {
1934                  $adlnav[$sco->id]['hidesuspendall'] = $sco->hidesuspendall;
1935              }
1936              if (!empty($parentscoid)) {
1937                  $adlnav[$sco->id]['parentscoid'] = $parentscoid;
1938              }
1939              if (isset($adlnav['prevscoid'])) {
1940                  $adlnav[$sco->id]['prevscoid'] = $adlnav['prevscoid'];
1941                  $adlnav[$adlnav['prevscoid']]['nextscoid'] = $sco->id;
1942                  if (isset($adlnav['prevparent']) && $adlnav['prevparent'] == $sco->parent) {
1943                      $adlnav[$sco->id]['prevsibling'] = $adlnav['prevscoid'];
1944                      $adlnav[$adlnav['prevscoid']]['nextsibling'] = $sco->id;
1945                  }
1946              }
1947              $adlnav['prevscoid'] = $sco->id;
1948              $adlnav['prevparent'] = $sco->parent;
1949          }
1950          if (isset($sco->children)) {
1951              foreach ($sco->children as $children) {
1952                  scorm_get_adlnav_json($children, $adlnav, $sco->id);
1953              }
1954          }
1955      } else {
1956          foreach ($scoes as $sco) {
1957              scorm_get_adlnav_json ($sco, $adlnav);
1958          }
1959          unset($adlnav['prevscoid']);
1960          unset($adlnav['prevparent']);
1961      }
1962      return json_encode($adlnav);
1963  }
1964  
1965  /**
1966   * Check for the availability of a resource by URL.
1967   *
1968   * Check is performed using an HTTP HEAD call.
1969   *
1970   * @param $url string A valid URL
1971   * @return bool|string True if no issue is found. The error string message, otherwise
1972   */
1973  function scorm_check_url($url) {
1974      $curl = new curl;
1975      // Same options as in {@link download_file_content()}, used in {@link scorm_parse_scorm()}.
1976      $curl->setopt(array('CURLOPT_FOLLOWLOCATION' => true, 'CURLOPT_MAXREDIRS' => 5));
1977      $cmsg = $curl->head($url);
1978      $info = $curl->get_info();
1979      if (empty($info['http_code']) || $info['http_code'] != 200) {
1980          return get_string('invalidurlhttpcheck', 'scorm', array('cmsg' => $cmsg));
1981      }
1982  
1983      return true;
1984  }
1985  
1986  /**
1987   * Check for a parameter in userdata and return it if it's set
1988   * or return the value from $ifempty if its empty
1989   *
1990   * @param stdClass $userdata Contains user's data
1991   * @param string $param parameter that should be checked
1992   * @param string $ifempty value to be replaced with if $param is not set
1993   * @return string value from $userdata->$param if its not empty, or $ifempty
1994   */
1995  function scorm_isset($userdata, $param, $ifempty = '') {
1996      if (isset($userdata->$param)) {
1997          return $userdata->$param;
1998      } else {
1999          return $ifempty;
2000      }
2001  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1