Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 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 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

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