See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Generator for the core_h5p subsystem. 19 * 20 * @package core_h5p 21 * @category test 22 * @copyright 2019 Victor Deniz <victor@moodle.com> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 use core_h5p\local\library\autoloader; 27 use core_h5p\core; 28 use core_h5p\player; 29 use core_h5p\factory; 30 31 defined('MOODLE_INTERNAL') || die(); 32 33 /** 34 * Generator for the core_h5p subsystem. 35 * 36 * @package core_h5p 37 * @category test 38 * @copyright 2019 Victor Deniz <victor@moodle.com> 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 */ 41 class core_h5p_generator extends \component_generator_base { 42 43 /** Url pointing to webservice plugin file. */ 44 public const WSPLUGINFILE = 0; 45 /** Url pointing to token plugin file. */ 46 public const TOKENPLUGINFILE = 1; 47 /** Url pointing to plugin file. */ 48 public const PLUGINFILE = 2; 49 50 /** 51 * Convenience function to create a file. 52 * 53 * @param string $file path to a file. 54 * @param string $content file content. 55 */ 56 public function create_file(string $file, string $content=''): void { 57 $handle = fopen($file, 'w+'); 58 // File content is not relevant. 59 if (empty($content)) { 60 $content = hash("md5", $file); 61 } 62 fwrite($handle, $content); 63 fclose($handle); 64 } 65 66 /** 67 * Creates the file record. Currently used for the cache tests. 68 * 69 * @param string $type Either 'scripts' or 'styles'. 70 * @param string $path Path to the file in the file system. 71 * @param string $version Not really needed at the moment. 72 */ 73 protected function add_libfile_to_array(string $type, string $path, string $version, &$files): void { 74 $files[$type][] = (object)[ 75 'path' => $path, 76 'version' => "?ver=$version" 77 ]; 78 } 79 80 /** 81 * Create the necessary files and return an array structure for a library. 82 * 83 * @param string $uploaddirectory Base directory for the library. 84 * @param int $libraryid Library id. 85 * @param string $machinename Name for this library. 86 * @param int $majorversion Major version (any number will do). 87 * @param int $minorversion Minor version (any number will do). 88 * @param array $langs Languages to be included into the library. 89 * @return array A list of library data and files that the core API will understand. 90 */ 91 public function create_library(string $uploaddirectory, int $libraryid, string $machinename, int $majorversion, 92 int $minorversion, ?array $langs = []): array { 93 // Array $files used in the cache tests. 94 $files = ['scripts' => [], 'styles' => [], 'language' => []]; 95 96 check_dir_exists($uploaddirectory . '/' . 'scripts'); 97 check_dir_exists($uploaddirectory . '/' . 'styles'); 98 if (!empty($langs)) { 99 check_dir_exists($uploaddirectory . '/' . 'language'); 100 } 101 102 $jsonfile = $uploaddirectory . '/' . 'library.json'; 103 $jsfile = $uploaddirectory . '/' . 'scripts/testlib.min.js'; 104 $cssfile = $uploaddirectory . '/' . 'styles/testlib.min.css'; 105 $this->create_file($jsonfile); 106 $this->create_file($jsfile); 107 $this->create_file($cssfile); 108 foreach ($langs as $lang => $value) { 109 $jsonfile = $uploaddirectory . '/' . 'language/' . $lang . '.json'; 110 $this->create_file($jsonfile, $value); 111 } 112 113 $lib = [ 114 'title' => 'Test lib', 115 'description' => 'Test library description', 116 'majorVersion' => $majorversion, 117 'minorVersion' => $minorversion, 118 'patchVersion' => 2, 119 'machineName' => $machinename, 120 'preloadedJs' => [ 121 [ 122 'path' => 'scripts' . '/' . 'testlib.min.js' 123 ] 124 ], 125 'preloadedCss' => [ 126 [ 127 'path' => 'styles' . '/' . 'testlib.min.css' 128 ] 129 ], 130 'uploadDirectory' => $uploaddirectory, 131 'libraryId' => $libraryid 132 ]; 133 134 $version = "{$majorversion}.{$minorversion}.2"; 135 $libname = "{$machinename}-{$majorversion}.{$minorversion}"; 136 $path = '/' . 'libraries' . '/' . $libraryid . '/' . $libname . '/' . 'scripts' . '/' . 'testlib.min.js'; 137 $this->add_libfile_to_array('scripts', $path, $version, $files); 138 $path = '/' . 'libraries' . '/' . $libraryid .'/' . $libname . '/' . 'styles' . '/' . 'testlib.min.css'; 139 $this->add_libfile_to_array('styles', $path, $version, $files); 140 foreach ($langs as $lang => $notused) { 141 $path = '/' . 'libraries' . '/' . $libraryid . '/' . $libname . '/' . 'language' . '/' . $lang . '.json'; 142 $this->add_libfile_to_array('language', $path, $version, $files); 143 } 144 145 return [$lib, $files]; 146 } 147 148 /** 149 * Save the library files on the filesystem. 150 * 151 * @param stdClss $lib The library data 152 */ 153 private function save_library(stdClass $lib) { 154 // Get a temp path. 155 $filestorage = new \core_h5p\file_storage(); 156 $temppath = $filestorage->getTmpPath(); 157 158 // Create and save the library files on the filesystem. 159 $basedirectorymain = $temppath . '/' . $lib->machinename . '-' . 160 $lib->majorversion . '.' . $lib->minorversion; 161 162 list($library, $libraryfiles) = $this->create_library($basedirectorymain, $lib->id, $lib->machinename, 163 $lib->majorversion, $lib->minorversion); 164 165 $filestorage->saveLibrary($library); 166 } 167 168 /** 169 * Populate H5P database tables with relevant data to simulate the process of adding H5P content. 170 * 171 * @param bool $createlibraryfiles Whether to create and store library files on the filesystem 172 * @return stdClass An object representing the added H5P records 173 */ 174 public function generate_h5p_data(bool $createlibraryfiles = false): stdClass { 175 // Create libraries. 176 $mainlib = $libraries[] = $this->create_library_record('MainLibrary', 'Main Lib', 1, 0); 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); 182 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 } 189 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); 208 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 } 240 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; 256 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 ); 271 272 $libraryid = $DB->insert_record('h5p_libraries', $content); 273 274 return $DB->get_record('h5p_libraries', ['id' => $libraryid]); 275 } 276 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; 287 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 } 296 297 if (!$filtered) { 298 $filtered = json_encode( 299 array( 300 'text' => 'Dummy text', 301 'questions' => 'Test question' 302 ) 303 ); 304 } 305 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 } 320 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; 332 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 } 344 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; 356 357 return $DB->insert_record( 358 'h5p_library_dependencies', 359 array( 360 'libraryid' => $libid, 361 'requiredlibraryid' => $requiredlibid, 362 'dependencytype' => $dependencytype 363 ) 364 ); 365 } 366 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; 376 377 autoloader::register(); 378 379 // Get info of latest content types versions. 380 $contenttypes = $core->get_latest_content_types()->contentTypes; 381 382 $installedtypes = 0; 383 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 } 402 403 return [$installedtypes, count($typestonotinstall)]; 404 } 405 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 global $USER; 418 419 $filepath = '/'.dirname($file).'/'; 420 $filename = basename($file); 421 422 if (($filearea === 'content') && ($contentid == 0)) { 423 throw new coding_exception('Files belonging to an H5P content must specify the H5P content id'); 424 } 425 426 if ($filearea === 'draft') { 427 $usercontext = \context_user::instance($USER->id); 428 $context = $usercontext->id; 429 $component = 'user'; 430 $itemid = 0; 431 } else { 432 $systemcontext = context_system::instance(); 433 $context = $systemcontext->id; 434 $component = \core_h5p\file_storage::COMPONENT; 435 $itemid = $contentid; 436 } 437 438 $content = 'fake content'; 439 440 $filerecord = array( 441 'contextid' => $context, 442 'component' => $component, 443 'filearea' => $filearea, 444 'itemid' => $itemid, 445 'filepath' => $filepath, 446 'filename' => $filename, 447 ); 448 449 $fs = new file_storage(); 450 return $fs->create_file_from_string($filerecord, $content); 451 } 452 453 /** 454 * Create a fake export H5P deployed file. 455 * 456 * @param string $filename Name of the H5P file to deploy. 457 * @param int $contextid Context id of the H5P activity. 458 * @param string $component component. 459 * @param string $filearea file area. 460 * @param int $typeurl Type of url to create the export url plugin file. 461 * @return array return deployed file information. 462 */ 463 public function create_export_file(string $filename, int $contextid, 464 string $component, 465 string $filearea, 466 int $typeurl = self::WSPLUGINFILE): array { 467 global $CFG; 468 469 // We need the autoloader for H5P player. 470 autoloader::register(); 471 472 $path = $CFG->dirroot.'/h5p/tests/fixtures/'.$filename; 473 $filerecord = [ 474 'contextid' => $contextid, 475 'component' => $component, 476 'filearea' => $filearea, 477 'itemid' => 0, 478 'filepath' => '/', 479 'filename' => $filename, 480 ]; 481 // Load the h5p file into DB. 482 $fs = get_file_storage(); 483 if (!$fs->get_file($contextid, $component, $filearea, $filerecord['itemid'], $filerecord['filepath'], $filename)) { 484 $fs->create_file_from_pathname($filerecord, $path); 485 } 486 487 // Make the URL to pass to the player. 488 if ($typeurl == self::WSPLUGINFILE) { 489 $url = \moodle_url::make_webservice_pluginfile_url( 490 $filerecord['contextid'], 491 $filerecord['component'], 492 $filerecord['filearea'], 493 $filerecord['itemid'], 494 $filerecord['filepath'], 495 $filerecord['filename'] 496 ); 497 } else { 498 $includetoken = false; 499 if ($typeurl == self::TOKENPLUGINFILE) { 500 $includetoken = true; 501 } 502 $url = \moodle_url::make_pluginfile_url( 503 $filerecord['contextid'], 504 $filerecord['component'], 505 $filerecord['filearea'], 506 $filerecord['itemid'], 507 $filerecord['filepath'], 508 $filerecord['filename'], 509 false, 510 $includetoken 511 ); 512 } 513 514 $config = new stdClass(); 515 $h5pplayer = new player($url->out(false), $config); 516 // We need to add assets to page to create the export file. 517 $h5pplayer->add_assets_to_page(); 518 519 // Call the method. We need the id of the new H5P content. 520 $rc = new \ReflectionClass(player::class); 521 $rcp = $rc->getProperty('h5pid'); 522 $rcp->setAccessible(true); 523 $h5pid = $rcp->getValue($h5pplayer); 524 525 // Get the info export file. 526 $factory = new factory(); 527 $core = $factory->get_core(); 528 $content = $core->loadContent($h5pid); 529 $slug = $content['slug'] ? $content['slug'] . '-' : ''; 530 $exportfilename = "{$slug}{$h5pid}.h5p"; 531 $fileh5p = $core->fs->get_export_file($exportfilename); 532 $deployedfile = []; 533 $deployedfile['filename'] = $fileh5p->get_filename(); 534 $deployedfile['filepath'] = $fileh5p->get_filepath(); 535 $deployedfile['mimetype'] = $fileh5p->get_mimetype(); 536 $deployedfile['filesize'] = $fileh5p->get_filesize(); 537 $deployedfile['timemodified'] = $fileh5p->get_timemodified(); 538 539 // Create the url depending the request was made through typeurl. 540 if ($typeurl == self::WSPLUGINFILE) { 541 $url = \moodle_url::make_webservice_pluginfile_url( 542 $fileh5p->get_contextid(), 543 $fileh5p->get_component(), 544 $fileh5p->get_filearea(), 545 '', 546 '', 547 $fileh5p->get_filename() 548 ); 549 } else { 550 $includetoken = false; 551 if ($typeurl == self::TOKENPLUGINFILE) { 552 $includetoken = true; 553 } 554 $url = \moodle_url::make_pluginfile_url( 555 $fileh5p->get_contextid(), 556 $fileh5p->get_component(), 557 $fileh5p->get_filearea(), 558 '', 559 '', 560 $fileh5p->get_filename(), 561 false, 562 $includetoken 563 ); 564 } 565 $deployedfile['fileurl'] = $url->out(false); 566 567 return $deployedfile; 568 } 569 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body