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.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

< /** < * Generator for the core_h5p subsystem. < * < * @package core_h5p < * @category test < * @copyright 2019 Victor Deniz <victor@moodle.com> < * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later < */ <
use core_h5p\local\library\autoloader; use core_h5p\core; use core_h5p\player; use core_h5p\factory;
< < defined('MOODLE_INTERNAL') || die();
> use core_xapi\local\statement\item_activity;
/** * Generator for the core_h5p subsystem. * * @package core_h5p * @category test * @copyright 2019 Victor Deniz <victor@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class core_h5p_generator extends \component_generator_base { /** Url pointing to webservice plugin file. */ public const WSPLUGINFILE = 0; /** Url pointing to token plugin file. */ public const TOKENPLUGINFILE = 1; /** Url pointing to plugin file. */ public const PLUGINFILE = 2; /** * Convenience function to create a file. * * @param string $file path to a file. * @param string $content file content. */ public function create_file(string $file, string $content=''): void { $handle = fopen($file, 'w+'); // File content is not relevant. if (empty($content)) { $content = hash("md5", $file); } fwrite($handle, $content); fclose($handle); } /** * Creates the file record. Currently used for the cache tests. * * @param string $type Either 'scripts' or 'styles'. * @param string $path Path to the file in the file system. * @param string $version Not really needed at the moment. */ protected function add_libfile_to_array(string $type, string $path, string $version, &$files): void { $files[$type][] = (object)[ 'path' => $path, 'version' => "?ver=$version" ]; } /** * Create the necessary files and return an array structure for a library. * * @param string $uploaddirectory Base directory for the library. * @param int $libraryid Library id. * @param string $machinename Name for this library. * @param int $majorversion Major version (any number will do). * @param int $minorversion Minor version (any number will do). * @param array $langs Languages to be included into the library. * @return array A list of library data and files that the core API will understand. */ public function create_library(string $uploaddirectory, int $libraryid, string $machinename, int $majorversion, int $minorversion, ?array $langs = []): array { // Array $files used in the cache tests. $files = ['scripts' => [], 'styles' => [], 'language' => []]; check_dir_exists($uploaddirectory . '/' . 'scripts'); check_dir_exists($uploaddirectory . '/' . 'styles'); if (!empty($langs)) { check_dir_exists($uploaddirectory . '/' . 'language'); } $jsonfile = $uploaddirectory . '/' . 'library.json'; $jsfile = $uploaddirectory . '/' . 'scripts/testlib.min.js'; $cssfile = $uploaddirectory . '/' . 'styles/testlib.min.css'; $this->create_file($jsonfile); $this->create_file($jsfile); $this->create_file($cssfile); foreach ($langs as $lang => $value) { $jsonfile = $uploaddirectory . '/' . 'language/' . $lang . '.json'; $this->create_file($jsonfile, $value); } $lib = [ 'title' => 'Test lib', 'description' => 'Test library description', 'majorVersion' => $majorversion, 'minorVersion' => $minorversion, 'patchVersion' => 2, 'machineName' => $machinename, 'preloadedJs' => [ [ 'path' => 'scripts' . '/' . 'testlib.min.js' ] ], 'preloadedCss' => [ [ 'path' => 'styles' . '/' . 'testlib.min.css' ] ], 'uploadDirectory' => $uploaddirectory, 'libraryId' => $libraryid ]; $version = "{$majorversion}.{$minorversion}.2"; $libname = "{$machinename}-{$majorversion}.{$minorversion}"; $path = '/' . 'libraries' . '/' . $libraryid . '/' . $libname . '/' . 'scripts' . '/' . 'testlib.min.js'; $this->add_libfile_to_array('scripts', $path, $version, $files); $path = '/' . 'libraries' . '/' . $libraryid .'/' . $libname . '/' . 'styles' . '/' . 'testlib.min.css'; $this->add_libfile_to_array('styles', $path, $version, $files); foreach ($langs as $lang => $notused) { $path = '/' . 'libraries' . '/' . $libraryid . '/' . $libname . '/' . 'language' . '/' . $lang . '.json'; $this->add_libfile_to_array('language', $path, $version, $files); } return [$lib, $files]; } /** * Save the library files on the filesystem. * * @param stdClss $lib The library data */ private function save_library(stdClass $lib) { // Get a temp path. $filestorage = new \core_h5p\file_storage(); $temppath = $filestorage->getTmpPath(); // Create and save the library files on the filesystem. $basedirectorymain = $temppath . '/' . $lib->machinename . '-' . $lib->majorversion . '.' . $lib->minorversion; list($library, $libraryfiles) = $this->create_library($basedirectorymain, $lib->id, $lib->machinename, $lib->majorversion, $lib->minorversion); $filestorage->saveLibrary($library); } /** * Populate H5P database tables with relevant data to simulate the process of adding H5P content. * * @param bool $createlibraryfiles Whether to create and store library files on the filesystem
> * @param array|null $filerecord The file associated to the H5P entry.
* @return stdClass An object representing the added H5P records */
< public function generate_h5p_data(bool $createlibraryfiles = false): stdClass {
> public function generate_h5p_data(bool $createlibraryfiles = false, ?array $filerecord = null): stdClass {
// Create libraries.
< $mainlib = $libraries[] = $this->create_library_record('MainLibrary', 'Main Lib', 1, 0); < $lib1 = $libraries[] = $this->create_library_record('Library1', 'Lib1', 2, 0); < $lib2 = $libraries[] = $this->create_library_record('Library2', 'Lib2', 2, 1); < $lib3 = $libraries[] = $this->create_library_record('Library3', 'Lib3', 3, 2);
> $mainlib = $libraries[] = $this->create_library_record('MainLibrary', 'Main Lib', 1, 0, 1, '', null, > 'http://tutorial.org', 'http://example.org'); > $lib1 = $libraries[] = $this->create_library_record('Library1', 'Lib1', 2, 0, 1, '', null, null, 'http://example.org'); > $lib2 = $libraries[] = $this->create_library_record('Library2', 'Lib2', 2, 1, 1, '', null, 'http://tutorial.org'); > $lib3 = $libraries[] = $this->create_library_record('Library3', 'Lib3', 3, 2, 1, '', null, null, null, true, 0);
$lib4 = $libraries[] = $this->create_library_record('Library4', 'Lib4', 1, 1); $lib5 = $libraries[] = $this->create_library_record('Library5', 'Lib5', 1, 3); if ($createlibraryfiles) { foreach ($libraries as $lib) { // Create and save the library files on the filesystem. $this->save_library($lib); } } // Create h5p content.
< $h5p = $this->create_h5p_record($mainlib->id);
> $h5p = $this->create_h5p_record($mainlib->id, null, null, $filerecord);
// Create h5p content library dependencies. $this->create_contents_libraries_record($h5p, $mainlib->id); $this->create_contents_libraries_record($h5p, $lib1->id); $this->create_contents_libraries_record($h5p, $lib2->id); $this->create_contents_libraries_record($h5p, $lib3->id); $this->create_contents_libraries_record($h5p, $lib4->id); // Create library dependencies for $mainlib. $this->create_library_dependency_record($mainlib->id, $lib1->id); $this->create_library_dependency_record($mainlib->id, $lib2->id); $this->create_library_dependency_record($mainlib->id, $lib3->id); // Create library dependencies for $lib1. $this->create_library_dependency_record($lib1->id, $lib2->id); $this->create_library_dependency_record($lib1->id, $lib3->id); $this->create_library_dependency_record($lib1->id, $lib4->id); // Create library dependencies for $lib3. $this->create_library_dependency_record($lib3->id, $lib5->id); return (object) [ 'h5pcontent' => (object) array( 'h5pid' => $h5p, 'contentdependencies' => array($mainlib, $lib1, $lib2, $lib3, $lib4) ), 'mainlib' => (object) array( 'data' => $mainlib, 'dependencies' => array($lib1, $lib2, $lib3) ), 'lib1' => (object) array( 'data' => $lib1, 'dependencies' => array($lib2, $lib3, $lib4) ), 'lib2' => (object) array( 'data' => $lib2, 'dependencies' => array() ), 'lib3' => (object) array( 'data' => $lib3, 'dependencies' => array($lib5) ), 'lib4' => (object) array( 'data' => $lib4, 'dependencies' => array() ), 'lib5' => (object) array( 'data' => $lib5, 'dependencies' => array() ), ]; } /** * Create a record in the h5p_libraries database table. * * @param string $machinename The library machine name * @param string $title The library's name * @param int $majorversion The library's major version * @param int $minorversion The library's minor version * @param int $patchversion The library's patch version * @param string $semantics Json describing the content structure for the library * @param string $addto The plugin configuration data
> * @param string $tutorial The tutorial URL * @return stdClass An object representing the added library record > * @param string $examlpe The example URL */ > * @param bool $enabled Whether the library is enabled or not public function create_library_record(string $machinename, string $title, int $majorversion = 1, > * @param int $runnable Whether the library is runnable (1) or not (0)
< int $minorversion = 0, int $patchversion = 1, string $semantics = '', string $addto = null): stdClass {
> int $minorversion = 0, int $patchversion = 1, string $semantics = '', string $addto = null, > string $tutorial = null, string $example = null, bool $enabled = true, int $runnable = 1): stdClass {
global $DB;
< $content = array(
> $content = [
'machinename' => $machinename, 'title' => $title, 'majorversion' => $majorversion, 'minorversion' => $minorversion, 'patchversion' => $patchversion,
< 'runnable' => 1,
> 'runnable' => $runnable,
'fullscreen' => 1, 'preloadedjs' => 'js/example.js', 'preloadedcss' => 'css/example.css', 'droplibrarycss' => '', 'semantics' => $semantics,
< 'addto' => $addto < );
> 'addto' => $addto, > 'tutorial' => $tutorial, > 'example' => $example, > 'enabled' => $enabled, > ];
$libraryid = $DB->insert_record('h5p_libraries', $content); return $DB->get_record('h5p_libraries', ['id' => $libraryid]); } /** * Create a record in the h5p database table. * * @param int $mainlibid The ID of the content's main library * @param string $jsoncontent The content in json format * @param string $filtered The filtered content parameters
> * @param array|null $filerecord The file associated to the H5P entry.
* @return int The ID of the added record */
< public function create_h5p_record(int $mainlibid, string $jsoncontent = null, string $filtered = null): int {
> public function create_h5p_record(int $mainlibid, string $jsoncontent = null, string $filtered = null, > ?array $filerecord = null): int {
global $DB; if (!$jsoncontent) { $jsoncontent = json_encode( array( 'text' => '<p>Dummy text<\/p>\n', 'questions' => '<p>Test question<\/p>\n' ) ); } if (!$filtered) { $filtered = json_encode( array( 'text' => 'Dummy text', 'questions' => 'Test question' ) ); }
> // Load the H5P file into DB. return $DB->insert_record( > $pathnamehash = sha1('pathname'); 'h5p', > $contenthash = sha1('content'); array( > if ($filerecord) { 'jsoncontent' => $jsoncontent, > $fs = get_file_storage(); 'displayoptions' => 8, > if (!$fs->get_file( 'mainlibraryid' => $mainlibid, > $filerecord['contextid'], 'timecreated' => time(), > $filerecord['component'], 'timemodified' => time(), > $filerecord['filearea'], 'filtered' => $filtered, > $filerecord['itemid'], 'pathnamehash' => sha1('pathname'), > $filerecord['filepath'], 'contenthash' => sha1('content') > $filerecord['filename'])) { ) > $file = $fs->create_file_from_string($filerecord, $jsoncontent); ); > $pathnamehash = $file->get_pathnamehash(); } > $contenthash = $file->get_contenthash(); > if (array_key_exists('addxapistate', $filerecord) && $filerecord['addxapistate']) { /** > // Save some xAPI state associated to this H5P content. * Create a record in the h5p_contents_libraries database table. > $params = [ * > 'component' => $filerecord['component'], * @param string $h5pid The ID of the H5P content > 'activity' => item_activity::create_from_id($filerecord['contextid']), * @param int $libid The ID of the library > ]; * @param string $dependencytype The dependency type > global $CFG; * @return int The ID of the added record > require_once($CFG->dirroot.'/lib/xapi/tests/helper.php'); */ > \core_xapi\test_helper::create_state($params, true); public function create_contents_libraries_record(string $h5pid, int $libid, > } string $dependencytype = 'preloaded'): int { > } global $DB; > } >
< array(
> [
< 'pathnamehash' => sha1('pathname'), < 'contenthash' => sha1('content') < )
> 'pathnamehash' => $pathnamehash, > 'contenthash' => $contenthash, > ]
'libraryid' => $libid, 'dependencytype' => $dependencytype, 'dropcss' => 0, 'weight' => 1 ) ); } /** * Create a record in the h5p_library_dependencies database table. * * @param int $libid The ID of the library * @param int $requiredlibid The ID of the required library * @param string $dependencytype The dependency type * @return int The ID of the added record */ public function create_library_dependency_record(int $libid, int $requiredlibid, string $dependencytype = 'preloaded'): int { global $DB; return $DB->insert_record( 'h5p_library_dependencies', array( 'libraryid' => $libid, 'requiredlibraryid' => $requiredlibid, 'dependencytype' => $dependencytype ) ); } /** * Create H5P content type records in the h5p_libraries database table. * * @param array $typestonotinstall H5P content types that should not be installed * @param core $core h5p_test_core instance required to use the exttests URL * @return array Data of the content types not installed.
> * */ > * @throws invalid_response_exception If request to get the latest content types fails (usually due to a transient error)
public function create_content_types(array $typestonotinstall, core $core): array { global $DB; autoloader::register(); // Get info of latest content types versions.
< $contenttypes = $core->get_latest_content_types()->contentTypes;
> $response = $core->get_latest_content_types(); > if (!empty($response->error)) { > throw new invalid_response_exception($response->error); > }
$installedtypes = 0; // Fake installation of all other H5P content types.
< foreach ($contenttypes as $contenttype) {
> foreach ($response->contentTypes as $contenttype) {
// Don't install pending content types. if (in_array($contenttype->id, $typestonotinstall)) { continue; } $library = [ 'machinename' => $contenttype->id, 'majorversion' => $contenttype->version->major, 'minorversion' => $contenttype->version->minor, 'patchversion' => $contenttype->version->patch, 'runnable' => 1, 'coremajor' => $contenttype->coreApiVersionNeeded->major, 'coreminor' => $contenttype->coreApiVersionNeeded->minor ]; $DB->insert_record('h5p_libraries', (object) $library); $installedtypes++; } return [$installedtypes, count($typestonotinstall)]; } /** * Add a record on files table for a file that belongs to * * @param string $file File name and path inside the filearea. * @param string $filearea The filearea in which the file is ("editor" or "content"). * @param int $contentid Id of the H5P content to which the file belongs. null if the file is in the editor. * * @return stored_file; * @throws coding_exception */ public function create_content_file(string $file, string $filearea, int $contentid = 0): stored_file { global $USER; $filepath = '/'.dirname($file).'/'; $filename = basename($file); if (($filearea === 'content') && ($contentid == 0)) { throw new coding_exception('Files belonging to an H5P content must specify the H5P content id'); } if ($filearea === 'draft') { $usercontext = \context_user::instance($USER->id); $context = $usercontext->id; $component = 'user'; $itemid = 0; } else { $systemcontext = context_system::instance(); $context = $systemcontext->id; $component = \core_h5p\file_storage::COMPONENT; $itemid = $contentid; } $content = 'fake content'; $filerecord = array( 'contextid' => $context, 'component' => $component, 'filearea' => $filearea, 'itemid' => $itemid, 'filepath' => $filepath, 'filename' => $filename, ); $fs = new file_storage(); return $fs->create_file_from_string($filerecord, $content); } /** * Create a fake export H5P deployed file. * * @param string $filename Name of the H5P file to deploy. * @param int $contextid Context id of the H5P activity. * @param string $component component. * @param string $filearea file area. * @param int $typeurl Type of url to create the export url plugin file. * @return array return deployed file information. */ public function create_export_file(string $filename, int $contextid, string $component, string $filearea, int $typeurl = self::WSPLUGINFILE): array { global $CFG; // We need the autoloader for H5P player. autoloader::register(); $path = $CFG->dirroot.'/h5p/tests/fixtures/'.$filename; $filerecord = [ 'contextid' => $contextid, 'component' => $component, 'filearea' => $filearea, 'itemid' => 0, 'filepath' => '/', 'filename' => $filename, ]; // Load the h5p file into DB. $fs = get_file_storage(); if (!$fs->get_file($contextid, $component, $filearea, $filerecord['itemid'], $filerecord['filepath'], $filename)) { $fs->create_file_from_pathname($filerecord, $path); } // Make the URL to pass to the player. if ($typeurl == self::WSPLUGINFILE) { $url = \moodle_url::make_webservice_pluginfile_url( $filerecord['contextid'], $filerecord['component'], $filerecord['filearea'], $filerecord['itemid'], $filerecord['filepath'], $filerecord['filename'] ); } else { $includetoken = false; if ($typeurl == self::TOKENPLUGINFILE) { $includetoken = true; } $url = \moodle_url::make_pluginfile_url( $filerecord['contextid'], $filerecord['component'], $filerecord['filearea'], $filerecord['itemid'], $filerecord['filepath'], $filerecord['filename'], false, $includetoken ); } $config = new stdClass(); $h5pplayer = new player($url->out(false), $config); // We need to add assets to page to create the export file. $h5pplayer->add_assets_to_page(); // Call the method. We need the id of the new H5P content. $rc = new \ReflectionClass(player::class); $rcp = $rc->getProperty('h5pid'); $rcp->setAccessible(true); $h5pid = $rcp->getValue($h5pplayer); // Get the info export file. $factory = new factory(); $core = $factory->get_core(); $content = $core->loadContent($h5pid); $slug = $content['slug'] ? $content['slug'] . '-' : ''; $exportfilename = "{$slug}{$h5pid}.h5p"; $fileh5p = $core->fs->get_export_file($exportfilename); $deployedfile = []; $deployedfile['filename'] = $fileh5p->get_filename(); $deployedfile['filepath'] = $fileh5p->get_filepath(); $deployedfile['mimetype'] = $fileh5p->get_mimetype(); $deployedfile['filesize'] = $fileh5p->get_filesize(); $deployedfile['timemodified'] = $fileh5p->get_timemodified(); // Create the url depending the request was made through typeurl. if ($typeurl == self::WSPLUGINFILE) { $url = \moodle_url::make_webservice_pluginfile_url( $fileh5p->get_contextid(), $fileh5p->get_component(), $fileh5p->get_filearea(), '', '', $fileh5p->get_filename() ); } else { $includetoken = false; if ($typeurl == self::TOKENPLUGINFILE) { $includetoken = true; } $url = \moodle_url::make_pluginfile_url( $fileh5p->get_contextid(), $fileh5p->get_component(), $fileh5p->get_filearea(), '', '', $fileh5p->get_filename(), false, $includetoken ); } $deployedfile['fileurl'] = $url->out(false); return $deployedfile; } }