Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 9 May 2022 (12 months).
  • Bug fixes for security issues in 3.11.x will end 14 November 2022 (18 months).
  • 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 38 and 311] [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   * 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      public function create_content_types(array $typestonotinstall, core $core): array {
     384          global $DB;
     385  
     386          autoloader::register();
     387  
     388          // Get info of latest content types versions.
     389          $contenttypes = $core->get_latest_content_types()->contentTypes;
     390  
     391          $installedtypes = 0;
     392  
     393          // Fake installation of all other H5P content types.
     394          foreach ($contenttypes as $contenttype) {
     395              // Don't install pending content types.
     396              if (in_array($contenttype->id, $typestonotinstall)) {
     397                  continue;
     398              }
     399              $library = [
     400                  'machinename' => $contenttype->id,
     401                  'majorversion' => $contenttype->version->major,
     402                  'minorversion' => $contenttype->version->minor,
     403                  'patchversion' => $contenttype->version->patch,
     404                  'runnable' => 1,
     405                  'coremajor' => $contenttype->coreApiVersionNeeded->major,
     406                  'coreminor' => $contenttype->coreApiVersionNeeded->minor
     407              ];
     408              $DB->insert_record('h5p_libraries', (object) $library);
     409              $installedtypes++;
     410          }
     411  
     412          return [$installedtypes, count($typestonotinstall)];
     413      }
     414  
     415      /**
     416       * Add a record on files table for a file that belongs to
     417       *
     418       * @param string $file File name and path inside the filearea.
     419       * @param string $filearea The filearea in which the file is ("editor" or "content").
     420       * @param int $contentid Id of the H5P content to which the file belongs. null if the file is in the editor.
     421       *
     422       * @return stored_file;
     423       * @throws coding_exception
     424       */
     425      public function create_content_file(string $file, string $filearea, int $contentid = 0): stored_file {
     426          global $USER;
     427  
     428          $filepath = '/'.dirname($file).'/';
     429          $filename = basename($file);
     430  
     431          if (($filearea === 'content') && ($contentid == 0)) {
     432              throw new coding_exception('Files belonging to an H5P content must specify the H5P content id');
     433          }
     434  
     435          if ($filearea === 'draft') {
     436              $usercontext = \context_user::instance($USER->id);
     437              $context = $usercontext->id;
     438              $component = 'user';
     439              $itemid = 0;
     440          } else {
     441              $systemcontext = context_system::instance();
     442              $context = $systemcontext->id;
     443              $component = \core_h5p\file_storage::COMPONENT;
     444              $itemid = $contentid;
     445          }
     446  
     447          $content = 'fake content';
     448  
     449          $filerecord = array(
     450              'contextid' => $context,
     451              'component' => $component,
     452              'filearea'  => $filearea,
     453              'itemid'    => $itemid,
     454              'filepath'  => $filepath,
     455              'filename'  => $filename,
     456          );
     457  
     458          $fs = new file_storage();
     459          return $fs->create_file_from_string($filerecord, $content);
     460      }
     461  
     462      /**
     463       * Create a fake export H5P deployed file.
     464       *
     465       * @param string $filename Name of the H5P file to deploy.
     466       * @param int $contextid Context id of the H5P activity.
     467       * @param string $component component.
     468       * @param string $filearea file area.
     469       * @param int $typeurl Type of url to create the export url plugin file.
     470       * @return array return deployed file information.
     471       */
     472      public function create_export_file(string $filename, int $contextid,
     473          string $component,
     474          string $filearea,
     475          int $typeurl = self::WSPLUGINFILE): array {
     476          global $CFG;
     477  
     478          // We need the autoloader for H5P player.
     479          autoloader::register();
     480  
     481          $path = $CFG->dirroot.'/h5p/tests/fixtures/'.$filename;
     482          $filerecord = [
     483              'contextid' => $contextid,
     484              'component' => $component,
     485              'filearea'  => $filearea,
     486              'itemid'    => 0,
     487              'filepath'  => '/',
     488              'filename'  => $filename,
     489          ];
     490          // Load the h5p file into DB.
     491          $fs = get_file_storage();
     492          if (!$fs->get_file($contextid, $component, $filearea, $filerecord['itemid'], $filerecord['filepath'], $filename)) {
     493              $fs->create_file_from_pathname($filerecord, $path);
     494          }
     495  
     496          // Make the URL to pass to the player.
     497          if ($typeurl == self::WSPLUGINFILE) {
     498              $url = \moodle_url::make_webservice_pluginfile_url(
     499                  $filerecord['contextid'],
     500                  $filerecord['component'],
     501                  $filerecord['filearea'],
     502                  $filerecord['itemid'],
     503                  $filerecord['filepath'],
     504                  $filerecord['filename']
     505              );
     506          } else {
     507              $includetoken = false;
     508              if ($typeurl == self::TOKENPLUGINFILE) {
     509                  $includetoken = true;
     510              }
     511              $url = \moodle_url::make_pluginfile_url(
     512                  $filerecord['contextid'],
     513                  $filerecord['component'],
     514                  $filerecord['filearea'],
     515                  $filerecord['itemid'],
     516                  $filerecord['filepath'],
     517                  $filerecord['filename'],
     518                  false,
     519                  $includetoken
     520              );
     521          }
     522  
     523          $config = new stdClass();
     524          $h5pplayer = new player($url->out(false), $config);
     525          // We need to add assets to page to create the export file.
     526          $h5pplayer->add_assets_to_page();
     527  
     528          // Call the method. We need the id of the new H5P content.
     529          $rc = new \ReflectionClass(player::class);
     530          $rcp = $rc->getProperty('h5pid');
     531          $rcp->setAccessible(true);
     532          $h5pid = $rcp->getValue($h5pplayer);
     533  
     534          // Get the info export file.
     535          $factory = new factory();
     536          $core = $factory->get_core();
     537          $content = $core->loadContent($h5pid);
     538          $slug = $content['slug'] ? $content['slug'] . '-' : '';
     539          $exportfilename = "{$slug}{$h5pid}.h5p";
     540          $fileh5p = $core->fs->get_export_file($exportfilename);
     541          $deployedfile = [];
     542          $deployedfile['filename'] = $fileh5p->get_filename();
     543          $deployedfile['filepath'] = $fileh5p->get_filepath();
     544          $deployedfile['mimetype'] = $fileh5p->get_mimetype();
     545          $deployedfile['filesize'] = $fileh5p->get_filesize();
     546          $deployedfile['timemodified'] = $fileh5p->get_timemodified();
     547  
     548          // Create the url depending the request was made through typeurl.
     549          if ($typeurl == self::WSPLUGINFILE) {
     550              $url  = \moodle_url::make_webservice_pluginfile_url(
     551                  $fileh5p->get_contextid(),
     552                  $fileh5p->get_component(),
     553                  $fileh5p->get_filearea(),
     554                  '',
     555                  '',
     556                  $fileh5p->get_filename()
     557              );
     558          } else {
     559              $includetoken = false;
     560              if ($typeurl == self::TOKENPLUGINFILE) {
     561                  $includetoken = true;
     562              }
     563              $url = \moodle_url::make_pluginfile_url(
     564                  $fileh5p->get_contextid(),
     565                  $fileh5p->get_component(),
     566                  $fileh5p->get_filearea(),
     567                  '',
     568                  '',
     569                  $fileh5p->get_filename(),
     570                  false,
     571                  $includetoken
     572              );
     573          }
     574          $deployedfile['fileurl'] = $url->out(false);
     575  
     576          return $deployedfile;
     577      }
     578  }