Search moodle.org's
Developer Documentation


  • Bug fixes for general core bugs in 2.8.x ended 9 November 2015 (12 months).
  • Bug fixes for security issues in 2.8.x ended 9 May 2016 (18 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported
  • /mod/scorm/ -> lib.php (source)

    Differences Between: [Versions 28 and 29] [Versions 28 and 30] [Versions 28 and 31] [Versions 28 and 32] [Versions 28 and 33] [Versions 28 and 34] [Versions 28 and 35] [Versions 28 and 36] [Versions 28 and 37]

       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   * @package   mod_scorm
      19   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
      20   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      21   */
      22  
      23  /** SCORM_TYPE_LOCAL = local */
      24  define('SCORM_TYPE_LOCAL', 'local');
      25  /** SCORM_TYPE_LOCALSYNC = localsync */
      26  define('SCORM_TYPE_LOCALSYNC', 'localsync');
      27  /** SCORM_TYPE_EXTERNAL = external */
      28  define('SCORM_TYPE_EXTERNAL', 'external');
      29  /** SCORM_TYPE_AICCURL = external AICC url */
      30  define('SCORM_TYPE_AICCURL', 'aiccurl');
      31  
      32  define('SCORM_TOC_SIDE', 0);
      33  define('SCORM_TOC_HIDDEN', 1);
      34  define('SCORM_TOC_POPUP', 2);
      35  define('SCORM_TOC_DISABLED', 3);
      36  
      37  // Used to show/hide navigation buttons and set their position.
      38  define('SCORM_NAV_DISABLED', 0);
      39  define('SCORM_NAV_UNDER_CONTENT', 1);
      40  define('SCORM_NAV_FLOATING', 2);
      41  
      42  // Used to check what SCORM version is being used.
      43  define('SCORM_12', 1);
      44  define('SCORM_13', 2);
      45  define('SCORM_AICC', 3);
      46  
      47  // List of possible attemptstatusdisplay options.
      48  define('SCORM_DISPLAY_ATTEMPTSTATUS_NO', 0);
      49  define('SCORM_DISPLAY_ATTEMPTSTATUS_ALL', 1);
      50  define('SCORM_DISPLAY_ATTEMPTSTATUS_MY', 2);
      51  define('SCORM_DISPLAY_ATTEMPTSTATUS_ENTRY', 3);
      52  
      53  /**
      54   * Return an array of status options
      55   *
      56   * Optionally with translated strings
      57   *
      58   * @param   bool    $with_strings   (optional)
      59   * @return  array
      60   */
      61  function scorm_status_options($withstrings = false) {
      62      // Id's are important as they are bits.
      63      $options = array(
      64          2 => 'passed',
      65          4 => 'completed'
      66      );
      67  
      68      if ($withstrings) {
      69          foreach ($options as $key => $value) {
      70              $options[$key] = get_string('completionstatus_'.$value, 'scorm');
      71          }
      72      }
      73  
      74      return $options;
      75  }
      76  
      77  
      78  /**
      79   * Given an object containing all the necessary data,
      80   * (defined by the form in mod_form.php) this function
      81   * will create a new instance and return the id number
      82   * of the new instance.
      83   *
      84   * @global stdClass
      85   * @global object
      86   * @uses CONTEXT_MODULE
      87   * @uses SCORM_TYPE_LOCAL
      88   * @uses SCORM_TYPE_LOCALSYNC
      89   * @uses SCORM_TYPE_EXTERNAL
      90   * @param object $scorm Form data
      91   * @param object $mform
      92   * @return int new instance id
      93   */
      94  function scorm_add_instance($scorm, $mform=null) {
      95      global $CFG, $DB;
      96  
      97      require_once($CFG->dirroot.'/mod/scorm/locallib.php');
      98  
      99      if (empty($scorm->timeopen)) {
     100          $scorm->timeopen = 0;
     101      }
     102      if (empty($scorm->timeclose)) {
     103          $scorm->timeclose = 0;
     104      }
     105      $cmid       = $scorm->coursemodule;
     106      $cmidnumber = $scorm->cmidnumber;
     107      $courseid   = $scorm->course;
     108  
     109      $context = context_module::instance($cmid);
     110  
     111      $scorm = scorm_option2text($scorm);
     112      $scorm->width  = (int)str_replace('%', '', $scorm->width);
     113      $scorm->height = (int)str_replace('%', '', $scorm->height);
     114  
     115      if (!isset($scorm->whatgrade)) {
     116          $scorm->whatgrade = 0;
     117      }
     118  
     119      $id = $DB->insert_record('scorm', $scorm);
     120  
     121      // Update course module record - from now on this instance properly exists and all function may be used.
     122      $DB->set_field('course_modules', 'instance', $id, array('id' => $cmid));
     123  
     124      // Reload scorm instance.
     125      $record = $DB->get_record('scorm', array('id' => $id));
     126  
     127      // Store the package and verify.
     128      if ($record->scormtype === SCORM_TYPE_LOCAL) {
     129          if (!empty($scorm->packagefile)) {
     130              $fs = get_file_storage();
     131              $fs->delete_area_files($context->id, 'mod_scorm', 'package');
     132              file_save_draft_area_files($scorm->packagefile, $context->id, 'mod_scorm', 'package',
     133                  0, array('subdirs' => 0, 'maxfiles' => 1));
     134              // Get filename of zip that was uploaded.
     135              $files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, '', false);
     136              $file = reset($files);
     137              $filename = $file->get_filename();
     138              if ($filename !== false) {
     139                  $record->reference = $filename;
     140              }
     141          }
     142  
     143      } else if ($record->scormtype === SCORM_TYPE_LOCALSYNC) {
     144          $record->reference = $scorm->packageurl;
     145      } else if ($record->scormtype === SCORM_TYPE_EXTERNAL) {
     146          $record->reference = $scorm->packageurl;
     147      } else if ($record->scormtype === SCORM_TYPE_AICCURL) {
     148          $record->reference = $scorm->packageurl;
     149          $record->hidetoc = SCORM_TOC_DISABLED; // TOC is useless for direct AICCURL so disable it.
     150      } else {
     151          return false;
     152      }
     153  
     154      // Save reference.
     155      $DB->update_record('scorm', $record);
     156  
     157      // Extra fields required in grade related functions.
     158      $record->course     = $courseid;
     159      $record->cmidnumber = $cmidnumber;
     160      $record->cmid       = $cmid;
     161  
     162      scorm_parse($record, true);
     163  
     164      scorm_grade_item_update($record);
     165  
     166      return $record->id;
     167  }
     168  
     169  /**
     170   * Given an object containing all the necessary data,
     171   * (defined by the form in mod_form.php) this function
     172   * will update an existing instance with new data.
     173   *
     174   * @global stdClass
     175   * @global object
     176   * @uses CONTEXT_MODULE
     177   * @uses SCORM_TYPE_LOCAL
     178   * @uses SCORM_TYPE_LOCALSYNC
     179   * @uses SCORM_TYPE_EXTERNAL
     180   * @param object $scorm Form data
     181   * @param object $mform
     182   * @return bool
     183   */
     184  function scorm_update_instance($scorm, $mform=null) {
     185      global $CFG, $DB;
     186  
     187      require_once($CFG->dirroot.'/mod/scorm/locallib.php');
     188  
     189      if (empty($scorm->timeopen)) {
     190          $scorm->timeopen = 0;
     191      }
     192      if (empty($scorm->timeclose)) {
     193          $scorm->timeclose = 0;
     194      }
     195  
     196      $cmid       = $scorm->coursemodule;
     197      $cmidnumber = $scorm->cmidnumber;
     198      $courseid   = $scorm->course;
     199  
     200      $scorm->id = $scorm->instance;
     201  
     202      $context = context_module::instance($cmid);
     203  
     204      if ($scorm->scormtype === SCORM_TYPE_LOCAL) {
     205          if (!empty($scorm->packagefile)) {
     206              $fs = get_file_storage();
     207              $fs->delete_area_files($context->id, 'mod_scorm', 'package');
     208              file_save_draft_area_files($scorm->packagefile, $context->id, 'mod_scorm', 'package',
     209                  0, array('subdirs' => 0, 'maxfiles' => 1));
     210              // Get filename of zip that was uploaded.
     211              $files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, '', false);
     212              $file = reset($files);
     213              $filename = $file->get_filename();
     214              if ($filename !== false) {
     215                  $scorm->reference = $filename;
     216              }
     217          }
     218  
     219      } else if ($scorm->scormtype === SCORM_TYPE_LOCALSYNC) {
     220          $scorm->reference = $scorm->packageurl;
     221      } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL) {
     222          $scorm->reference = $scorm->packageurl;
     223      } else if ($scorm->scormtype === SCORM_TYPE_AICCURL) {
     224          $scorm->reference = $scorm->packageurl;
     225          $scorm->hidetoc = SCORM_TOC_DISABLED; // TOC is useless for direct AICCURL so disable it.
     226      } else {
     227          return false;
     228      }
     229  
     230      $scorm = scorm_option2text($scorm);
     231      $scorm->width        = (int)str_replace('%', '', $scorm->width);
     232      $scorm->height       = (int)str_replace('%', '', $scorm->height);
     233      $scorm->timemodified = time();
     234  
     235      if (!isset($scorm->whatgrade)) {
     236          $scorm->whatgrade = 0;
     237      }
     238  
     239      $DB->update_record('scorm', $scorm);
     240  
     241      $scorm = $DB->get_record('scorm', array('id' => $scorm->id));
     242  
     243      // Extra fields required in grade related functions.
     244      $scorm->course   = $courseid;
     245      $scorm->idnumber = $cmidnumber;
     246      $scorm->cmid     = $cmid;
     247  
     248      scorm_parse($scorm, (bool)$scorm->updatefreq);
     249  
     250      scorm_grade_item_update($scorm);
     251      scorm_update_grades($scorm);
     252  
     253      return true;
     254  }
     255  
     256  /**
     257   * Given an ID of an instance of this module,
     258   * this function will permanently delete the instance
     259   * and any data that depends on it.
     260   *
     261   * @global stdClass
     262   * @global object
     263   * @param int $id Scorm instance id
     264   * @return boolean
     265   */
     266  function scorm_delete_instance($id) {
     267      global $CFG, $DB;
     268  
     269      if (! $scorm = $DB->get_record('scorm', array('id' => $id))) {
     270          return false;
     271      }
     272  
     273      $result = true;
     274  
     275      // Delete any dependent records.
     276      if (! $DB->delete_records('scorm_scoes_track', array('scormid' => $scorm->id))) {
     277          $result = false;
     278      }
     279      if ($scoes = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id))) {
     280          foreach ($scoes as $sco) {
     281              if (! $DB->delete_records('scorm_scoes_data', array('scoid' => $sco->id))) {
     282                  $result = false;
     283              }
     284          }
     285          $DB->delete_records('scorm_scoes', array('scorm' => $scorm->id));
     286      }
     287      if (! $DB->delete_records('scorm', array('id' => $scorm->id))) {
     288          $result = false;
     289      }
     290  
     291      /*if (! $DB->delete_records('scorm_sequencing_controlmode', array('scormid'=>$scorm->id))) {
     292          $result = false;
     293      }
     294      if (! $DB->delete_records('scorm_sequencing_rolluprules', array('scormid'=>$scorm->id))) {
     295          $result = false;
     296      }
     297      if (! $DB->delete_records('scorm_sequencing_rolluprule', array('scormid'=>$scorm->id))) {
     298          $result = false;
     299      }
     300      if (! $DB->delete_records('scorm_sequencing_rollupruleconditions', array('scormid'=>$scorm->id))) {
     301          $result = false;
     302      }
     303      if (! $DB->delete_records('scorm_sequencing_rolluprulecondition', array('scormid'=>$scorm->id))) {
     304          $result = false;
     305      }
     306      if (! $DB->delete_records('scorm_sequencing_rulecondition', array('scormid'=>$scorm->id))) {
     307          $result = false;
     308      }
     309      if (! $DB->delete_records('scorm_sequencing_ruleconditions', array('scormid'=>$scorm->id))) {
     310          $result = false;
     311      }*/
     312  
     313      scorm_grade_item_delete($scorm);
     314  
     315      return $result;
     316  }
     317  
     318  /**
     319   * Return a small object with summary information about what a
     320   * user has done with a given particular instance of this module
     321   * Used for user activity reports.
     322   *
     323   * @global stdClass
     324   * @param int $course Course id
     325   * @param int $user User id
     326   * @param int $mod
     327   * @param int $scorm The scorm id
     328   * @return mixed
     329   */
     330  function scorm_user_outline($course, $user, $mod, $scorm) {
     331      global $CFG;
     332      require_once($CFG->dirroot.'/mod/scorm/locallib.php');
     333  
     334      require_once("$CFG->libdir/gradelib.php");
     335      $grades = grade_get_grades($course->id, 'mod', 'scorm', $scorm->id, $user->id);
     336      if (!empty($grades->items[0]->grades)) {
     337          $grade = reset($grades->items[0]->grades);
     338          $result = new stdClass();
     339          $result->info = get_string('grade') . ': '. $grade->str_long_grade;
     340  
     341          // Datesubmitted == time created. dategraded == time modified or time overridden
     342          // if grade was last modified by the user themselves use date graded. Otherwise use date submitted.
     343          // TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704.
     344          if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
     345              $result->time = $grade->dategraded;
     346          } else {
     347              $result->time = $grade->datesubmitted;
     348          }
     349  
     350          return $result;
     351      }
     352      return null;
     353  }
     354  
     355  /**
     356   * Print a detailed representation of what a user has done with
     357   * a given particular instance of this module, for user activity reports.
     358   *
     359   * @global stdClass
     360   * @global object
     361   * @param object $course
     362   * @param object $user
     363   * @param object $mod
     364   * @param object $scorm
     365   * @return boolean
     366   */
     367  function scorm_user_complete($course, $user, $mod, $scorm) {
     368      global $CFG, $DB, $OUTPUT;
     369      require_once("$CFG->libdir/gradelib.php");
     370  
     371      $liststyle = 'structlist';
     372      $now = time();
     373      $firstmodify = $now;
     374      $lastmodify = 0;
     375      $sometoreport = false;
     376      $report = '';
     377  
     378      // First Access and Last Access dates for SCOs.
     379      require_once($CFG->dirroot.'/mod/scorm/locallib.php');
     380      $timetracks = scorm_get_sco_runtime($scorm->id, false, $user->id);
     381      $firstmodify = $timetracks->start;
     382      $lastmodify = $timetracks->finish;
     383  
     384      $grades = grade_get_grades($course->id, 'mod', 'scorm', $scorm->id, $user->id);
     385      if (!empty($grades->items[0]->grades)) {
     386          $grade = reset($grades->items[0]->grades);
     387          echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
     388          if ($grade->str_feedback) {
     389              echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
     390          }
     391      }
     392  
     393      if ($orgs = $DB->get_records_select('scorm_scoes', 'scorm = ? AND '.
     394                                           $DB->sql_isempty('scorm_scoes', 'launch', false, true).' AND '.
     395                                           $DB->sql_isempty('scorm_scoes', 'organization', false, false),
     396                                           array($scorm->id), 'sortorder, id', 'id, identifier, title')) {
     397          if (count($orgs) <= 1) {
     398              unset($orgs);
     399              $orgs = array();
     400              $org = new stdClass();
     401              $org->identifier = '';
     402              $orgs[] = $org;
     403          }
     404          $report .= html_writer::start_div('mod-scorm');
     405          foreach ($orgs as $org) {
     406              $conditions = array();
     407              $currentorg = '';
     408              if (!empty($org->identifier)) {
     409                  $report .= html_writer::div($org->title, 'orgtitle');
     410                  $currentorg = $org->identifier;
     411                  $conditions['organization'] = $currentorg;
     412              }
     413              $report .= html_writer::start_tag('ul', array('id' => '0', 'class' => $liststyle));
     414                  $conditions['scorm'] = $scorm->id;
     415              if ($scoes = $DB->get_records('scorm_scoes', $conditions, "sortorder, id")) {
     416                  // Drop keys so that we can access array sequentially.
     417                  $scoes = array_values($scoes);
     418                  $level = 0;
     419                  $sublist = 1;
     420                  $parents[$level] = '/';
     421                  foreach ($scoes as $pos => $sco) {
     422                      if ($parents[$level] != $sco->parent) {
     423                          if ($level > 0 && $parents[$level - 1] == $sco->parent) {
     424                              $report .= html_writer::end_tag('ul').html_writer::end_tag('li');
     425                              $level--;
     426                          } else {
     427                              $i = $level;
     428                              $closelist = '';
     429                              while (($i > 0) && ($parents[$level] != $sco->parent)) {
     430                                  $closelist .= html_writer::end_tag('ul').html_writer::end_tag('li');
     431                                  $i--;
     432                              }
     433                              if (($i == 0) && ($sco->parent != $currentorg)) {
     434                                  $report .= html_writer::start_tag('li');
     435                                  $report .= html_writer::start_tag('ul', array('id' => $sublist, 'class' => $liststyle));
     436                                  $level++;
     437                              } else {
     438                                  $report .= $closelist;
     439                                  $level = $i;
     440                              }
     441                              $parents[$level] = $sco->parent;
     442                          }
     443                      }
     444                      $report .= html_writer::start_tag('li');
     445                      if (isset($scoes[$pos + 1])) {
     446                          $nextsco = $scoes[$pos + 1];
     447                      } else {
     448                          $nextsco = false;
     449                      }
     450                      if (($nextsco !== false) && ($sco->parent != $nextsco->parent) &&
     451                              (($level == 0) || (($level > 0) && ($nextsco->parent == $sco->identifier)))) {
     452                          $sublist++;
     453                      } else {
     454                          $report .= $OUTPUT->spacer(array("height" => "12", "width" => "13"));
     455                      }
     456  
     457                      if ($sco->launch) {
     458                          $score = '';
     459                          $totaltime = '';
     460                          if ($usertrack = scorm_get_tracks($sco->id, $user->id)) {
     461                              if ($usertrack->status == '') {
     462                                  $usertrack->status = 'notattempted';
     463                              }
     464                              $strstatus = get_string($usertrack->status, 'scorm');
     465                              $report .= html_writer::img($OUTPUT->pix_url($usertrack->status, 'scorm'),
     466                                                          $strstatus, array('title' => $strstatus));
     467                          } else {
     468                              if ($sco->scormtype == 'sco') {
     469                                  $report .= html_writer::img($OUTPUT->pix_url('notattempted', 'scorm'),
     470                                                              get_string('notattempted', 'scorm'),
     471                                                              array('title' => get_string('notattempted', 'scorm')));
     472                              } else {
     473                                  $report .= html_writer::img($OUTPUT->pix_url('asset', 'scorm'), get_string('asset', 'scorm'),
     474                                                              array('title' => get_string('asset', 'scorm')));
     475                              }
     476                          }
     477                          $report .= "&nbsp;$sco->title $score$totaltime".html_writer::end_tag('li');
     478                          if ($usertrack !== false) {
     479                              $sometoreport = true;
     480                              $report .= html_writer::start_tag('li').html_writer::start_tag('ul', array('class' => $liststyle));
     481                              foreach ($usertrack as $element => $value) {
     482                                  if (substr($element, 0, 3) == 'cmi') {
     483                                      $report .= html_writer::tag('li', $element.' => '.s($value));
     484                                  }
     485                              }
     486                              $report .= html_writer::end_tag('ul').html_writer::end_tag('li');
     487                          }
     488                      } else {
     489                          $report .= "&nbsp;$sco->title".html_writer::end_tag('li');
     490                      }
     491                  }
     492                  for ($i = 0; $i < $level; $i++) {
     493                      $report .= html_writer::end_tag('ul').html_writer::end_tag('li');
     494                  }
     495              }
     496              $report .= html_writer::end_tag('ul').html_writer::empty_tag('br');
     497          }
     498          $report .= html_writer::end_div();
     499      }
     500      if ($sometoreport) {
     501          if ($firstmodify < $now) {
     502              $timeago = format_time($now - $firstmodify);
     503              echo get_string('firstaccess', 'scorm').': '.userdate($firstmodify).' ('.$timeago.")".html_writer::empty_tag('br');
     504          }
     505          if ($lastmodify > 0) {
     506              $timeago = format_time($now - $lastmodify);
     507              echo get_string('lastaccess', 'scorm').': '.userdate($lastmodify).' ('.$timeago.")".html_writer::empty_tag('br');
     508          }
     509          echo get_string('report', 'scorm').":".html_writer::empty_tag('br');
     510          echo $report;
     511      } else {
     512          print_string('noactivity', 'scorm');
     513      }
     514  
     515      return true;
     516  }
     517  
     518  /**
     519   * Function to be run periodically according to the moodle cron
     520   * This function searches for things that need to be done, such
     521   * as sending out mail, toggling flags etc ...
     522   *
     523   * @global stdClass
     524   * @global object
     525   * @return boolean
     526   */
     527  function scorm_cron () {
     528      global $CFG, $DB;
     529  
     530      require_once($CFG->dirroot.'/mod/scorm/locallib.php');
     531  
     532      $sitetimezone = $CFG->timezone;
     533      // Now see if there are any scorm updates to be done.
     534  
     535      if (!isset($CFG->scorm_updatetimelast)) {    // To catch the first time.
     536          set_config('scorm_updatetimelast', 0);
     537      }
     538  
     539      $timenow = time();
     540      $updatetime = usergetmidnight($timenow, $sitetimezone);
     541  
     542      if ($CFG->scorm_updatetimelast < $updatetime and $timenow > $updatetime) {
     543  
     544          set_config('scorm_updatetimelast', $timenow);
     545  
     546          mtrace('Updating scorm packages which require daily update');// We are updating.
     547  
     548          $scormsupdate = $DB->get_records('scorm', array('updatefreq' => SCORM_UPDATE_EVERYDAY));
     549          foreach ($scormsupdate as $scormupdate) {
     550              scorm_parse($scormupdate, true);
     551          }
     552  
     553          // Now clear out AICC session table with old session data.
     554          $cfgscorm = get_config('scorm');
     555          if (!empty($cfgscorm->allowaicchacp)) {
     556              $expiretime = time() - ($cfgscorm->aicchacpkeepsessiondata * 24 * 60 * 60);
     557              $DB->delete_records_select('scorm_aicc_session', 'timemodified < ?', array($expiretime));
     558          }
     559      }
     560  
     561      return true;
     562  }
     563  
     564  /**
     565   * Return grade for given user or all users.
     566   *
     567   * @global stdClass
     568   * @global object
     569   * @param int $scormid id of scorm
     570   * @param int $userid optional user id, 0 means all users
     571   * @return array array of grades, false if none
     572   */
     573  function scorm_get_user_grades($scorm, $userid=0) {
     574      global $CFG, $DB;
     575      require_once($CFG->dirroot.'/mod/scorm/locallib.php');
     576  
     577      $grades = array();
     578      if (empty($userid)) {
     579          $scousers = $DB->get_records_select('scorm_scoes_track', "scormid=? GROUP BY userid",
     580                                              array($scorm->id), "", "userid,null");
     581          if ($scousers) {
     582              foreach ($scousers as $scouser) {
     583                  $grades[$scouser->userid] = new stdClass();
     584                  $grades[$scouser->userid]->id         = $scouser->userid;
     585                  $grades[$scouser->userid]->userid     = $scouser->userid;
     586                  $grades[$scouser->userid]->rawgrade = scorm_grade_user($scorm, $scouser->userid);
     587              }
     588          } else {
     589              return false;
     590          }
     591  
     592      } else {
     593          $preattempt = $DB->get_records_select('scorm_scoes_track', "scormid=? AND userid=? GROUP BY userid",
     594                                                  array($scorm->id, $userid), "", "userid,null");
     595          if (!$preattempt) {
     596              return false; // No attempt yet.
     597          }
     598          $grades[$userid] = new stdClass();
     599          $grades[$userid]->id         = $userid;
     600          $grades[$userid]->userid     = $userid;
     601          $grades[$userid]->rawgrade = scorm_grade_user($scorm, $userid);
     602      }
     603  
     604      return $grades;
     605  }
     606  
     607  /**
     608   * Update grades in central gradebook
     609   *
     610   * @category grade
     611   * @param object $scorm
     612   * @param int $userid specific user only, 0 mean all
     613   * @param bool $nullifnone
     614   */
     615  function scorm_update_grades($scorm, $userid=0, $nullifnone=true) {
     616      global $CFG;
     617      require_once($CFG->libdir.'/gradelib.php');
     618      require_once($CFG->libdir.'/completionlib.php');
     619  
     620      if ($grades = scorm_get_user_grades($scorm, $userid)) {
     621          scorm_grade_item_update($scorm, $grades);
     622          // Set complete.
     623          scorm_set_completion($scorm, $userid, COMPLETION_COMPLETE, $grades);
     624      } else if ($userid and $nullifnone) {
     625          $grade = new stdClass();
     626          $grade->userid   = $userid;
     627          $grade->rawgrade = null;
     628          scorm_grade_item_update($scorm, $grade);
     629          // Set incomplete.
     630          scorm_set_completion($scorm, $userid, COMPLETION_INCOMPLETE);
     631      } else {
     632          scorm_grade_item_update($scorm);
     633      }
     634  }
     635  
     636  /**
     637   * Update/create grade item for given scorm
     638   *
     639   * @category grade
     640   * @uses GRADE_TYPE_VALUE
     641   * @uses GRADE_TYPE_NONE
     642   * @param object $scorm object with extra cmidnumber
     643   * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
     644   * @return object grade_item
     645   */
     646  function scorm_grade_item_update($scorm, $grades=null) {
     647      global $CFG, $DB;
     648      require_once($CFG->dirroot.'/mod/scorm/locallib.php');
     649      if (!function_exists('grade_update')) { // Workaround for buggy PHP versions.
     650          require_once($CFG->libdir.'/gradelib.php');
     651      }
     652  
     653      $params = array('itemname' => $scorm->name);
     654      if (isset($scorm->cmidnumber)) {
     655          $params['idnumber'] = $scorm->cmidnumber;
     656      }
     657  
     658      if ($scorm->grademethod == GRADESCOES) {
     659          $maxgrade = $DB->count_records_select('scorm_scoes', 'scorm = ? AND '.
     660                                                  $DB->sql_isnotempty('scorm_scoes', 'launch', false, true), array($scorm->id));
     661          if ($maxgrade) {
     662              $params['gradetype'] = GRADE_TYPE_VALUE;
     663              $params['grademax']  = $maxgrade;
     664              $params['grademin']  = 0;
     665          } else {
     666              $params['gradetype'] = GRADE_TYPE_NONE;
     667          }
     668      } else {
     669          $params['gradetype'] = GRADE_TYPE_VALUE;
     670          $params['grademax']  = $scorm->maxgrade;
     671          $params['grademin']  = 0;
     672      }
     673  
     674      if ($grades === 'reset') {
     675          $params['reset'] = true;
     676          $grades = null;
     677      }
     678  
     679      return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, $grades, $params);
     680  }
     681  
     682  /**
     683   * Delete grade item for given scorm
     684   *
     685   * @category grade
     686   * @param object $scorm object
     687   * @return object grade_item
     688   */
     689  function scorm_grade_item_delete($scorm) {
     690      global $CFG;
     691      require_once($CFG->libdir.'/gradelib.php');
     692  
     693      return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, null, array('deleted' => 1));
     694  }
     695  
     696  /**
     697   * List the actions that correspond to a view of this module.
     698   * This is used by the participation report.
     699   *
     700   * Note: This is not used by new logging system. Event with
     701   *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
     702   *       be considered as view action.
     703   *
     704   * @return array
     705   */
     706  function scorm_get_view_actions() {
     707      return array('pre-view', 'view', 'view all', 'report');
     708  }
     709  
     710  /**
     711   * List the actions that correspond to a post of this module.
     712   * This is used by the participation report.
     713   *
     714   * Note: This is not used by new logging system. Event with
     715   *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
     716   *       will be considered as post action.
     717   *
     718   * @return array
     719   */
     720  function scorm_get_post_actions() {
     721      return array();
     722  }
     723  
     724  /**
     725   * @param object $scorm
     726   * @return object $scorm
     727   */
     728  function scorm_option2text($scorm) {
     729      $scormpopoupoptions = scorm_get_popup_options_array();
     730  
     731      if (isset($scorm->popup)) {
     732          if ($scorm->popup == 1) {
     733              $optionlist = array();
     734              foreach ($scormpopoupoptions as $name => $option) {
     735                  if (isset($scorm->$name)) {
     736                      $optionlist[] = $name.'='.$scorm->$name;
     737                  } else {
     738                      $optionlist[] = $name.'=0';
     739                  }
     740              }
     741              $scorm->options = implode(',', $optionlist);
     742          } else {
     743              $scorm->options = '';
     744          }
     745      } else {
     746          $scorm->popup = 0;
     747          $scorm->options = '';
     748      }
     749      return $scorm;
     750  }
     751  
     752  /**
     753   * Implementation of the function for printing the form elements that control
     754   * whether the course reset functionality affects the scorm.
     755   *
     756   * @param object $mform form passed by reference
     757   */
     758  function scorm_reset_course_form_definition(&$mform) {
     759      $mform->addElement('header', 'scormheader', get_string('modulenameplural', 'scorm'));
     760      $mform->addElement('advcheckbox', 'reset_scorm', get_string('deleteallattempts', 'scorm'));
     761  }
     762  
     763  /**
     764   * Course reset form defaults.
     765   *
     766   * @return array
     767   */
     768  function scorm_reset_course_form_defaults($course) {
     769      return array('reset_scorm' => 1);
     770  }
     771  
     772  /**
     773   * Removes all grades from gradebook
     774   *
     775   * @global stdClass
     776   * @global object
     777   * @param int $courseid
     778   * @param string optional type
     779   */
     780  function scorm_reset_gradebook($courseid, $type='') {
     781      global $CFG, $DB;
     782  
     783      $sql = "SELECT s.*, cm.idnumber as cmidnumber, s.course as courseid
     784                FROM {scorm} s, {course_modules} cm, {modules} m
     785               WHERE m.name='scorm' AND m.id=cm.module AND cm.instance=s.id AND s.course=?";
     786  
     787      if ($scorms = $DB->get_records_sql($sql, array($courseid))) {
     788          foreach ($scorms as $scorm) {
     789              scorm_grade_item_update($scorm, 'reset');
     790          }
     791      }
     792  }
     793  
     794  /**
     795   * Actual implementation of the reset course functionality, delete all the
     796   * scorm attempts for course $data->courseid.
     797   *
     798   * @global stdClass
     799   * @global object
     800   * @param object $data the data submitted from the reset course.
     801   * @return array status array
     802   */
     803  function scorm_reset_userdata($data) {
     804      global $CFG, $DB;
     805  
     806      $componentstr = get_string('modulenameplural', 'scorm');
     807      $status = array();
     808  
     809      if (!empty($data->reset_scorm)) {
     810          $scormssql = "SELECT s.id
     811                           FROM {scorm} s
     812                          WHERE s.course=?";
     813  
     814          $DB->delete_records_select('scorm_scoes_track', "scormid IN ($scormssql)", array($data->courseid));
     815  
     816          // Remove all grades from gradebook.
     817          if (empty($data->reset_gradebook_grades)) {
     818              scorm_reset_gradebook($data->courseid);
     819          }
     820  
     821          $status[] = array('component' => $componentstr, 'item' => get_string('deleteallattempts', 'scorm'), 'error' => false);
     822      }
     823  
     824      // No dates to shift here.
     825  
     826      return $status;
     827  }
     828  
     829  /**
     830   * Returns all other caps used in module
     831   *
     832   * @return array
     833   */
     834  function scorm_get_extra_capabilities() {
     835      return array('moodle/site:accessallgroups');
     836  }
     837  
     838  /**
     839   * Lists all file areas current user may browse
     840   *
     841   * @param object $course
     842   * @param object $cm
     843   * @param object $context
     844   * @return array
     845   */
     846  function scorm_get_file_areas($course, $cm, $context) {
     847      $areas = array();
     848      $areas['content'] = get_string('areacontent', 'scorm');
     849      $areas['package'] = get_string('areapackage', 'scorm');
     850      return $areas;
     851  }
     852  
     853  /**
     854   * File browsing support for SCORM file areas
     855   *
     856   * @package  mod_scorm
     857   * @category files
     858   * @param file_browser $browser file browser instance
     859   * @param array $areas file areas
     860   * @param stdClass $course course object
     861   * @param stdClass $cm course module object
     862   * @param stdClass $context context object
     863   * @param string $filearea file area
     864   * @param int $itemid item ID
     865   * @param string $filepath file path
     866   * @param string $filename file name
     867   * @return file_info instance or null if not found
     868   */
     869  function scorm_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
     870      global $CFG;
     871  
     872      if (!has_capability('moodle/course:managefiles', $context)) {
     873          return null;
     874      }
     875  
     876      // No writing for now!
     877  
     878      $fs = get_file_storage();
     879  
     880      if ($filearea === 'content') {
     881  
     882          $filepath = is_null($filepath) ? '/' : $filepath;
     883          $filename = is_null($filename) ? '.' : $filename;
     884  
     885          $urlbase = $CFG->wwwroot.'/pluginfile.php';
     886          if (!$storedfile = $fs->get_file($context->id, 'mod_scorm', 'content', 0, $filepath, $filename)) {
     887              if ($filepath === '/' and $filename === '.') {
     888                  $storedfile = new virtual_root_file($context->id, 'mod_scorm', 'content', 0);
     889              } else {
     890                  // Not found.
     891                  return null;
     892              }
     893          }
     894          require_once("$CFG->dirroot/mod/scorm/locallib.php");
     895          return new scorm_package_file_info($browser, $context, $storedfile, $urlbase, $areas[$filearea], true, true, false, false);
     896  
     897      } else if ($filearea === 'package') {
     898          $filepath = is_null($filepath) ? '/' : $filepath;
     899          $filename = is_null($filename) ? '.' : $filename;
     900  
     901          $urlbase = $CFG->wwwroot.'/pluginfile.php';
     902          if (!$storedfile = $fs->get_file($context->id, 'mod_scorm', 'package', 0, $filepath, $filename)) {
     903              if ($filepath === '/' and $filename === '.') {
     904                  $storedfile = new virtual_root_file($context->id, 'mod_scorm', 'package', 0);
     905              } else {
     906                  // Not found.
     907                  return null;
     908              }
     909          }
     910          return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, false, false);
     911      }
     912  
     913      // Scorm_intro handled in file_browser.
     914  
     915      return false;
     916  }
     917  
     918  /**
     919   * Serves scorm content, introduction images and packages. Implements needed access control ;-)
     920   *
     921   * @package  mod_scorm
     922   * @category files
     923   * @param stdClass $course course object
     924   * @param stdClass $cm course module object
     925   * @param stdClass $context context object
     926   * @param string $filearea file area
     927   * @param array $args extra arguments
     928   * @param bool $forcedownload whether or not force download
     929   * @param array $options additional options affecting the file serving
     930   * @return bool false if file not found, does not return if found - just send the file
     931   */
     932  function scorm_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
     933      global $CFG, $DB;
     934  
     935      if ($context->contextlevel != CONTEXT_MODULE) {
     936          return false;
     937      }
     938  
     939      require_login($course, true, $cm);
     940  
     941      $canmanageactivity = has_capability('moodle/course:manageactivities', $context);
     942      $lifetime = null;
     943  
     944      // Check SCORM availability.
     945      if (!$canmanageactivity) {
     946          require_once($CFG->dirroot.'/mod/scorm/locallib.php');
     947  
     948          $scorm = $DB->get_record('scorm', array('id' => $cm->instance), 'id, timeopen, timeclose', MUST_EXIST);
     949          list($available, $warnings) = scorm_get_availability_status($scorm);
     950          if (!$available) {
     951              return false;
     952          }
     953      }
     954  
     955      if ($filearea === 'content') {
     956          $revision = (int)array_shift($args); // Prevents caching problems - ignored here.
     957          $relativepath = implode('/', $args);
     958          $fullpath = "/$context->id/mod_scorm/content/0/$relativepath";
     959          // TODO: add any other access restrictions here if needed!
     960  
     961      } else if ($filearea === 'package') {
     962          if (!$canmanageactivity) {
     963              return false;
     964          }
     965          $revision = (int)array_shift($args); // Prevents caching problems - ignored here.
     966          $relativepath = implode('/', $args);
     967          $fullpath = "/$context->id/mod_scorm/package/0/$relativepath";
     968          $lifetime = 0; // No caching here.
     969  
     970      } else if ($filearea === 'imsmanifest') { // This isn't a real filearea, it's a url parameter for this type of package.
     971          $revision = (int)array_shift($args); // Prevents caching problems - ignored here.
     972          $relativepath = implode('/', $args);
     973  
     974          // Get imsmanifest file.
     975          $fs = get_file_storage();
     976          $files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, '', false);
     977          $file = reset($files);
     978  
     979          // Check that the package file is an imsmanifest.xml file - if not then this method is not allowed.
     980          $packagefilename = $file->get_filename();
     981          if (strtolower($packagefilename) !== 'imsmanifest.xml') {
     982              return false;
     983          }
     984  
     985          $file->send_relative_file($relativepath);
     986      } else {
     987          return false;
     988      }
     989  
     990      $fs = get_file_storage();
     991      if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
     992          if ($filearea === 'content') { // Return file not found straight away to improve performance.
     993              send_header_404();
     994              die;
     995          }
     996          return false;
     997      }
     998  
     999      // Finally send the file.
    1000      send_stored_file($file, $lifetime, 0, false, $options);
    1001  }
    1002  
    1003  /**
    1004   * @uses FEATURE_GROUPS
    1005   * @uses FEATURE_GROUPINGS
    1006   * @uses FEATURE_MOD_INTRO
    1007   * @uses FEATURE_COMPLETION_TRACKS_VIEWS
    1008   * @uses FEATURE_COMPLETION_HAS_RULES
    1009   * @uses FEATURE_GRADE_HAS_GRADE
    1010   * @uses FEATURE_GRADE_OUTCOMES
    1011   * @param string $feature FEATURE_xx constant for requested feature
    1012   * @return mixed True if module supports feature, false if not, null if doesn't know
    1013   */
    1014  function scorm_supports($feature) {
    1015      switch($feature) {
    1016          case FEATURE_GROUPS:                  return false;
    1017          case FEATURE_GROUPINGS:               return false;
    1018          case FEATURE_MOD_INTRO:               return true;
    1019          case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
    1020          case FEATURE_COMPLETION_HAS_RULES:    return true;
    1021          case FEATURE_GRADE_HAS_GRADE:         return true;
    1022          case FEATURE_GRADE_OUTCOMES:          return true;
    1023          case FEATURE_BACKUP_MOODLE2:          return true;
    1024          case FEATURE_SHOW_DESCRIPTION:        return true;
    1025  
    1026          default: return null;
    1027      }
    1028  }
    1029  
    1030  /**
    1031   * Get the filename for a temp log file
    1032   *
    1033   * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
    1034   * @param integer $scoid - scoid of object this log entry is for
    1035   * @return string The filename as an absolute path
    1036   */
    1037  function scorm_debug_log_filename($type, $scoid) {
    1038      global $CFG, $USER;
    1039  
    1040      $logpath = $CFG->tempdir.'/scormlogs';
    1041      $logfile = $logpath.'/'.$type.'debug_'.$USER->id.'_'.$scoid.'.log';
    1042      return $logfile;
    1043  }
    1044  
    1045  /**
    1046   * writes log output to a temp log file
    1047   *
    1048   * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
    1049   * @param string $text - text to be written to file.
    1050   * @param integer $scoid - scoid of object this log entry is for.
    1051   */
    1052  function scorm_debug_log_write($type, $text, $scoid) {
    1053      global $CFG;
    1054  
    1055      $debugenablelog = get_config('scorm', 'allowapidebug');
    1056      if (!$debugenablelog || empty($text)) {
    1057          return;
    1058      }
    1059      if (make_temp_directory('scormlogs/')) {
    1060          $logfile = scorm_debug_log_filename($type, $scoid);
    1061          @file_put_contents($logfile, date('Y/m/d H:i:s O')." DEBUG $text\r\n", FILE_APPEND);
    1062          @chmod($logfile, $CFG->filepermissions);
    1063      }
    1064  }
    1065  
    1066  /**
    1067   * Remove debug log file
    1068   *
    1069   * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
    1070   * @param integer $scoid - scoid of object this log entry is for
    1071   * @return boolean True if the file is successfully deleted, false otherwise
    1072   */
    1073  function scorm_debug_log_remove($type, $scoid) {
    1074  
    1075      $debugenablelog = get_config('scorm', 'allowapidebug');
    1076      $logfile = scorm_debug_log_filename($type, $scoid);
    1077      if (!$debugenablelog || !file_exists($logfile)) {
    1078          return false;
    1079      }
    1080  
    1081      return @unlink($logfile);
    1082  }
    1083  
    1084  /**
    1085   * writes overview info for course_overview block - displays upcoming scorm objects that have a due date
    1086   *
    1087   * @param object $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
    1088   * @param array $htmlarray
    1089   * @return mixed
    1090   */
    1091  function scorm_print_overview($courses, &$htmlarray) {
    1092      global $USER, $CFG;
    1093  
    1094      if (empty($courses) || !is_array($courses) || count($courses) == 0) {
    1095          return array();
    1096      }
    1097  
    1098      if (!$scorms = get_all_instances_in_courses('scorm', $courses)) {
    1099          return;
    1100      }
    1101  
    1102      $strscorm   = get_string('modulename', 'scorm');
    1103      $strduedate = get_string('duedate', 'scorm');
    1104  
    1105      foreach ($scorms as $scorm) {
    1106          $time = time();
    1107          $showattemptstatus = false;
    1108          if ($scorm->timeopen) {
    1109              $isopen = ($scorm->timeopen <= $time && $time <= $scorm->timeclose);
    1110          }
    1111          if ($scorm->displayattemptstatus == SCORM_DISPLAY_ATTEMPTSTATUS_ALL ||
    1112                  $scorm->displayattemptstatus == SCORM_DISPLAY_ATTEMPTSTATUS_MY) {
    1113              $showattemptstatus = true;
    1114          }
    1115          if ($showattemptstatus || !empty($isopen) || !empty($scorm->timeclose)) {
    1116              $str = html_writer::start_div('scorm overview').html_writer::div($strscorm. ': '.
    1117                      html_writer::link($CFG->wwwroot.'/mod/scorm/view.php?id='.$scorm->coursemodule, $scorm->name,
    1118                                          array('title' => $strscorm, 'class' => $scorm->visible ? '' : 'dimmed')), 'name');
    1119              if ($scorm->timeclose) {
    1120                  $str .= html_writer::div($strduedate.': '.userdate($scorm->timeclose), 'info');
    1121              }
    1122              if ($showattemptstatus) {
    1123                  require_once($CFG->dirroot.'/mod/scorm/locallib.php');
    1124                  $str .= html_writer::div(scorm_get_attempt_status($USER, $scorm), 'details');
    1125              }
    1126              $str .= html_writer::end_div();
    1127              if (empty($htmlarray[$scorm->course]['scorm'])) {
    1128                  $htmlarray[$scorm->course]['scorm'] = $str;
    1129              } else {
    1130                  $htmlarray[$scorm->course]['scorm'] .= $str;
    1131              }
    1132          }
    1133      }
    1134  }
    1135  
    1136  /**
    1137   * Return a list of page types
    1138   * @param string $pagetype current page type
    1139   * @param stdClass $parentcontext Block's parent context
    1140   * @param stdClass $currentcontext Current context of block
    1141   */
    1142  function scorm_page_type_list($pagetype, $parentcontext, $currentcontext) {
    1143      $modulepagetype = array('mod-scorm-*' => get_string('page-mod-scorm-x', 'scorm'));
    1144      return $modulepagetype;
    1145  }
    1146  
    1147  /**
    1148   * Returns the SCORM version used.
    1149   * @param string $scormversion comes from $scorm->version
    1150   * @param string $version one of the defined vars SCORM_12, SCORM_13, SCORM_AICC (or empty)
    1151   * @return Scorm version.
    1152   */
    1153  function scorm_version_check($scormversion, $version='') {
    1154      $scormversion = trim(strtolower($scormversion));
    1155      if (empty($version) || $version == SCORM_12) {
    1156          if ($scormversion == 'scorm_12' || $scormversion == 'scorm_1.2') {
    1157              return SCORM_12;
    1158          }
    1159          if (!empty($version)) {
    1160              return false;
    1161          }
    1162      }
    1163      if (empty($version) || $version == SCORM_13) {
    1164          if ($scormversion == 'scorm_13' || $scormversion == 'scorm_1.3') {
    1165              return SCORM_13;
    1166          }
    1167          if (!empty($version)) {
    1168              return false;
    1169          }
    1170      }
    1171      if (empty($version) || $version == SCORM_AICC) {
    1172          if (strpos($scormversion, 'aicc')) {
    1173              return SCORM_AICC;
    1174          }
    1175          if (!empty($version)) {
    1176              return false;
    1177          }
    1178      }
    1179      return false;
    1180  }
    1181  
    1182  /**
    1183   * Obtains the automatic completion state for this scorm based on any conditions
    1184   * in scorm settings.
    1185   *
    1186   * @param object $course Course
    1187   * @param object $cm Course-module
    1188   * @param int $userid User ID
    1189   * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
    1190   * @return bool True if completed, false if not. (If no conditions, then return
    1191   *   value depends on comparison type)
    1192   */
    1193  function scorm_get_completion_state($course, $cm, $userid, $type) {
    1194      global $DB;
    1195  
    1196      $result = $type;
    1197  
    1198      // Get scorm.
    1199      if (!$scorm = $DB->get_record('scorm', array('id' => $cm->instance))) {
    1200          print_error('cannotfindscorm');
    1201      }
    1202      // Only check for existence of tracks and return false if completionstatusrequired or completionscorerequired
    1203      // this means that if only view is required we don't end up with a false state.
    1204      if ($scorm->completionstatusrequired !== null ||
    1205          $scorm->completionscorerequired !== null) {
    1206          // Get user's tracks data.
    1207          $tracks = $DB->get_records_sql(
    1208              "
    1209              SELECT
    1210                  id,
    1211                  element,
    1212                  value
    1213              FROM
    1214                  {scorm_scoes_track}
    1215              WHERE
    1216                  scormid = ?
    1217              AND userid = ?
    1218              AND element IN
    1219              (
    1220                  'cmi.core.lesson_status',
    1221                  'cmi.completion_status',
    1222                  'cmi.success_status',
    1223                  'cmi.core.score.raw',
    1224                  'cmi.score.raw'
    1225              )
    1226              ",
    1227              array($scorm->id, $userid)
    1228          );
    1229  
    1230          if (!$tracks) {
    1231              return completion_info::aggregate_completion_states($type, $result, false);
    1232          }
    1233      }
    1234  
    1235      // Check for status.
    1236      if ($scorm->completionstatusrequired !== null) {
    1237  
    1238          // Get status.
    1239          $statuses = array_flip(scorm_status_options());
    1240          $nstatus = 0;
    1241  
    1242          foreach ($tracks as $track) {
    1243              if (!in_array($track->element, array('cmi.core.lesson_status', 'cmi.completion_status', 'cmi.success_status'))) {
    1244                  continue;
    1245              }
    1246  
    1247              if (array_key_exists($track->value, $statuses)) {
    1248                  $nstatus |= $statuses[$track->value];
    1249              }
    1250          }
    1251  
    1252          if ($scorm->completionstatusrequired & $nstatus) {
    1253              return completion_info::aggregate_completion_states($type, $result, true);
    1254          } else {
    1255              return completion_info::aggregate_completion_states($type, $result, false);
    1256          }
    1257  
    1258      }
    1259  
    1260      // Check for score.
    1261      if ($scorm->completionscorerequired !== null) {
    1262          $maxscore = -1;
    1263  
    1264          foreach ($tracks as $track) {
    1265              if (!in_array($track->element, array('cmi.core.score.raw', 'cmi.score.raw'))) {
    1266                  continue;
    1267              }
    1268  
    1269              if (strlen($track->value) && floatval($track->value) >= $maxscore) {
    1270                  $maxscore = floatval($track->value);
    1271              }
    1272          }
    1273  
    1274          if ($scorm->completionscorerequired <= $maxscore) {
    1275              return completion_info::aggregate_completion_states($type, $result, true);
    1276          } else {
    1277              return completion_info::aggregate_completion_states($type, $result, false);
    1278          }
    1279      }
    1280  
    1281      return $result;
    1282  }
    1283  
    1284  /**
    1285   * Register the ability to handle drag and drop file uploads
    1286   * @return array containing details of the files / types the mod can handle
    1287   */
    1288  function scorm_dndupload_register() {
    1289      return array('files' => array(
    1290          array('extension' => 'zip', 'message' => get_string('dnduploadscorm', 'scorm'))
    1291      ));
    1292  }
    1293  
    1294  /**
    1295   * Handle a file that has been uploaded
    1296   * @param object $uploadinfo details of the file / content that has been uploaded
    1297   * @return int instance id of the newly created mod
    1298   */
    1299  function scorm_dndupload_handle($uploadinfo) {
    1300  
    1301      $context = context_module::instance($uploadinfo->coursemodule);
    1302      file_save_draft_area_files($uploadinfo->draftitemid, $context->id, 'mod_scorm', 'package', 0);
    1303      $fs = get_file_storage();
    1304      $files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, 'sortorder, itemid, filepath, filename', false);
    1305      $file = reset($files);
    1306  
    1307      // Validate the file, make sure it's a valid SCORM package!
    1308      $errors = scorm_validate_package($file);
    1309      if (!empty($errors)) {
    1310          return false;
    1311      }
    1312      // Create a default scorm object to pass to scorm_add_instance()!
    1313      $scorm = get_config('scorm');
    1314      $scorm->course = $uploadinfo->course->id;
    1315      $scorm->coursemodule = $uploadinfo->coursemodule;
    1316      $scorm->cmidnumber = '';
    1317      $scorm->name = $uploadinfo->displayname;
    1318      $scorm->scormtype = SCORM_TYPE_LOCAL;
    1319      $scorm->reference = $file->get_filename();
    1320      $scorm->intro = '';
    1321      $scorm->width = $scorm->framewidth;
    1322      $scorm->height = $scorm->frameheight;
    1323  
    1324      return scorm_add_instance($scorm, null);
    1325  }
    1326  
    1327  /**
    1328   * Sets activity completion state
    1329   *
    1330   * @param object $scorm object
    1331   * @param int $userid User ID
    1332   * @param int $completionstate Completion state
    1333   * @param array $grades grades array of users with grades - used when $userid = 0
    1334   */
    1335  function scorm_set_completion($scorm, $userid, $completionstate = COMPLETION_COMPLETE, $grades = array()) {
    1336      $course = new stdClass();
    1337      $course->id = $scorm->course;
    1338      $completion = new completion_info($course);
    1339  
    1340      // Check if completion is enabled site-wide, or for the course.
    1341      if (!$completion->is_enabled()) {
    1342          return;
    1343      }
    1344  
    1345      $cm = get_coursemodule_from_instance('scorm', $scorm->id, $scorm->course);
    1346      if (empty($cm) || !$completion->is_enabled($cm)) {
    1347              return;
    1348      }
    1349  
    1350      if (empty($userid)) { // We need to get all the relevant users from $grades param.
    1351          foreach ($grades as $grade) {
    1352              $completion->update_state($cm, $completionstate, $grade->userid);
    1353          }
    1354      } else {
    1355          $completion->update_state($cm, $completionstate, $userid);
    1356      }
    1357  }
    1358  
    1359  /**
    1360   * Check that a Zip file contains a valid SCORM package
    1361   *
    1362   * @param $file stored_file a Zip file.
    1363   * @return array empty if no issue is found. Array of error message otherwise
    1364   */
    1365  function scorm_validate_package($file) {
    1366      $packer = get_file_packer('application/zip');
    1367      $errors = array();
    1368      if ($file->is_external_file()) { // Get zip file so we can check it is correct.
    1369          $file->import_external_file_contents();
    1370      }
    1371      $filelist = $file->list_files($packer);
    1372  
    1373      if (!is_array($filelist)) {
    1374          $errors['packagefile'] = get_string('badarchive', 'scorm');
    1375      } else {
    1376          $aiccfound = false;
    1377          $badmanifestpresent = false;
    1378          foreach ($filelist as $info) {
    1379              if ($info->pathname == 'imsmanifest.xml') {
    1380                  return array();
    1381              } else if (strpos($info->pathname, 'imsmanifest.xml') !== false) {
    1382                  // This package has an imsmanifest file inside a folder of the package.
    1383                  $badmanifestpresent = true;
    1384              }
    1385              if (preg_match('/\.cst$/', $info->pathname)) {
    1386                  return array();
    1387              }
    1388          }
    1389          if (!$aiccfound) {
    1390              if ($badmanifestpresent) {
    1391                  $errors['packagefile'] = get_string('badimsmanifestlocation', 'scorm');
    1392              } else {
    1393                  $errors['packagefile'] = get_string('nomanifest', 'scorm');
    1394              }
    1395          }
    1396      }
    1397      return $errors;
    1398  }
    1399  
    1400  /**
    1401   * Check and set the correct mode and attempt when entering a SCORM package.
    1402   *
    1403   * @param object $scorm object
    1404   * @param string $newattempt should a new attempt be generated here.
    1405   * @param int $attempt the attempt number this is for.
    1406   * @param int $userid the userid of the user.
    1407   * @param string $mode the current mode that has been selected.
    1408   */
    1409  function scorm_check_mode($scorm, &$newattempt, &$attempt, $userid, &$mode) {
    1410      global $DB;
    1411  
    1412      if (($mode == 'browse')) {
    1413          if ($scorm->hidebrowse == 1) {
    1414              // Prevent Browse mode if hidebrowse is set.
    1415              $mode = 'normal';
    1416          } else {
    1417              // We don't need to check attempts as browse mode is set.
    1418              return;
    1419          }
    1420      }
    1421      // Check if the scorm module is incomplete (used to validate user request to start a new attempt).
    1422      $incomplete = true;
    1423      $tracks = $DB->get_recordset('scorm_scoes_track', array('scormid' => $scorm->id, 'userid' => $userid,
    1424          'attempt' => $attempt, 'element' => 'cmi.core.lesson_status'));
    1425      foreach ($tracks as $track) {
    1426          if (($track->value == 'completed') || ($track->value == 'passed') || ($track->value == 'failed')) {
    1427              $incomplete = false;
    1428          } else {
    1429              $incomplete = true;
    1430              break; // Found an incomplete sco, so the result as a whole is incomplete.
    1431          }
    1432      }
    1433      $tracks->close();
    1434  
    1435      // Validate user request to start a new attempt.
    1436      if ($incomplete === true) {
    1437          // The option to start a new attempt should never have been presented. Force false.
    1438          $newattempt = 'off';
    1439      } else if (!empty($scorm->forcenewattempt)) {
    1440          // A new attempt should be forced for already completed attempts.
    1441          $newattempt = 'on';
    1442      }
    1443  
    1444      if (($newattempt == 'on') && (($attempt < $scorm->maxattempt) || ($scorm->maxattempt == 0))) {
    1445          $attempt++;
    1446          $mode = 'normal';
    1447      } else { // Check if review mode should be set.
    1448          if ($incomplete === true) {
    1449              $mode = 'normal';
    1450          } else {
    1451              $mode = 'review';
    1452          }
    1453      }
    1454  }
    

    Search This Site: