Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 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, 1, '', null,
 177              'http://tutorial.org', 'http://example.org');
 178          $lib1 = $libraries[] = $this->create_library_record('Library1', 'Lib1', 2, 0, 1, '', null, null,  'http://example.org');
 179          $lib2 = $libraries[] = $this->create_library_record('Library2', 'Lib2', 2, 1, 1, '', null, 'http://tutorial.org');
 180          $lib3 = $libraries[] = $this->create_library_record('Library3', 'Lib3', 3, 2, 1, '', null, null, null, true, 0);
 181          $lib4 = $libraries[] = $this->create_library_record('Library4', 'Lib4', 1, 1);
 182          $lib5 = $libraries[] = $this->create_library_record('Library5', 'Lib5', 1, 3);
 183  
 184          if ($createlibraryfiles) {
 185              foreach ($libraries as $lib) {
 186                  // Create and save the library files on the filesystem.
 187                  $this->save_library($lib);
 188              }
 189          }
 190  
 191          // Create h5p content.
 192          $h5p = $this->create_h5p_record($mainlib->id);
 193          // Create h5p content library dependencies.
 194          $this->create_contents_libraries_record($h5p, $mainlib->id);
 195          $this->create_contents_libraries_record($h5p, $lib1->id);
 196          $this->create_contents_libraries_record($h5p, $lib2->id);
 197          $this->create_contents_libraries_record($h5p, $lib3->id);
 198          $this->create_contents_libraries_record($h5p, $lib4->id);
 199          // Create library dependencies for $mainlib.
 200          $this->create_library_dependency_record($mainlib->id, $lib1->id);
 201          $this->create_library_dependency_record($mainlib->id, $lib2->id);
 202          $this->create_library_dependency_record($mainlib->id, $lib3->id);
 203          // Create library dependencies for $lib1.
 204          $this->create_library_dependency_record($lib1->id, $lib2->id);
 205          $this->create_library_dependency_record($lib1->id, $lib3->id);
 206          $this->create_library_dependency_record($lib1->id, $lib4->id);
 207          // Create library dependencies for $lib3.
 208          $this->create_library_dependency_record($lib3->id, $lib5->id);
 209  
 210          return (object) [
 211              'h5pcontent' => (object) array(
 212                  'h5pid' => $h5p,
 213                  'contentdependencies' => array($mainlib, $lib1, $lib2, $lib3, $lib4)
 214              ),
 215              'mainlib' => (object) array(
 216                  'data' => $mainlib,
 217                  'dependencies' => array($lib1, $lib2, $lib3)
 218              ),
 219              'lib1' => (object) array(
 220                  'data' => $lib1,
 221                  'dependencies' => array($lib2, $lib3, $lib4)
 222              ),
 223              'lib2' => (object) array(
 224                  'data' => $lib2,
 225                  'dependencies' => array()
 226              ),
 227              'lib3' => (object) array(
 228                  'data' => $lib3,
 229                  'dependencies' => array($lib5)
 230              ),
 231              'lib4' => (object) array(
 232                  'data' => $lib4,
 233                  'dependencies' => array()
 234              ),
 235              'lib5' => (object) array(
 236                  'data' => $lib5,
 237                  'dependencies' => array()
 238              ),
 239          ];
 240      }
 241  
 242      /**
 243       * Create a record in the h5p_libraries database table.
 244       *
 245       * @param string $machinename The library machine name
 246       * @param string $title The library's name
 247       * @param int $majorversion The library's major version
 248       * @param int $minorversion The library's minor version
 249       * @param int $patchversion The library's patch version
 250       * @param string $semantics Json describing the content structure for the library
 251       * @param string $addto The plugin configuration data
 252       * @param string $tutorial The tutorial URL
 253       * @param string $examlpe The example URL
 254       * @param bool $enabled Whether the library is enabled or not
 255       * @param int $runnable Whether the library is runnable (1) or not (0)
 256       * @return stdClass An object representing the added library record
 257       */
 258      public function create_library_record(string $machinename, string $title, int $majorversion = 1,
 259              int $minorversion = 0, int $patchversion = 1, string $semantics = '', string $addto = null,
 260              string $tutorial = null, string $example = null, bool $enabled = true, int $runnable = 1): stdClass {
 261          global $DB;
 262  
 263          $content = [
 264              'machinename' => $machinename,
 265              'title' => $title,
 266              'majorversion' => $majorversion,
 267              'minorversion' => $minorversion,
 268              'patchversion' => $patchversion,
 269              'runnable' => $runnable,
 270              'fullscreen' => 1,
 271              'preloadedjs' => 'js/example.js',
 272              'preloadedcss' => 'css/example.css',
 273              'droplibrarycss' => '',
 274              'semantics' => $semantics,
 275              'addto' => $addto,
 276              'tutorial' => $tutorial,
 277              'example' => $example,
 278              'enabled' => $enabled,
 279          ];
 280  
 281          $libraryid = $DB->insert_record('h5p_libraries', $content);
 282  
 283          return $DB->get_record('h5p_libraries', ['id' => $libraryid]);
 284      }
 285  
 286      /**
 287       * Create a record in the h5p database table.
 288       *
 289       * @param int $mainlibid The ID of the content's main library
 290       * @param string $jsoncontent The content in json format
 291       * @param string $filtered The filtered content parameters
 292       * @return int The ID of the added record
 293       */
 294      public function create_h5p_record(int $mainlibid, string $jsoncontent = null, string $filtered = null): int {
 295          global $DB;
 296  
 297          if (!$jsoncontent) {
 298              $jsoncontent = json_encode(
 299                  array(
 300                      'text' => '<p>Dummy text<\/p>\n',
 301                      'questions' => '<p>Test question<\/p>\n'
 302                  )
 303              );
 304          }
 305  
 306          if (!$filtered) {
 307              $filtered = json_encode(
 308                  array(
 309                      'text' => 'Dummy text',
 310                      'questions' => 'Test question'
 311                  )
 312              );
 313          }
 314  
 315          return $DB->insert_record(
 316              'h5p',
 317              array(
 318                  'jsoncontent' => $jsoncontent,
 319                  'displayoptions' => 8,
 320                  'mainlibraryid' => $mainlibid,
 321                  'timecreated' => time(),
 322                  'timemodified' => time(),
 323                  'filtered' => $filtered,
 324                  'pathnamehash' => sha1('pathname'),
 325                  'contenthash' => sha1('content')
 326              )
 327          );
 328      }
 329  
 330      /**
 331       * Create a record in the h5p_contents_libraries database table.
 332       *
 333       * @param string $h5pid The ID of the H5P content
 334       * @param int $libid The ID of the library
 335       * @param string $dependencytype The dependency type
 336       * @return int The ID of the added record
 337       */
 338      public function create_contents_libraries_record(string $h5pid, int $libid,
 339              string $dependencytype = 'preloaded'): int {
 340          global $DB;
 341  
 342          return $DB->insert_record(
 343              'h5p_contents_libraries',
 344              array(
 345                  'h5pid' => $h5pid,
 346                  'libraryid' => $libid,
 347                  'dependencytype' => $dependencytype,
 348                  'dropcss' => 0,
 349                  'weight' => 1
 350              )
 351          );
 352      }
 353  
 354      /**
 355       * Create a record in the h5p_library_dependencies database table.
 356       *
 357       * @param int $libid The ID of the library
 358       * @param int $requiredlibid The ID of the required library
 359       * @param string $dependencytype The dependency type
 360       * @return int The ID of the added record
 361       */
 362      public function create_library_dependency_record(int $libid, int $requiredlibid,
 363              string $dependencytype = 'preloaded'): int {
 364          global $DB;
 365  
 366          return $DB->insert_record(
 367              'h5p_library_dependencies',
 368              array(
 369                  'libraryid' => $libid,
 370                  'requiredlibraryid' => $requiredlibid,
 371                  'dependencytype' => $dependencytype
 372              )
 373          );
 374      }
 375  
 376      /**
 377       * Create H5P content type records in the h5p_libraries database table.
 378       *
 379       * @param array $typestonotinstall H5P content types that should not be installed
 380       * @param core $core h5p_test_core instance required to use the exttests URL
 381       * @return array Data of the content types not installed.
 382       *
 383       * @throws invalid_response_exception If request to get the latest content types fails (usually due to a transient error)
 384       */
 385      public function create_content_types(array $typestonotinstall, core $core): array {
 386          global $DB;
 387  
 388          autoloader::register();
 389  
 390          // Get info of latest content types versions.
 391          $response = $core->get_latest_content_types();
 392          if (!empty($response->error)) {
 393              throw new invalid_response_exception($response->error);
 394          }
 395  
 396          $installedtypes = 0;
 397  
 398          // Fake installation of all other H5P content types.
 399          foreach ($response->contentTypes as $contenttype) {
 400              // Don't install pending content types.
 401              if (in_array($contenttype->id, $typestonotinstall)) {
 402                  continue;
 403              }
 404              $library = [
 405                  'machinename' => $contenttype->id,
 406                  'majorversion' => $contenttype->version->major,
 407                  'minorversion' => $contenttype->version->minor,
 408                  'patchversion' => $contenttype->version->patch,
 409                  'runnable' => 1,
 410                  'coremajor' => $contenttype->coreApiVersionNeeded->major,
 411                  'coreminor' => $contenttype->coreApiVersionNeeded->minor
 412              ];
 413              $DB->insert_record('h5p_libraries', (object) $library);
 414              $installedtypes++;
 415          }
 416  
 417          return [$installedtypes, count($typestonotinstall)];
 418      }
 419  
 420      /**
 421       * Add a record on files table for a file that belongs to
 422       *
 423       * @param string $file File name and path inside the filearea.
 424       * @param string $filearea The filearea in which the file is ("editor" or "content").
 425       * @param int $contentid Id of the H5P content to which the file belongs. null if the file is in the editor.
 426       *
 427       * @return stored_file;
 428       * @throws coding_exception
 429       */
 430      public function create_content_file(string $file, string $filearea, int $contentid = 0): stored_file {
 431          global $USER;
 432  
 433          $filepath = '/'.dirname($file).'/';
 434          $filename = basename($file);
 435  
 436          if (($filearea === 'content') && ($contentid == 0)) {
 437              throw new coding_exception('Files belonging to an H5P content must specify the H5P content id');
 438          }
 439  
 440          if ($filearea === 'draft') {
 441              $usercontext = \context_user::instance($USER->id);
 442              $context = $usercontext->id;
 443              $component = 'user';
 444              $itemid = 0;
 445          } else {
 446              $systemcontext = context_system::instance();
 447              $context = $systemcontext->id;
 448              $component = \core_h5p\file_storage::COMPONENT;
 449              $itemid = $contentid;
 450          }
 451  
 452          $content = 'fake content';
 453  
 454          $filerecord = array(
 455              'contextid' => $context,
 456              'component' => $component,
 457              'filearea'  => $filearea,
 458              'itemid'    => $itemid,
 459              'filepath'  => $filepath,
 460              'filename'  => $filename,
 461          );
 462  
 463          $fs = new file_storage();
 464          return $fs->create_file_from_string($filerecord, $content);
 465      }
 466  
 467      /**
 468       * Create a fake export H5P deployed file.
 469       *
 470       * @param string $filename Name of the H5P file to deploy.
 471       * @param int $contextid Context id of the H5P activity.
 472       * @param string $component component.
 473       * @param string $filearea file area.
 474       * @param int $typeurl Type of url to create the export url plugin file.
 475       * @return array return deployed file information.
 476       */
 477      public function create_export_file(string $filename, int $contextid,
 478          string $component,
 479          string $filearea,
 480          int $typeurl = self::WSPLUGINFILE): array {
 481          global $CFG;
 482  
 483          // We need the autoloader for H5P player.
 484          autoloader::register();
 485  
 486          $path = $CFG->dirroot.'/h5p/tests/fixtures/'.$filename;
 487          $filerecord = [
 488              'contextid' => $contextid,
 489              'component' => $component,
 490              'filearea'  => $filearea,
 491              'itemid'    => 0,
 492              'filepath'  => '/',
 493              'filename'  => $filename,
 494          ];
 495          // Load the h5p file into DB.
 496          $fs = get_file_storage();
 497          if (!$fs->get_file($contextid, $component, $filearea, $filerecord['itemid'], $filerecord['filepath'], $filename)) {
 498              $fs->create_file_from_pathname($filerecord, $path);
 499          }
 500  
 501          // Make the URL to pass to the player.
 502          if ($typeurl == self::WSPLUGINFILE) {
 503              $url = \moodle_url::make_webservice_pluginfile_url(
 504                  $filerecord['contextid'],
 505                  $filerecord['component'],
 506                  $filerecord['filearea'],
 507                  $filerecord['itemid'],
 508                  $filerecord['filepath'],
 509                  $filerecord['filename']
 510              );
 511          } else {
 512              $includetoken = false;
 513              if ($typeurl == self::TOKENPLUGINFILE) {
 514                  $includetoken = true;
 515              }
 516              $url = \moodle_url::make_pluginfile_url(
 517                  $filerecord['contextid'],
 518                  $filerecord['component'],
 519                  $filerecord['filearea'],
 520                  $filerecord['itemid'],
 521                  $filerecord['filepath'],
 522                  $filerecord['filename'],
 523                  false,
 524                  $includetoken
 525              );
 526          }
 527  
 528          $config = new stdClass();
 529          $h5pplayer = new player($url->out(false), $config);
 530          // We need to add assets to page to create the export file.
 531          $h5pplayer->add_assets_to_page();
 532  
 533          // Call the method. We need the id of the new H5P content.
 534          $rc = new \ReflectionClass(player::class);
 535          $rcp = $rc->getProperty('h5pid');
 536          $rcp->setAccessible(true);
 537          $h5pid = $rcp->getValue($h5pplayer);
 538  
 539          // Get the info export file.
 540          $factory = new factory();
 541          $core = $factory->get_core();
 542          $content = $core->loadContent($h5pid);
 543          $slug = $content['slug'] ? $content['slug'] . '-' : '';
 544          $exportfilename = "{$slug}{$h5pid}.h5p";
 545          $fileh5p = $core->fs->get_export_file($exportfilename);
 546          $deployedfile = [];
 547          $deployedfile['filename'] = $fileh5p->get_filename();
 548          $deployedfile['filepath'] = $fileh5p->get_filepath();
 549          $deployedfile['mimetype'] = $fileh5p->get_mimetype();
 550          $deployedfile['filesize'] = $fileh5p->get_filesize();
 551          $deployedfile['timemodified'] = $fileh5p->get_timemodified();
 552  
 553          // Create the url depending the request was made through typeurl.
 554          if ($typeurl == self::WSPLUGINFILE) {
 555              $url  = \moodle_url::make_webservice_pluginfile_url(
 556                  $fileh5p->get_contextid(),
 557                  $fileh5p->get_component(),
 558                  $fileh5p->get_filearea(),
 559                  '',
 560                  '',
 561                  $fileh5p->get_filename()
 562              );
 563          } else {
 564              $includetoken = false;
 565              if ($typeurl == self::TOKENPLUGINFILE) {
 566                  $includetoken = true;
 567              }
 568              $url = \moodle_url::make_pluginfile_url(
 569                  $fileh5p->get_contextid(),
 570                  $fileh5p->get_component(),
 571                  $fileh5p->get_filearea(),
 572                  '',
 573                  '',
 574                  $fileh5p->get_filename(),
 575                  false,
 576                  $includetoken
 577              );
 578          }
 579          $deployedfile['fileurl'] = $url->out(false);
 580  
 581          return $deployedfile;
 582      }
 583  }