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