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