Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Generator for the core_h5p subsystem.
  19   *
  20   * @package    core_h5p
  21   * @category   test
  22   * @copyright  2019 Victor Deniz <victor@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  use core_h5p\local\library\autoloader;
  27  use core_h5p\core;
  28  use core_h5p\player;
  29  use core_h5p\factory;
  30  
  31  defined('MOODLE_INTERNAL') || die();
  32  
  33  /**
  34   * Generator for the core_h5p subsystem.
  35   *
  36   * @package    core_h5p
  37   * @category   test
  38   * @copyright  2019 Victor Deniz <victor@moodle.com>
  39   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   */
  41  class core_h5p_generator extends \component_generator_base {
  42  
  43      /** Url pointing to webservice plugin file. */
  44      public const WSPLUGINFILE = 0;
  45      /** Url pointing to token plugin file. */
  46      public const TOKENPLUGINFILE = 1;
  47      /** Url pointing to plugin file. */
  48      public const PLUGINFILE = 2;
  49  
  50      /**
  51       * Convenience function to create a file.
  52       *
  53       * @param  string $file path to a file.
  54       * @param  string $content file content.
  55       */
  56      public function create_file(string $file, string $content=''): void {
  57          $handle = fopen($file, 'w+');
  58          // File content is not relevant.
  59          if (empty($content)) {
  60              $content = hash("md5", $file);
  61          }
  62          fwrite($handle, $content);
  63          fclose($handle);
  64      }
  65  
  66      /**
  67       * Creates the file record. Currently used for the cache tests.
  68       *
  69       * @param string $type    Either 'scripts' or 'styles'.
  70       * @param string $path    Path to the file in the file system.
  71       * @param string $version Not really needed at the moment.
  72       */
  73      protected function add_libfile_to_array(string $type, string $path, string $version, &$files): void {
  74          $files[$type][] = (object)[
  75              'path' => $path,
  76              'version' => "?ver=$version"
  77          ];
  78      }
  79  
  80      /**
  81       * Create the necessary files and return an array structure for a library.
  82       *
  83       * @param  string $uploaddirectory Base directory for the library.
  84       * @param  int    $libraryid       Library id.
  85       * @param  string $machinename     Name for this library.
  86       * @param  int    $majorversion    Major version (any number will do).
  87       * @param  int    $minorversion    Minor version (any number will do).
  88       * @param  array  $langs           Languages to be included into the library.
  89       * @return array A list of library data and files that the core API will understand.
  90       */
  91      public function create_library(string $uploaddirectory, int $libraryid, string $machinename, int $majorversion,
  92              int $minorversion, ?array $langs = []): array {
  93          // Array $files used in the cache tests.
  94          $files = ['scripts' => [], 'styles' => [], 'language' => []];
  95  
  96          check_dir_exists($uploaddirectory . '/' . 'scripts');
  97          check_dir_exists($uploaddirectory . '/' . 'styles');
  98          if (!empty($langs)) {
  99              check_dir_exists($uploaddirectory . '/' . 'language');
 100          }
 101  
 102          $jsonfile = $uploaddirectory . '/' . 'library.json';
 103          $jsfile = $uploaddirectory . '/' . 'scripts/testlib.min.js';
 104          $cssfile = $uploaddirectory . '/' . 'styles/testlib.min.css';
 105          $this->create_file($jsonfile);
 106          $this->create_file($jsfile);
 107          $this->create_file($cssfile);
 108          foreach ($langs as $lang => $value) {
 109              $jsonfile = $uploaddirectory . '/' . 'language/' . $lang . '.json';
 110              $this->create_file($jsonfile, $value);
 111          }
 112  
 113          $lib = [
 114              'title' => 'Test lib',
 115              'description' => 'Test library description',
 116              'majorVersion' => $majorversion,
 117              'minorVersion' => $minorversion,
 118              'patchVersion' => 2,
 119              'machineName' => $machinename,
 120              'preloadedJs' => [
 121                  [
 122                      'path' => 'scripts' . '/' . 'testlib.min.js'
 123                  ]
 124              ],
 125              'preloadedCss' => [
 126                  [
 127                      'path' => 'styles' . '/' . 'testlib.min.css'
 128                  ]
 129              ],
 130              'uploadDirectory' => $uploaddirectory,
 131              'libraryId' => $libraryid
 132          ];
 133  
 134          $version = "{$majorversion}.{$minorversion}.2";
 135          $libname = "{$machinename}-{$majorversion}.{$minorversion}";
 136          $path = '/' . 'libraries' . '/' . $libraryid . '/' . $libname . '/' . 'scripts' . '/' . 'testlib.min.js';
 137          $this->add_libfile_to_array('scripts', $path, $version, $files);
 138          $path = '/' . 'libraries' . '/' . $libraryid .'/' . $libname . '/' . 'styles' . '/' . 'testlib.min.css';
 139          $this->add_libfile_to_array('styles', $path, $version, $files);
 140          foreach ($langs as $lang => $notused) {
 141              $path = '/' . 'libraries' . '/' . $libraryid . '/' . $libname . '/' . 'language' . '/' . $lang . '.json';
 142              $this->add_libfile_to_array('language', $path, $version, $files);
 143          }
 144  
 145          return [$lib, $files];
 146      }
 147  
 148      /**
 149       * Save the library files on the filesystem.
 150       *
 151       * @param stdClss $lib The library data
 152       */
 153      private function save_library(stdClass $lib) {
 154          // Get a temp path.
 155          $filestorage = new \core_h5p\file_storage();
 156          $temppath = $filestorage->getTmpPath();
 157  
 158          // Create and save the library files on the filesystem.
 159          $basedirectorymain = $temppath . '/' . $lib->machinename . '-' .
 160              $lib->majorversion . '.' . $lib->minorversion;
 161  
 162          list($library, $libraryfiles) = $this->create_library($basedirectorymain, $lib->id, $lib->machinename,
 163              $lib->majorversion, $lib->minorversion);
 164  
 165          $filestorage->saveLibrary($library);
 166      }
 167  
 168      /**
 169       * Populate H5P database tables with relevant data to simulate the process of adding H5P content.
 170       *
 171       * @param bool $createlibraryfiles Whether to create and store library files on the filesystem
 172       * @return stdClass An object representing the added H5P records
 173       */
 174      public function generate_h5p_data(bool $createlibraryfiles = false): stdClass {
 175          // Create libraries.
 176          $mainlib = $libraries[] = $this->create_library_record('MainLibrary', 'Main Lib', 1, 0);
 177          $lib1 = $libraries[] = $this->create_library_record('Library1', 'Lib1', 2, 0);
 178          $lib2 = $libraries[] = $this->create_library_record('Library2', 'Lib2', 2, 1);
 179          $lib3 = $libraries[] = $this->create_library_record('Library3', 'Lib3', 3, 2);
 180          $lib4 = $libraries[] = $this->create_library_record('Library4', 'Lib4', 1, 1);
 181          $lib5 = $libraries[] = $this->create_library_record('Library5', 'Lib5', 1, 3);
 182  
 183          if ($createlibraryfiles) {
 184              foreach ($libraries as $lib) {
 185                  // Create and save the library files on the filesystem.
 186                  $this->save_library($lib);
 187              }
 188          }
 189  
 190          // Create h5p content.
 191          $h5p = $this->create_h5p_record($mainlib->id);
 192          // Create h5p content library dependencies.
 193          $this->create_contents_libraries_record($h5p, $mainlib->id);
 194          $this->create_contents_libraries_record($h5p, $lib1->id);
 195          $this->create_contents_libraries_record($h5p, $lib2->id);
 196          $this->create_contents_libraries_record($h5p, $lib3->id);
 197          $this->create_contents_libraries_record($h5p, $lib4->id);
 198          // Create library dependencies for $mainlib.
 199          $this->create_library_dependency_record($mainlib->id, $lib1->id);
 200          $this->create_library_dependency_record($mainlib->id, $lib2->id);
 201          $this->create_library_dependency_record($mainlib->id, $lib3->id);
 202          // Create library dependencies for $lib1.
 203          $this->create_library_dependency_record($lib1->id, $lib2->id);
 204          $this->create_library_dependency_record($lib1->id, $lib3->id);
 205          $this->create_library_dependency_record($lib1->id, $lib4->id);
 206          // Create library dependencies for $lib3.
 207          $this->create_library_dependency_record($lib3->id, $lib5->id);
 208  
 209          return (object) [
 210              'h5pcontent' => (object) array(
 211                  'h5pid' => $h5p,
 212                  'contentdependencies' => array($mainlib, $lib1, $lib2, $lib3, $lib4)
 213              ),
 214              'mainlib' => (object) array(
 215                  'data' => $mainlib,
 216                  'dependencies' => array($lib1, $lib2, $lib3)
 217              ),
 218              'lib1' => (object) array(
 219                  'data' => $lib1,
 220                  'dependencies' => array($lib2, $lib3, $lib4)
 221              ),
 222              'lib2' => (object) array(
 223                  'data' => $lib2,
 224                  'dependencies' => array()
 225              ),
 226              'lib3' => (object) array(
 227                  'data' => $lib3,
 228                  'dependencies' => array($lib5)
 229              ),
 230              'lib4' => (object) array(
 231                  'data' => $lib4,
 232                  'dependencies' => array()
 233              ),
 234              'lib5' => (object) array(
 235                  'data' => $lib5,
 236                  'dependencies' => array()
 237              ),
 238          ];
 239      }
 240  
 241      /**
 242       * Create a record in the h5p_libraries database table.
 243       *
 244       * @param string $machinename The library machine name
 245       * @param string $title The library's name
 246       * @param int $majorversion The library's major version
 247       * @param int $minorversion The library's minor version
 248       * @param int $patchversion The library's patch version
 249       * @param string $semantics Json describing the content structure for the library
 250       * @param string $addto The plugin configuration data
 251       * @return stdClass An object representing the added library record
 252       */
 253      public function create_library_record(string $machinename, string $title, int $majorversion = 1,
 254              int $minorversion = 0, int $patchversion = 1, string $semantics = '', string $addto = null): stdClass {
 255          global $DB;
 256  
 257          $content = array(
 258              'machinename' => $machinename,
 259              'title' => $title,
 260              'majorversion' => $majorversion,
 261              'minorversion' => $minorversion,
 262              'patchversion' => $patchversion,
 263              'runnable' => 1,
 264              'fullscreen' => 1,
 265              'preloadedjs' => 'js/example.js',
 266              'preloadedcss' => 'css/example.css',
 267              'droplibrarycss' => '',
 268              'semantics' => $semantics,
 269              'addto' => $addto
 270          );
 271  
 272          $libraryid = $DB->insert_record('h5p_libraries', $content);
 273  
 274          return $DB->get_record('h5p_libraries', ['id' => $libraryid]);
 275      }
 276  
 277      /**
 278       * Create a record in the h5p database table.
 279       *
 280       * @param int $mainlibid The ID of the content's main library
 281       * @param string $jsoncontent The content in json format
 282       * @param string $filtered The filtered content parameters
 283       * @return int The ID of the added record
 284       */
 285      public function create_h5p_record(int $mainlibid, string $jsoncontent = null, string $filtered = null): int {
 286          global $DB;
 287  
 288          if (!$jsoncontent) {
 289              $jsoncontent = json_encode(
 290                  array(
 291                      'text' => '<p>Dummy text<\/p>\n',
 292                      'questions' => '<p>Test question<\/p>\n'
 293                  )
 294              );
 295          }
 296  
 297          if (!$filtered) {
 298              $filtered = json_encode(
 299                  array(
 300                      'text' => 'Dummy text',
 301                      'questions' => 'Test question'
 302                  )
 303              );
 304          }
 305  
 306          return $DB->insert_record(
 307              'h5p',
 308              array(
 309                  'jsoncontent' => $jsoncontent,
 310                  'displayoptions' => 8,
 311                  'mainlibraryid' => $mainlibid,
 312                  'timecreated' => time(),
 313                  'timemodified' => time(),
 314                  'filtered' => $filtered,
 315                  'pathnamehash' => sha1('pathname'),
 316                  'contenthash' => sha1('content')
 317              )
 318          );
 319      }
 320  
 321      /**
 322       * Create a record in the h5p_contents_libraries database table.
 323       *
 324       * @param string $h5pid The ID of the H5P content
 325       * @param int $libid The ID of the library
 326       * @param string $dependencytype The dependency type
 327       * @return int The ID of the added record
 328       */
 329      public function create_contents_libraries_record(string $h5pid, int $libid,
 330              string $dependencytype = 'preloaded'): int {
 331          global $DB;
 332  
 333          return $DB->insert_record(
 334              'h5p_contents_libraries',
 335              array(
 336                  'h5pid' => $h5pid,
 337                  'libraryid' => $libid,
 338                  'dependencytype' => $dependencytype,
 339                  'dropcss' => 0,
 340                  'weight' => 1
 341              )
 342          );
 343      }
 344  
 345      /**
 346       * Create a record in the h5p_library_dependencies database table.
 347       *
 348       * @param int $libid The ID of the library
 349       * @param int $requiredlibid The ID of the required library
 350       * @param string $dependencytype The dependency type
 351       * @return int The ID of the added record
 352       */
 353      public function create_library_dependency_record(int $libid, int $requiredlibid,
 354              string $dependencytype = 'preloaded'): int {
 355          global $DB;
 356  
 357          return $DB->insert_record(
 358              'h5p_library_dependencies',
 359              array(
 360                  'libraryid' => $libid,
 361                  'requiredlibraryid' => $requiredlibid,
 362                  'dependencytype' => $dependencytype
 363              )
 364          );
 365      }
 366  
 367      /**
 368       * Create H5P content type records in the h5p_libraries database table.
 369       *
 370       * @param array $typestonotinstall H5P content types that should not be installed
 371       * @param core $core h5p_test_core instance required to use the exttests URL
 372       * @return array Data of the content types not installed.
 373       */
 374      public function create_content_types(array $typestonotinstall, core $core): array {
 375          global $DB;
 376  
 377          autoloader::register();
 378  
 379          // Get info of latest content types versions.
 380          $contenttypes = $core->get_latest_content_types()->contentTypes;
 381  
 382          $installedtypes = 0;
 383  
 384          // Fake installation of all other H5P content types.
 385          foreach ($contenttypes as $contenttype) {
 386              // Don't install pending content types.
 387              if (in_array($contenttype->id, $typestonotinstall)) {
 388                  continue;
 389              }
 390              $library = [
 391                  'machinename' => $contenttype->id,
 392                  'majorversion' => $contenttype->version->major,
 393                  'minorversion' => $contenttype->version->minor,
 394                  'patchversion' => $contenttype->version->patch,
 395                  'runnable' => 1,
 396                  'coremajor' => $contenttype->coreApiVersionNeeded->major,
 397                  'coreminor' => $contenttype->coreApiVersionNeeded->minor
 398              ];
 399              $DB->insert_record('h5p_libraries', (object) $library);
 400              $installedtypes++;
 401          }
 402  
 403          return [$installedtypes, count($typestonotinstall)];
 404      }
 405  
 406      /**
 407       * Add a record on files table for a file that belongs to
 408       *
 409       * @param string $file File name and path inside the filearea.
 410       * @param string $filearea The filearea in which the file is ("editor" or "content").
 411       * @param int $contentid Id of the H5P content to which the file belongs. null if the file is in the editor.
 412       *
 413       * @return stored_file;
 414       * @throws coding_exception
 415       */
 416      public function create_content_file(string $file, string $filearea, int $contentid = 0): stored_file {
 417          global $USER;
 418  
 419          $filepath = '/'.dirname($file).'/';
 420          $filename = basename($file);
 421  
 422          if (($filearea === 'content') && ($contentid == 0)) {
 423              throw new coding_exception('Files belonging to an H5P content must specify the H5P content id');
 424          }
 425  
 426          if ($filearea === 'draft') {
 427              $usercontext = \context_user::instance($USER->id);
 428              $context = $usercontext->id;
 429              $component = 'user';
 430              $itemid = 0;
 431          } else {
 432              $systemcontext = context_system::instance();
 433              $context = $systemcontext->id;
 434              $component = \core_h5p\file_storage::COMPONENT;
 435              $itemid = $contentid;
 436          }
 437  
 438          $content = 'fake content';
 439  
 440          $filerecord = array(
 441              'contextid' => $context,
 442              'component' => $component,
 443              'filearea'  => $filearea,
 444              'itemid'    => $itemid,
 445              'filepath'  => $filepath,
 446              'filename'  => $filename,
 447          );
 448  
 449          $fs = new file_storage();
 450          return $fs->create_file_from_string($filerecord, $content);
 451      }
 452  
 453      /**
 454       * Create a fake export H5P deployed file.
 455       *
 456       * @param string $filename Name of the H5P file to deploy.
 457       * @param int $contextid Context id of the H5P activity.
 458       * @param string $component component.
 459       * @param string $filearea file area.
 460       * @param int $typeurl Type of url to create the export url plugin file.
 461       * @return array return deployed file information.
 462       */
 463      public function create_export_file(string $filename, int $contextid,
 464          string $component,
 465          string $filearea,
 466          int $typeurl = self::WSPLUGINFILE): array {
 467          global $CFG;
 468  
 469          // We need the autoloader for H5P player.
 470          autoloader::register();
 471  
 472          $path = $CFG->dirroot.'/h5p/tests/fixtures/'.$filename;
 473          $filerecord = [
 474              'contextid' => $contextid,
 475              'component' => $component,
 476              'filearea'  => $filearea,
 477              'itemid'    => 0,
 478              'filepath'  => '/',
 479              'filename'  => $filename,
 480          ];
 481          // Load the h5p file into DB.
 482          $fs = get_file_storage();
 483          if (!$fs->get_file($contextid, $component, $filearea, $filerecord['itemid'], $filerecord['filepath'], $filename)) {
 484              $fs->create_file_from_pathname($filerecord, $path);
 485          }
 486  
 487          // Make the URL to pass to the player.
 488          if ($typeurl == self::WSPLUGINFILE) {
 489              $url = \moodle_url::make_webservice_pluginfile_url(
 490                  $filerecord['contextid'],
 491                  $filerecord['component'],
 492                  $filerecord['filearea'],
 493                  $filerecord['itemid'],
 494                  $filerecord['filepath'],
 495                  $filerecord['filename']
 496              );
 497          } else {
 498              $includetoken = false;
 499              if ($typeurl == self::TOKENPLUGINFILE) {
 500                  $includetoken = true;
 501              }
 502              $url = \moodle_url::make_pluginfile_url(
 503                  $filerecord['contextid'],
 504                  $filerecord['component'],
 505                  $filerecord['filearea'],
 506                  $filerecord['itemid'],
 507                  $filerecord['filepath'],
 508                  $filerecord['filename'],
 509                  false,
 510                  $includetoken
 511              );
 512          }
 513  
 514          $config = new stdClass();
 515          $h5pplayer = new player($url->out(false), $config);
 516          // We need to add assets to page to create the export file.
 517          $h5pplayer->add_assets_to_page();
 518  
 519          // Call the method. We need the id of the new H5P content.
 520          $rc = new \ReflectionClass(player::class);
 521          $rcp = $rc->getProperty('h5pid');
 522          $rcp->setAccessible(true);
 523          $h5pid = $rcp->getValue($h5pplayer);
 524  
 525          // Get the info export file.
 526          $factory = new factory();
 527          $core = $factory->get_core();
 528          $content = $core->loadContent($h5pid);
 529          $slug = $content['slug'] ? $content['slug'] . '-' : '';
 530          $exportfilename = "{$slug}{$h5pid}.h5p";
 531          $fileh5p = $core->fs->get_export_file($exportfilename);
 532          $deployedfile = [];
 533          $deployedfile['filename'] = $fileh5p->get_filename();
 534          $deployedfile['filepath'] = $fileh5p->get_filepath();
 535          $deployedfile['mimetype'] = $fileh5p->get_mimetype();
 536          $deployedfile['filesize'] = $fileh5p->get_filesize();
 537          $deployedfile['timemodified'] = $fileh5p->get_timemodified();
 538  
 539          // Create the url depending the request was made through typeurl.
 540          if ($typeurl == self::WSPLUGINFILE) {
 541              $url  = \moodle_url::make_webservice_pluginfile_url(
 542                  $fileh5p->get_contextid(),
 543                  $fileh5p->get_component(),
 544                  $fileh5p->get_filearea(),
 545                  '',
 546                  '',
 547                  $fileh5p->get_filename()
 548              );
 549          } else {
 550              $includetoken = false;
 551              if ($typeurl == self::TOKENPLUGINFILE) {
 552                  $includetoken = true;
 553              }
 554              $url = \moodle_url::make_pluginfile_url(
 555                  $fileh5p->get_contextid(),
 556                  $fileh5p->get_component(),
 557                  $fileh5p->get_filearea(),
 558                  '',
 559                  '',
 560                  $fileh5p->get_filename(),
 561                  false,
 562                  $includetoken
 563              );
 564          }
 565          $deployedfile['fileurl'] = $url->out(false);
 566  
 567          return $deployedfile;
 568      }
 569  }