Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

   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   * functions used by SCORM 1.2/2004 packages.
  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  function scorm_get_resources($blocks) {
  26      $resources = array();
  27      foreach ($blocks as $block) {
  28          if ($block['name'] == 'RESOURCES' && isset($block['children'])) {
  29              foreach ($block['children'] as $resource) {
  30                  if ($resource['name'] == 'RESOURCE') {
  31                      $resources[addslashes_js($resource['attrs']['IDENTIFIER'])] = $resource['attrs'];
  32                  }
  33              }
  34          }
  35      }
  36      return $resources;
  37  }
  38  
  39  function scorm_get_manifest($blocks, $scoes) {
  40      global $OUTPUT;
  41      static $parents = array();
  42      static $resources;
  43  
  44      static $manifest;
  45      static $organization;
  46  
  47      $manifestresourcesnotfound = array();
  48      if (count($blocks) > 0) {
  49          foreach ($blocks as $block) {
  50              switch ($block['name']) {
  51                  case 'METADATA':
  52                      if (isset($block['children'])) {
  53                          foreach ($block['children'] as $metadata) {
  54                              if ($metadata['name'] == 'SCHEMAVERSION') {
  55                                  if (empty($scoes->version)) {
  56                                      $isversionset = (preg_match("/^(1\.2)$|^(CAM )?(1\.3)$/", $metadata['tagData'], $matches));
  57                                      if (isset($metadata['tagData']) && $isversionset) {
  58                                          $scoes->version = 'SCORM_'.$matches[count($matches) - 1];
  59                                      } else {
  60                                          $isversionset = (preg_match("/^2004 (3rd|4th) Edition$/", $metadata['tagData'], $matches));
  61                                          if (isset($metadata['tagData']) && $isversionset) {
  62                                              $scoes->version = 'SCORM_1.3';
  63                                          } else {
  64                                              $scoes->version = 'SCORM_1.2';
  65                                          }
  66                                      }
  67                                  }
  68                              }
  69                          }
  70                      }
  71                  break;
  72                  case 'MANIFEST':
  73                      $manifest = $block['attrs']['IDENTIFIER'];
  74                      $organization = '';
  75                      $resources = array();
  76                      $resources = scorm_get_resources($block['children']);
  77                      $scoes = scorm_get_manifest($block['children'], $scoes);
  78                      if (empty($scoes->elements) || count($scoes->elements) <= 0) {
  79                          foreach ($resources as $item => $resource) {
  80                              if (!empty($resource['HREF'])) {
  81                                  $sco = new stdClass();
  82                                  $sco->identifier = $item;
  83                                  $sco->title = $item;
  84                                  $sco->parent = '/';
  85                                  $sco->launch = $resource['HREF'];
  86                                  $sco->scormtype = $resource['ADLCP:SCORMTYPE'];
  87                                  $scoes->elements[$manifest][$organization][$item] = $sco;
  88                              }
  89                          }
  90                      }
  91                  break;
  92                  case 'ORGANIZATIONS':
  93                      if (!isset($scoes->defaultorg) && isset($block['attrs']['DEFAULT'])) {
  94                          $scoes->defaultorg = $block['attrs']['DEFAULT'];
  95                      }
  96                      if (!empty($block['children'])) {
  97                          $scoes = scorm_get_manifest($block['children'], $scoes);
  98                      }
  99                  break;
 100                  case 'ORGANIZATION':
 101                      $identifier = $block['attrs']['IDENTIFIER'];
 102                      $organization = '';
 103                      $scoes->elements[$manifest][$organization][$identifier] = new stdClass();
 104                      $scoes->elements[$manifest][$organization][$identifier]->identifier = $identifier;
 105                      $scoes->elements[$manifest][$organization][$identifier]->parent = '/';
 106                      $scoes->elements[$manifest][$organization][$identifier]->launch = '';
 107                      $scoes->elements[$manifest][$organization][$identifier]->scormtype = '';
 108  
 109                      $parents = array();
 110                      $parent = new stdClass();
 111                      $parent->identifier = $identifier;
 112                      $parent->organization = $organization;
 113                      array_push($parents, $parent);
 114                      $organization = $identifier;
 115  
 116                      if (!empty($block['children'])) {
 117                          $scoes = scorm_get_manifest($block['children'], $scoes);
 118                      }
 119  
 120                      array_pop($parents);
 121                  break;
 122                  case 'ITEM':
 123                      $parent = array_pop($parents);
 124                      array_push($parents, $parent);
 125  
 126                      $identifier = $block['attrs']['IDENTIFIER'];
 127                      $scoes->elements[$manifest][$organization][$identifier] = new stdClass();
 128                      $scoes->elements[$manifest][$organization][$identifier]->identifier = $identifier;
 129                      $scoes->elements[$manifest][$organization][$identifier]->parent = $parent->identifier;
 130                      if (!isset($block['attrs']['ISVISIBLE'])) {
 131                          $block['attrs']['ISVISIBLE'] = 'true';
 132                      }
 133                      $scoes->elements[$manifest][$organization][$identifier]->isvisible = $block['attrs']['ISVISIBLE'];
 134                      if (!isset($block['attrs']['PARAMETERS'])) {
 135                          $block['attrs']['PARAMETERS'] = '';
 136                      }
 137                      $scoes->elements[$manifest][$organization][$identifier]->parameters = $block['attrs']['PARAMETERS'];
 138                      if (!isset($block['attrs']['IDENTIFIERREF'])) {
 139                          $scoes->elements[$manifest][$organization][$identifier]->launch = '';
 140                          $scoes->elements[$manifest][$organization][$identifier]->scormtype = 'asset';
 141                      } else {
 142                          $idref = addslashes_js($block['attrs']['IDENTIFIERREF']);
 143                          $base = '';
 144                          if (isset($resources[$idref]['XML:BASE'])) {
 145                              $base = $resources[$idref]['XML:BASE'];
 146                          }
 147                          if (!isset($resources[$idref])) {
 148                              $manifestresourcesnotfound[] = $idref;
 149                              $scoes->elements[$manifest][$organization][$identifier]->launch = '';
 150                          } else {
 151                              $scoes->elements[$manifest][$organization][$identifier]->launch = $base.$resources[$idref]['HREF'];
 152                              if (empty($resources[$idref]['ADLCP:SCORMTYPE'])) {
 153                                  $resources[$idref]['ADLCP:SCORMTYPE'] = 'asset';
 154                              }
 155                              $scoes->elements[$manifest][$organization][$identifier]->scormtype = $resources[$idref]['ADLCP:SCORMTYPE'];
 156                          }
 157                      }
 158  
 159                      $parent = new stdClass();
 160                      $parent->identifier = $identifier;
 161                      $parent->organization = $organization;
 162                      array_push($parents, $parent);
 163  
 164                      if (!empty($block['children'])) {
 165                          $scoes = scorm_get_manifest($block['children'], $scoes);
 166                      }
 167  
 168                      array_pop($parents);
 169                  break;
 170                  case 'TITLE':
 171                      $parent = array_pop($parents);
 172                      array_push($parents, $parent);
 173                      if (!isset($block['tagData'])) {
 174                          $block['tagData'] = '';
 175                      }
 176                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->title = $block['tagData'];
 177                  break;
 178                  case 'ADLCP:PREREQUISITES':
 179                      if ($block['attrs']['TYPE'] == 'aicc_script') {
 180                          $parent = array_pop($parents);
 181                          array_push($parents, $parent);
 182                          if (!isset($block['tagData'])) {
 183                              $block['tagData'] = '';
 184                          }
 185                          $scoes->elements[$manifest][$parent->organization][$parent->identifier]->prerequisites = $block['tagData'];
 186                      }
 187                  break;
 188                  case 'ADLCP:MAXTIMEALLOWED':
 189                      $parent = array_pop($parents);
 190                      array_push($parents, $parent);
 191                      if (!isset($block['tagData'])) {
 192                          $block['tagData'] = '';
 193                      }
 194                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->maxtimeallowed = $block['tagData'];
 195                  break;
 196                  case 'ADLCP:TIMELIMITACTION':
 197                      $parent = array_pop($parents);
 198                      array_push($parents, $parent);
 199                      if (!isset($block['tagData'])) {
 200                          $block['tagData'] = '';
 201                      }
 202                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->timelimitaction = $block['tagData'];
 203                  break;
 204                  case 'ADLCP:DATAFROMLMS':
 205                      $parent = array_pop($parents);
 206                      array_push($parents, $parent);
 207                      if (!isset($block['tagData'])) {
 208                          $block['tagData'] = '';
 209                      }
 210                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->datafromlms = $block['tagData'];
 211                  break;
 212                  case 'ADLCP:MASTERYSCORE':
 213                      $parent = array_pop($parents);
 214                      array_push($parents, $parent);
 215                      if (!isset($block['tagData'])) {
 216                          $block['tagData'] = '';
 217                      }
 218                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->masteryscore = $block['tagData'];
 219                  break;
 220                  case 'ADLCP:COMPLETIONTHRESHOLD':
 221                      $parent = array_pop($parents);
 222                      array_push($parents, $parent);
 223                      if (!isset($block['attrs']['MINPROGRESSMEASURE'])) {
 224                          $block['attrs']['MINPROGRESSMEASURE'] = '1.0';
 225                      }
 226                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->threshold = $block['attrs']['MINPROGRESSMEASURE'];
 227                  break;
 228                  case 'ADLNAV:PRESENTATION':
 229                      $parent = array_pop($parents);
 230                      array_push($parents, $parent);
 231                      if (!empty($block['children'])) {
 232                          foreach ($block['children'] as $adlnav) {
 233                              if ($adlnav['name'] == 'ADLNAV:NAVIGATIONINTERFACE') {
 234                                  foreach ($adlnav['children'] as $adlnavinterface) {
 235                                      if ($adlnavinterface['name'] == 'ADLNAV:HIDELMSUI') {
 236                                          if ($adlnavinterface['tagData'] == 'continue') {
 237                                              $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hidecontinue = 1;
 238                                          }
 239                                          if ($adlnavinterface['tagData'] == 'previous') {
 240                                              $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideprevious = 1;
 241                                          }
 242                                          if ($adlnavinterface['tagData'] == 'exit') {
 243                                              $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideexit = 1;
 244                                          }
 245                                          if ($adlnavinterface['tagData'] == 'exitAll') {
 246                                              $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideexitall = 1;
 247                                          }
 248                                          if ($adlnavinterface['tagData'] == 'abandon') {
 249                                              $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideabandon = 1;
 250                                          }
 251                                          if ($adlnavinterface['tagData'] == 'abandonAll') {
 252                                              $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hideabandonall = 1;
 253                                          }
 254                                          if ($adlnavinterface['tagData'] == 'suspendAll') {
 255                                              $scoes->elements[$manifest][$parent->organization][$parent->identifier]->hidesuspendall = 1;
 256                                          }
 257                                      }
 258                                  }
 259                              }
 260                          }
 261                      }
 262                  break;
 263                  case 'IMSSS:SEQUENCING':
 264                      $parent = array_pop($parents);
 265                      array_push($parents, $parent);
 266                      if (!empty($block['children'])) {
 267                          foreach ($block['children'] as $sequencing) {
 268                              if ($sequencing['name'] == 'IMSSS:CONTROLMODE') {
 269                                  if (isset($sequencing['attrs']['CHOICE'])) {
 270                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->choice =
 271                                          $sequencing['attrs']['CHOICE'] == 'true' ? 1 : 0;
 272                                  }
 273                                  if (isset($sequencing['attrs']['CHOICEEXIT'])) {
 274                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->choiceexit =
 275                                          $sequencing['attrs']['CHOICEEXIT'] == 'true' ? 1 : 0;
 276                                  }
 277                                  if (isset($sequencing['attrs']['FLOW'])) {
 278                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->flow =
 279                                          $sequencing['attrs']['FLOW'] == 'true' ? 1 : 0;
 280                                  }
 281                                  if (isset($sequencing['attrs']['FORWARDONLY'])) {
 282                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->forwardonly =
 283                                          $sequencing['attrs']['FORWARDONLY'] == 'true' ? 1 : 0;
 284                                  }
 285                                  if (isset($sequencing['attrs']['USECURRENTATTEMPTOBJECTINFO'])) {
 286                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->usecurrentattemptobjectinfo =
 287                                          $sequencing['attrs']['USECURRENTATTEMPTOBJECTINFO'] == 'true' ? 1 : 0;
 288                                  }
 289                                  if (isset($sequencing['attrs']['USECURRENTATTEMPTPROGRESSINFO'])) {
 290                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->usecurrentattemptprogressinfo =
 291                                          $sequencing['attrs']['USECURRENTATTEMPTPROGRESSINFO'] == 'true' ? 1 : 0;
 292                                  }
 293                              }
 294                              if ($sequencing['name'] == 'IMSSS:DELIVERYCONTROLS') {
 295                                  if (isset($sequencing['attrs']['TRACKED'])) {
 296                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->tracked =
 297                                          $sequencing['attrs']['TRACKED'] == 'true' ? 1 : 0;
 298                                  }
 299                                  if (isset($sequencing['attrs']['COMPLETIONSETBYCONTENT'])) {
 300                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->completionsetbycontent =
 301                                          $sequencing['attrs']['COMPLETIONSETBYCONTENT'] == 'true' ? 1 : 0;
 302                                  }
 303                                  if (isset($sequencing['attrs']['OBJECTIVESETBYCONTENT'])) {
 304                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->objectivesetbycontent =
 305                                          $sequencing['attrs']['OBJECTIVESETBYCONTENT'] == 'true' ? 1 : 0;
 306                                  }
 307                              }
 308                              if ($sequencing['name'] == 'ADLSEQ:CONSTRAINEDCHOICECONSIDERATIONS') {
 309                                  if (isset($sequencing['attrs']['CONSTRAINCHOICE'])) {
 310                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->constrainChoice =
 311                                          $sequencing['attrs']['CONSTRAINCHOICE'] == 'true' ? 1 : 0;
 312                                  }
 313                                  if (isset($sequencing['attrs']['PREVENTACTIVATION'])) {
 314                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->preventactivation =
 315                                          $sequencing['attrs']['PREVENTACTIVATION'] == 'true' ? 1 : 0;
 316                                  }
 317                              }
 318                              if ($sequencing['name'] == 'IMSSS:OBJECTIVES') {
 319                                  $objectives = array();
 320                                  foreach ($sequencing['children'] as $objective) {
 321                                      $objectivedata = new stdClass();
 322                                      $objectivedata->primaryobj = 0;
 323                                      switch ($objective['name']) {
 324                                          case 'IMSSS:PRIMARYOBJECTIVE':
 325                                              $objectivedata->primaryobj = 1;
 326                                          case 'IMSSS:OBJECTIVE':
 327                                              $objectivedata->satisfiedbymeasure = 0;
 328                                              if (isset($objective['attrs']['SATISFIEDBYMEASURE'])) {
 329                                                  $objectivedata->satisfiedbymeasure =
 330                                                      $objective['attrs']['SATISFIEDBYMEASURE'] == 'true' ? 1 : 0;
 331                                              }
 332                                              $objectivedata->objectiveid = '';
 333                                              if (isset($objective['attrs']['OBJECTIVEID'])) {
 334                                                  $objectivedata->objectiveid = $objective['attrs']['OBJECTIVEID'];
 335                                              }
 336                                              $objectivedata->minnormalizedmeasure = 1.0;
 337                                              if (!empty($objective['children'])) {
 338                                                  $mapinfos = array();
 339                                                  foreach ($objective['children'] as $objectiveparam) {
 340                                                      if ($objectiveparam['name'] == 'IMSSS:MINNORMALIZEDMEASURE') {
 341                                                          if (isset($objectiveparam['tagData'])) {
 342                                                              $objectivedata->minnormalizedmeasure = $objectiveparam['tagData'];
 343                                                          } else {
 344                                                              $objectivedata->minnormalizedmeasure = 0;
 345                                                          }
 346                                                      }
 347                                                      if ($objectiveparam['name'] == 'IMSSS:MAPINFO') {
 348                                                          $mapinfo = new stdClass();
 349                                                          $mapinfo->targetobjectiveid = '';
 350                                                          if (isset($objectiveparam['attrs']['TARGETOBJECTIVEID'])) {
 351                                                              $mapinfo->targetobjectiveid =
 352                                                                  $objectiveparam['attrs']['TARGETOBJECTIVEID'];
 353                                                          }
 354                                                          $mapinfo->readsatisfiedstatus = 1;
 355                                                          if (isset($objectiveparam['attrs']['READSATISFIEDSTATUS'])) {
 356                                                              $mapinfo->readsatisfiedstatus =
 357                                                                  $objectiveparam['attrs']['READSATISFIEDSTATUS'] == 'true' ? 1 : 0;
 358                                                          }
 359                                                          $mapinfo->writesatisfiedstatus = 0;
 360                                                          if (isset($objectiveparam['attrs']['WRITESATISFIEDSTATUS'])) {
 361                                                              $mapinfo->writesatisfiedstatus =
 362                                                                  $objectiveparam['attrs']['WRITESATISFIEDSTATUS'] == 'true' ? 1 : 0;
 363                                                          }
 364                                                          $mapinfo->readnormalizemeasure = 1;
 365                                                          if (isset($objectiveparam['attrs']['READNORMALIZEDMEASURE'])) {
 366                                                              $mapinfo->readnormalizemeasure =
 367                                                                  $objectiveparam['attrs']['READNORMALIZEDMEASURE'] == 'true' ? 1 : 0;
 368                                                          }
 369                                                          $mapinfo->writenormalizemeasure = 0;
 370                                                          if (isset($objectiveparam['attrs']['WRITENORMALIZEDMEASURE'])) {
 371                                                              $mapinfo->writenormalizemeasure =
 372                                                                  $objectiveparam['attrs']['WRITENORMALIZEDMEASURE'] == 'true' ? 1 : 0;
 373                                                          }
 374                                                          array_push($mapinfos, $mapinfo);
 375                                                      }
 376                                                  }
 377                                                  if (!empty($mapinfos)) {
 378                                                      $objectivesdata->mapinfos = $mapinfos;
 379                                                  }
 380                                              }
 381                                          break;
 382                                      }
 383                                      array_push($objectives, $objectivedata);
 384                                  }
 385                                  $scoes->elements[$manifest][$parent->organization][$parent->identifier]->objectives = $objectives;
 386                              }
 387                              if ($sequencing['name'] == 'IMSSS:LIMITCONDITIONS') {
 388                                  if (isset($sequencing['attrs']['ATTEMPTLIMIT'])) {
 389                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->attemptLimit =
 390                                          $sequencing['attrs']['ATTEMPTLIMIT'];
 391                                  }
 392                                  if (isset($sequencing['attrs']['ATTEMPTABSOLUTEDURATIONLIMIT'])) {
 393                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->attemptAbsoluteDurationLimit =
 394                                          $sequencing['attrs']['ATTEMPTABSOLUTEDURATIONLIMIT'];
 395                                  }
 396                              }
 397                              if ($sequencing['name'] == 'IMSSS:ROLLUPRULES') {
 398                                  if (isset($sequencing['attrs']['ROLLUPOBJECTIVESATISFIED'])) {
 399                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->rollupobjectivesatisfied =
 400                                          $sequencing['attrs']['ROLLUPOBJECTIVESATISFIED'] == 'true' ? 1 : 0;
 401                                  }
 402                                  if (isset($sequencing['attrs']['ROLLUPPROGRESSCOMPLETION'])) {
 403                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->rollupprogresscompletion =
 404                                          $sequencing['attrs']['ROLLUPPROGRESSCOMPLETION'] == 'true' ? 1 : 0;
 405                                  }
 406                                  if (isset($sequencing['attrs']['OBJECTIVEMEASUREWEIGHT'])) {
 407                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->objectivemeasureweight =
 408                                          $sequencing['attrs']['OBJECTIVEMEASUREWEIGHT'];
 409                                  }
 410  
 411                                  if (!empty($sequencing['children'])) {
 412                                      $rolluprules = array();
 413                                      foreach ($sequencing['children'] as $sequencingrolluprule) {
 414                                          if ($sequencingrolluprule['name'] == 'IMSSS:ROLLUPRULE' ) {
 415                                              $rolluprule = new stdClass();
 416                                              $rolluprule->childactivityset = 'all';
 417                                              if (isset($sequencingrolluprule['attrs']['CHILDACTIVITYSET'])) {
 418                                                  $rolluprule->childactivityset = $sequencingrolluprule['attrs']['CHILDACTIVITYSET'];
 419                                              }
 420                                              $rolluprule->minimumcount = 0;
 421                                              if (isset($sequencingrolluprule['attrs']['MINIMUMCOUNT'])) {
 422                                                  $rolluprule->minimumcount = $sequencingrolluprule['attrs']['MINIMUMCOUNT'];
 423                                              }
 424                                              $rolluprule->minimumpercent = 0.0000;
 425                                              if (isset($sequencingrolluprule['attrs']['MINIMUMPERCENT'])) {
 426                                                  $rolluprule->minimumpercent = $sequencingrolluprule['attrs']['MINIMUMPERCENT'];
 427                                              }
 428                                              if (!empty($sequencingrolluprule['children'])) {
 429                                                  foreach ($sequencingrolluprule['children'] as $rolluproleconditions) {
 430                                                      if ($rolluproleconditions['name'] == 'IMSSS:ROLLUPCONDITIONS') {
 431                                                          $conditions = array();
 432                                                          $rolluprule->conditioncombination = 'all';
 433                                                          if (isset($rolluproleconditions['attrs']['CONDITIONCOMBINATION'])) {
 434                                                              $rolluprule->CONDITIONCOMBINATION = $rolluproleconditions['attrs']['CONDITIONCOMBINATION'];
 435                                                          }
 436                                                          foreach ($rolluproleconditions['children'] as $rolluprulecondition) {
 437                                                              if ($rolluprulecondition['name'] == 'IMSSS:ROLLUPCONDITION') {
 438                                                                  $condition = new stdClass();
 439                                                                  if (isset($rolluprulecondition['attrs']['CONDITION'])) {
 440                                                                      $condition->cond = $rolluprulecondition['attrs']['CONDITION'];
 441                                                                  }
 442                                                                  $condition->operator = 'noOp';
 443                                                                  if (isset($rolluprulecondition['attrs']['OPERATOR'])) {
 444                                                                      $condition->operator = $rolluprulecondition['attrs']['OPERATOR'];
 445                                                                  }
 446                                                                  array_push($conditions, $condition);
 447                                                              }
 448                                                          }
 449                                                          $rolluprule->conditions = $conditions;
 450                                                      }
 451                                                      if ($rolluproleconditions['name'] == 'IMSSS:ROLLUPACTION') {
 452                                                          $rolluprule->rollupruleaction = $rolluproleconditions['attrs']['ACTION'];
 453                                                      }
 454                                                  }
 455                                              }
 456                                              array_push($rolluprules, $rolluprule);
 457                                          }
 458                                      }
 459                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->rolluprules = $rolluprules;
 460                                  }
 461                              }
 462  
 463                              if ($sequencing['name'] == 'IMSSS:SEQUENCINGRULES') {
 464                                  if (!empty($sequencing['children'])) {
 465                                      $sequencingrules = array();
 466                                      foreach ($sequencing['children'] as $conditionrules) {
 467                                          $conditiontype = -1;
 468                                          switch($conditionrules['name']) {
 469                                              case 'IMSSS:PRECONDITIONRULE':
 470                                                  $conditiontype = 0;
 471                                              break;
 472                                              case 'IMSSS:POSTCONDITIONRULE':
 473                                                  $conditiontype = 1;
 474                                              break;
 475                                              case 'IMSSS:EXITCONDITIONRULE':
 476                                                  $conditiontype = 2;
 477                                              break;
 478                                          }
 479                                          if (!empty($conditionrules['children'])) {
 480                                              $sequencingrule = new stdClass();
 481                                              foreach ($conditionrules['children'] as $conditionrule) {
 482                                                  if ($conditionrule['name'] == 'IMSSS:RULECONDITIONS') {
 483                                                      $ruleconditions = array();
 484                                                      $sequencingrule->conditioncombination = 'all';
 485                                                      if (isset($conditionrule['attrs']['CONDITIONCOMBINATION'])) {
 486                                                          $sequencingrule->conditioncombination = $conditionrule['attrs']['CONDITIONCOMBINATION'];
 487                                                      }
 488                                                      foreach ($conditionrule['children'] as $rulecondition) {
 489                                                          if ($rulecondition['name'] == 'IMSSS:RULECONDITION') {
 490                                                              $condition = new stdClass();
 491                                                              if (isset($rulecondition['attrs']['CONDITION'])) {
 492                                                                  $condition->cond = $rulecondition['attrs']['CONDITION'];
 493                                                              }
 494                                                              $condition->operator = 'noOp';
 495                                                              if (isset($rulecondition['attrs']['OPERATOR'])) {
 496                                                                  $condition->operator = $rulecondition['attrs']['OPERATOR'];
 497                                                              }
 498                                                              $condition->measurethreshold = 0.0000;
 499                                                              if (isset($rulecondition['attrs']['MEASURETHRESHOLD'])) {
 500                                                                  $condition->measurethreshold = $rulecondition['attrs']['MEASURETHRESHOLD'];
 501                                                              }
 502                                                              $condition->referencedobjective = '';
 503                                                              if (isset($rulecondition['attrs']['REFERENCEDOBJECTIVE'])) {
 504                                                                  $condition->referencedobjective = $rulecondition['attrs']['REFERENCEDOBJECTIVE'];
 505                                                              }
 506                                                              array_push($ruleconditions, $condition);
 507                                                          }
 508                                                      }
 509                                                      $sequencingrule->ruleconditions = $ruleconditions;
 510                                                  }
 511                                                  if ($conditionrule['name'] == 'IMSSS:RULEACTION') {
 512                                                      $sequencingrule->action = $conditionrule['attrs']['ACTION'];
 513                                                  }
 514                                                  $sequencingrule->type = $conditiontype;
 515                                              }
 516                                              array_push($sequencingrules, $sequencingrule);
 517                                          }
 518                                      }
 519                                      $scoes->elements[$manifest][$parent->organization][$parent->identifier]->sequencingrules = $sequencingrules;
 520                                  }
 521                              }
 522                          }
 523                      }
 524                  break;
 525              }
 526          }
 527      }
 528      if (!empty($manifestresourcesnotfound)) {
 529          // Throw warning to user to let them know manifest contains references to resources that don't appear to exist.
 530          if (!defined('DEBUGGING_PRINTED')) {
 531              // Prevent redirect and display warning.
 532              define('DEBUGGING_PRINTED', 1);
 533          }
 534          echo $OUTPUT->notification(get_string('invalidmanifestresource', 'scorm').' '. implode(', ', $manifestresourcesnotfound));
 535      }
 536      return $scoes;
 537  }
 538  
 539  /**
 540   * Sets up SCORM 1.2/2004 packages using the manifest file.
 541   * Called whenever SCORM changes
 542   * @param object $scorm instance - fields are updated and changes saved into database
 543   * @param stored_file|string $manifest - path to manifest file or stored_file.
 544   * @return bool
 545   */
 546  function scorm_parse_scorm(&$scorm, $manifest) {
 547      global $CFG, $DB;
 548  
 549      // Load manifest into string.
 550      if ($manifest instanceof stored_file) {
 551          $xmltext = $manifest->get_content();
 552      } else {
 553          require_once("$CFG->libdir/filelib.php");
 554          $xmltext = download_file_content($manifest);
 555      }
 556  
 557      $defaultorgid = 0;
 558      $firstinorg = 0;
 559  
 560      $pattern = '/&(?!\w{2,6};)/';
 561      $replacement = '&amp;';
 562      $xmltext = preg_replace($pattern, $replacement, $xmltext);
 563  
 564      $objxml = new xml2Array();
 565      $manifests = $objxml->parse($xmltext);
 566      $scoes = new stdClass();
 567      $scoes->version = '';
 568      $scoes = scorm_get_manifest($manifests, $scoes);
 569      $newscoes = array();
 570      $sortorder = 0;
 571      if (count($scoes->elements) > 0) {
 572          $olditems = $DB->get_records('scorm_scoes', array('scorm' => $scorm->id));
 573          foreach ($scoes->elements as $manifest => $organizations) {
 574              foreach ($organizations as $organization => $items) {
 575                  foreach ($items as $identifier => $item) {
 576                      $sortorder++;
 577                      // This new db mngt will support all SCORM future extensions.
 578                      $newitem = new stdClass();
 579                      $newitem->scorm = $scorm->id;
 580                      $newitem->manifest = $manifest;
 581                      $newitem->organization = $organization;
 582                      $newitem->sortorder = $sortorder;
 583                      $standarddatas = array('parent', 'identifier', 'launch', 'scormtype', 'title');
 584                      foreach ($standarddatas as $standarddata) {
 585                          if (isset($item->$standarddata)) {
 586                              $newitem->$standarddata = $item->$standarddata;
 587                          } else {
 588                              $newitem->$standarddata = '';
 589                          }
 590                      }
 591  
 592                      if (!empty($defaultorgid) && !empty($scoes->defaultorg) && empty($firstinorg) &&
 593                          $newitem->parent == $scoes->defaultorg) {
 594  
 595                          $firstinorg = $sortorder;
 596                      }
 597  
 598                      if (!empty($olditems) && ($olditemid = scorm_array_search('identifier', $newitem->identifier, $olditems))) {
 599                          $newitem->id = $olditemid;
 600                          // Update the Sco sortorder but keep id so that user tracks are kept against the same ids.
 601                          $DB->update_record('scorm_scoes', $newitem);
 602                          $id = $olditemid;
 603                          // Remove all old data so we don't duplicate it.
 604                          $DB->delete_records('scorm_scoes_data', array('scoid' => $olditemid));
 605                          $DB->delete_records('scorm_seq_objective', array('scoid' => $olditemid));
 606                          $DB->delete_records('scorm_seq_mapinfo', array('scoid' => $olditemid));
 607                          $DB->delete_records('scorm_seq_ruleconds', array('scoid' => $olditemid));
 608                          $DB->delete_records('scorm_seq_rulecond', array('scoid' => $olditemid));
 609                          $DB->delete_records('scorm_seq_rolluprule', array('scoid' => $olditemid));
 610                          $DB->delete_records('scorm_seq_rolluprulecond', array('scoid' => $olditemid));
 611  
 612                          // Now remove this SCO from the olditems object as we have dealt with it.
 613                          unset($olditems[$olditemid]);
 614                      } else {
 615                          // Insert the new SCO, and retain the link between the old and new for later adjustment.
 616                          $id = $DB->insert_record('scorm_scoes', $newitem);
 617                      }
 618                      // Save this sco in memory so we can use it later.
 619                      $newscoes[$id] = $newitem;
 620  
 621                      if ($optionaldatas = scorm_optionals_data($item, $standarddatas)) {
 622                          $data = new stdClass();
 623                          $data->scoid = $id;
 624                          foreach ($optionaldatas as $optionaldata) {
 625                              if (isset($item->$optionaldata)) {
 626                                  $data->name = $optionaldata;
 627                                  $data->value = $item->$optionaldata;
 628                                  $dataid = $DB->insert_record('scorm_scoes_data', $data);
 629                              }
 630                          }
 631                      }
 632  
 633                      if (isset($item->sequencingrules)) {
 634                          foreach ($item->sequencingrules as $sequencingrule) {
 635                              $rule = new stdClass();
 636                              $rule->scoid = $id;
 637                              $rule->ruletype = $sequencingrule->type;
 638                              $rule->conditioncombination = $sequencingrule->conditioncombination;
 639                              $rule->action = $sequencingrule->action;
 640                              $ruleid = $DB->insert_record('scorm_seq_ruleconds', $rule);
 641                              if (isset($sequencingrule->ruleconditions)) {
 642                                  foreach ($sequencingrule->ruleconditions as $rulecondition) {
 643                                      $rulecond = new stdClass();
 644                                      $rulecond->scoid = $id;
 645                                      $rulecond->ruleconditionsid = $ruleid;
 646                                      $rulecond->referencedobjective = $rulecondition->referencedobjective;
 647                                      $rulecond->measurethreshold = $rulecondition->measurethreshold;
 648                                      $rulecond->operator = $rulecondition->operator;
 649                                      $rulecond->cond = $rulecondition->cond;
 650                                      $rulecondid = $DB->insert_record('scorm_seq_rulecond', $rulecond);
 651                                  }
 652                              }
 653                          }
 654                      }
 655  
 656                      if (isset($item->rolluprules)) {
 657                          foreach ($item->rolluprules as $rolluprule) {
 658                              $rollup = new stdClass();
 659                              $rollup->scoid = $id;
 660                              $rollup->childactivityset = $rolluprule->childactivityset;
 661                              $rollup->minimumcount = $rolluprule->minimumcount;
 662                              $rollup->minimumpercent = $rolluprule->minimumpercent;
 663                              $rollup->rollupruleaction = $rolluprule->rollupruleaction;
 664                              $rollup->conditioncombination = $rolluprule->conditioncombination;
 665  
 666                              $rollupruleid = $DB->insert_record('scorm_seq_rolluprule', $rollup);
 667                              if (isset($rollup->conditions)) {
 668                                  foreach ($rollup->conditions as $condition) {
 669                                      $cond = new stdClass();
 670                                      $cond->scoid = $rollup->scoid;
 671                                      $cond->rollupruleid = $rollupruleid;
 672                                      $cond->operator = $condition->operator;
 673                                      $cond->cond = $condition->cond;
 674                                      $conditionid = $DB->insert_record('scorm_seq_rolluprulecond', $cond);
 675                                  }
 676                              }
 677                          }
 678                      }
 679  
 680                      if (isset($item->objectives)) {
 681                          foreach ($item->objectives as $objective) {
 682                              $obj = new stdClass();
 683                              $obj->scoid = $id;
 684                              $obj->primaryobj = $objective->primaryobj;
 685                              $obj->satisfiedbumeasure = $objective->satisfiedbymeasure;
 686                              $obj->objectiveid = $objective->objectiveid;
 687                              $obj->minnormalizedmeasure = trim($objective->minnormalizedmeasure);
 688                              $objectiveid = $DB->insert_record('scorm_seq_objective', $obj);
 689                              if (isset($objective->mapinfos)) {
 690                                  foreach ($objective->mapinfos as $objmapinfo) {
 691                                      $mapinfo = new stdClass();
 692                                      $mapinfo->scoid = $id;
 693                                      $mapinfo->objectiveid = $objectiveid;
 694                                      $mapinfo->targetobjectiveid = $objmapinfo->targetobjectiveid;
 695                                      $mapinfo->readsatisfiedstatus = $objmapinfo->readsatisfiedstatus;
 696                                      $mapinfo->writesatisfiedstatus = $objmapinfo->writesatisfiedstatus;
 697                                      $mapinfo->readnormalizedmeasure = $objmapinfo->readnormalizedmeasure;
 698                                      $mapinfo->writenormalizedmeasure = $objmapinfo->writenormalizedmeasure;
 699                                      $mapinfoid = $DB->insert_record('scorm_seq_mapinfo', $mapinfo);
 700                                  }
 701                              }
 702                          }
 703                      }
 704                      if (empty($defaultorgid) && ((empty($scoes->defaultorg)) || ($scoes->defaultorg == $identifier))) {
 705                          $defaultorgid = $id;
 706                      }
 707                  }
 708              }
 709          }
 710          if (!empty($olditems)) {
 711              foreach ($olditems as $olditem) {
 712                  $DB->delete_records('scorm_scoes', array('id' => $olditem->id));
 713                  $DB->delete_records('scorm_scoes_data', array('scoid' => $olditem->id));
 714                  $DB->delete_records('scorm_scoes_track', array('scoid' => $olditem->id));
 715                  $DB->delete_records('scorm_seq_objective', array('scoid' => $olditem->id));
 716                  $DB->delete_records('scorm_seq_mapinfo', array('scoid' => $olditem->id));
 717                  $DB->delete_records('scorm_seq_ruleconds', array('scoid' => $olditem->id));
 718                  $DB->delete_records('scorm_seq_rulecond', array('scoid' => $olditem->id));
 719                  $DB->delete_records('scorm_seq_rolluprule', array('scoid' => $olditem->id));
 720                  $DB->delete_records('scorm_seq_rolluprulecond', array('scoid' => $olditem->id));
 721              }
 722          }
 723          if (empty($scoes->version)) {
 724              $scoes->version = 'SCORM_1.2';
 725          }
 726          $DB->set_field('scorm', 'version', $scoes->version, array('id' => $scorm->id));
 727          $scorm->version = $scoes->version;
 728      }
 729      $scorm->launch = 0;
 730      // Check launch sco is valid.
 731      if (!empty($defaultorgid) && isset($newscoes[$defaultorgid]) && !empty($newscoes[$defaultorgid]->launch)) {
 732          // Launch param is valid - do nothing.
 733          $scorm->launch = $defaultorgid;
 734      } else if (!empty($defaultorgid) && isset($newscoes[$defaultorgid]) && empty($newscoes[$defaultorgid]->launch)) {
 735          // The launch is probably the default org so we need to find the first launchable item inside this org.
 736          $sqlselect = 'scorm = ? AND sortorder >= ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true);
 737          // We use get_records here as we need to pass a limit in the query that works cross db.
 738          $scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scorm->id, $firstinorg), 'sortorder', 'id', 0, 1);
 739          if (!empty($scoes)) {
 740              $sco = reset($scoes); // We only care about the first record - the above query only returns one.
 741              $scorm->launch = $sco->id;
 742          }
 743      }
 744      if (empty($scorm->launch)) {
 745          // No valid Launch is specified - find the first launchable sco instead.
 746          $sqlselect = 'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true);
 747          // We use get_records here as we need to pass a limit in the query that works cross db.
 748          $scoes = $DB->get_records_select('scorm_scoes', $sqlselect, array($scorm->id), 'sortorder', 'id', 0, 1);
 749          if (!empty($scoes)) {
 750              $sco = reset($scoes); // We only care about the first record - the above query only returns one.
 751              $scorm->launch = $sco->id;
 752          }
 753      }
 754  
 755      return true;
 756  }
 757  
 758  function scorm_optionals_data($item, $standarddata) {
 759      $result = array();
 760      $sequencingdata = array('sequencingrules', 'rolluprules', 'objectives');
 761      foreach ($item as $element => $value) {
 762          if (! in_array($element, $standarddata)) {
 763              if (! in_array($element, $sequencingdata)) {
 764                  $result[] = $element;
 765              }
 766          }
 767      }
 768      return $result;
 769  }
 770  
 771  function scorm_is_leaf($sco) {
 772      global $DB;
 773  
 774      if ($DB->record_exists('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->identifier))) {
 775          return false;
 776      }
 777      return true;
 778  }
 779  
 780  function scorm_get_parent($sco) {
 781      global $DB;
 782  
 783      if ($sco->parent != '/') {
 784          if ($parent = $DB->get_record('scorm_scoes', array('scorm' => $sco->scorm, 'identifier' => $sco->parent))) {
 785              return scorm_get_sco($parent->id);
 786          }
 787      }
 788      return null;
 789  }
 790  
 791  function scorm_get_children($sco) {
 792      global $DB;
 793  
 794      $children = $DB->get_records('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->identifier), 'sortorder, id');
 795      if (!empty($children)) {
 796          return $children;
 797      }
 798      return null;
 799  }
 800  
 801  function scorm_get_available_children($sco) {
 802      global $DB;
 803  
 804      $res = $DB->get_records('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->identifier), 'sortorder, id');
 805      if (!$res || $res == null) {
 806          return false;
 807      } else {
 808          foreach ($res as $sco) {
 809              $result[] = $sco;
 810          }
 811          return $result;
 812      }
 813  }
 814  
 815  function scorm_get_available_descendent($descend, $sco) {
 816      if ($sco == null) {
 817          return $descend;
 818      } else {
 819          $avchildren = scorm_get_available_children($sco);
 820          foreach ($avchildren as $avchild) {
 821              array_push($descend, $avchild);
 822          }
 823          foreach ($avchildren as $avchild) {
 824              scorm_get_available_descendent($descend, $avchild);
 825          }
 826      }
 827  }
 828  
 829  function scorm_get_siblings($sco) {
 830      global $DB;
 831  
 832      if ($siblings = $DB->get_records('scorm_scoes', array('scorm' => $sco->scorm, 'parent' => $sco->parent), 'sortorder, id')) {
 833          unset($siblings[$sco->id]);
 834          if (!empty($siblings)) {
 835              return $siblings;
 836          }
 837      }
 838      return null;
 839  }
 840  // Get an array that contains all the parent scos for this sco.
 841  function scorm_get_ancestors($sco) {
 842      $ancestors = array();
 843      $continue = true;
 844      while ($continue) {
 845          $ancestor = scorm_get_parent($sco);
 846          if (!empty($ancestor) && $ancestor->id !== $sco->id) {
 847              $sco = $ancestor;
 848              $ancestors[] = $ancestor;
 849              if ($sco->parent == '/') {
 850                  $continue = false;
 851              }
 852          } else {
 853              $continue = false;
 854          }
 855      }
 856      return $ancestors;
 857  }
 858  
 859  function scorm_get_preorder(&$preorder = array(), $sco = null) {
 860      if ($sco != null) {
 861          array_push($preorder, $sco);
 862          if ($children = scorm_get_children($sco)) {
 863              foreach ($children as $child) {
 864                  scorm_get_preorder($preorder, $child);
 865              }
 866          }
 867      }
 868      return $preorder;
 869  }
 870  
 871  function scorm_find_common_ancestor($ancestors, $sco) {
 872      $pos = scorm_array_search('identifier', $sco->parent, $ancestors);
 873      if ($sco->parent != '/') {
 874          if ($pos === false) {
 875              return scorm_find_common_ancestor($ancestors, scorm_get_parent($sco));
 876          }
 877      }
 878      return $pos;
 879  }
 880  
 881  /* Usage
 882   Grab some XML data, either from a file, URL, etc. however you want. Assume storage in $strYourXML;
 883  
 884   $objXML = new xml2Array();
 885   $arroutput = $objXML->parse($strYourXML);
 886   print_r($arroutput); //print it out, or do whatever!
 887  
 888  */
 889  class xml2Array {
 890  
 891      public $arroutput = array();
 892      public $resparser;
 893      public $strxmldata;
 894  
 895      /**
 896       * Convert a utf-8 string to html entities
 897       *
 898       * @param string $str The UTF-8 string
 899       * @return string
 900       */
 901      public function utf8_to_entities($str) {
 902          global $CFG;
 903  
 904          $entities = '';
 905          $values = array();
 906          $lookingfor = 1;
 907  
 908          return $str;
 909      }
 910  
 911      /**
 912       * Parse an XML text string and create an array tree that rapresent the XML structure
 913       *
 914       * @param string $strinputxml The XML string
 915       * @return array
 916       */
 917      public function parse($strinputxml) {
 918          $this->resparser = xml_parser_create ('UTF-8');
 919          xml_set_object($this->resparser, $this);
 920          xml_set_element_handler($this->resparser, "tagopen", "tagclosed");
 921  
 922          xml_set_character_data_handler($this->resparser, "tagdata");
 923  
 924          $this->strxmldata = xml_parse($this->resparser, $strinputxml );
 925          if (!$this->strxmldata) {
 926              die(sprintf("XML error: %s at line %d",
 927              xml_error_string(xml_get_error_code($this->resparser)),
 928              xml_get_current_line_number($this->resparser)));
 929          }
 930  
 931          xml_parser_free($this->resparser);
 932  
 933          return $this->arroutput;
 934      }
 935  
 936      public function tagopen($parser, $name, $attrs) {
 937          $tag = array("name" => $name, "attrs" => $attrs);
 938          array_push($this->arroutput, $tag);
 939      }
 940  
 941      public function tagdata($parser, $tagdata) {
 942          if (trim($tagdata)) {
 943              if (isset($this->arroutput[count($this->arroutput) - 1]['tagData'])) {
 944                  $this->arroutput[count($this->arroutput) - 1]['tagData'] .= $this->utf8_to_entities($tagdata);
 945              } else {
 946                  $this->arroutput[count($this->arroutput) - 1]['tagData'] = $this->utf8_to_entities($tagdata);
 947              }
 948          }
 949      }
 950  
 951      public function tagclosed($parser, $name) {
 952          $this->arroutput[count($this->arroutput) - 2]['children'][] = $this->arroutput[count($this->arroutput) - 1];
 953          array_pop($this->arroutput);
 954      }
 955  
 956  }