Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]
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 use core_h5p\local\library\autoloader; 18 use core_h5p\core; 19 use core_h5p\player; 20 use core_h5p\factory; 21 use core_xapi\local\statement\item_activity; 22 23 /** 24 * Generator for the core_h5p subsystem. 25 * 26 * @package core_h5p 27 * @category test 28 * @copyright 2019 Victor Deniz <victor@moodle.com> 29 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 30 */ 31 class core_h5p_generator extends \component_generator_base { 32 33 /** Url pointing to webservice plugin file. */ 34 public const WSPLUGINFILE = 0; 35 /** Url pointing to token plugin file. */ 36 public const TOKENPLUGINFILE = 1; 37 /** Url pointing to plugin file. */ 38 public const PLUGINFILE = 2; 39 40 /** 41 * Convenience function to create a file. 42 * 43 * @param string $file path to a file. 44 * @param string $content file content. 45 */ 46 public function create_file(string $file, string $content=''): void { 47 $handle = fopen($file, 'w+'); 48 // File content is not relevant. 49 if (empty($content)) { 50 $content = hash("md5", $file); 51 } 52 fwrite($handle, $content); 53 fclose($handle); 54 } 55 56 /** 57 * Creates the file record. Currently used for the cache tests. 58 * 59 * @param string $type Either 'scripts' or 'styles'. 60 * @param string $path Path to the file in the file system. 61 * @param string $version Not really needed at the moment. 62 */ 63 protected function add_libfile_to_array(string $type, string $path, string $version, &$files): void { 64 $files[$type][] = (object)[ 65 'path' => $path, 66 'version' => "?ver=$version" 67 ]; 68 } 69 70 /** 71 * Create the necessary files and return an array structure for a library. 72 * 73 * @param string $uploaddirectory Base directory for the library. 74 * @param int $libraryid Library id. 75 * @param string $machinename Name for this library. 76 * @param int $majorversion Major version (any number will do). 77 * @param int $minorversion Minor version (any number will do). 78 * @param array $langs Languages to be included into the library. 79 * @return array A list of library data and files that the core API will understand. 80 */ 81 public function create_library(string $uploaddirectory, int $libraryid, string $machinename, int $majorversion, 82 int $minorversion, ?array $langs = []): array { 83 // Array $files used in the cache tests. 84 $files = ['scripts' => [], 'styles' => [], 'language' => []]; 85 86 check_dir_exists($uploaddirectory . '/' . 'scripts'); 87 check_dir_exists($uploaddirectory . '/' . 'styles'); 88 if (!empty($langs)) { 89 check_dir_exists($uploaddirectory . '/' . 'language'); 90 } 91 92 $jsonfile = $uploaddirectory . '/' . 'library.json'; 93 $jsfile = $uploaddirectory . '/' . 'scripts/testlib.min.js'; 94 $cssfile = $uploaddirectory . '/' . 'styles/testlib.min.css'; 95 $this->create_file($jsonfile); 96 $this->create_file($jsfile); 97 $this->create_file($cssfile); 98 foreach ($langs as $lang => $value) { 99 $jsonfile = $uploaddirectory . '/' . 'language/' . $lang . '.json'; 100 $this->create_file($jsonfile, $value); 101 } 102 103 $lib = [ 104 'title' => 'Test lib', 105 'description' => 'Test library description', 106 'majorVersion' => $majorversion, 107 'minorVersion' => $minorversion, 108 'patchVersion' => 2, 109 'machineName' => $machinename, 110 'preloadedJs' => [ 111 [ 112 'path' => 'scripts' . '/' . 'testlib.min.js' 113 ] 114 ], 115 'preloadedCss' => [ 116 [ 117 'path' => 'styles' . '/' . 'testlib.min.css' 118 ] 119 ], 120 'uploadDirectory' => $uploaddirectory, 121 'libraryId' => $libraryid 122 ]; 123 124 $version = "{$majorversion}.{$minorversion}.2"; 125 $libname = "{$machinename}-{$majorversion}.{$minorversion}"; 126 $path = '/' . 'libraries' . '/' . $libraryid . '/' . $libname . '/' . 'scripts' . '/' . 'testlib.min.js'; 127 $this->add_libfile_to_array('scripts', $path, $version, $files); 128 $path = '/' . 'libraries' . '/' . $libraryid .'/' . $libname . '/' . 'styles' . '/' . 'testlib.min.css'; 129 $this->add_libfile_to_array('styles', $path, $version, $files); 130 foreach ($langs as $lang => $notused) { 131 $path = '/' . 'libraries' . '/' . $libraryid . '/' . $libname . '/' . 'language' . '/' . $lang . '.json'; 132 $this->add_libfile_to_array('language', $path, $version, $files); 133 } 134 135 return [$lib, $files]; 136 } 137 138 /** 139 * Save the library files on the filesystem. 140 * 141 * @param stdClss $lib The library data 142 */ 143 private function save_library(stdClass $lib) { 144 // Get a temp path. 145 $filestorage = new \core_h5p\file_storage(); 146 $temppath = $filestorage->getTmpPath(); 147 148 // Create and save the library files on the filesystem. 149 $basedirectorymain = $temppath . '/' . $lib->machinename . '-' . 150 $lib->majorversion . '.' . $lib->minorversion; 151 152 list($library, $libraryfiles) = $this->create_library($basedirectorymain, $lib->id, $lib->machinename, 153 $lib->majorversion, $lib->minorversion); 154 155 $filestorage->saveLibrary($library); 156 } 157 158 /** 159 * Populate H5P database tables with relevant data to simulate the process of adding H5P content. 160 * 161 * @param bool $createlibraryfiles Whether to create and store library files on the filesystem 162 * @param array|null $filerecord The file associated to the H5P entry. 163 * @return stdClass An object representing the added H5P records 164 */ 165 public function generate_h5p_data(bool $createlibraryfiles = false, ?array $filerecord = null): stdClass { 166 // Create libraries. 167 $mainlib = $libraries[] = $this->create_library_record('MainLibrary', 'Main Lib', 1, 0, 1, '', null, 168 'http://tutorial.org', 'http://example.org'); 169 $lib1 = $libraries[] = $this->create_library_record('Library1', 'Lib1', 2, 0, 1, '', null, null, 'http://example.org'); 170 $lib2 = $libraries[] = $this->create_library_record('Library2', 'Lib2', 2, 1, 1, '', null, 'http://tutorial.org'); 171 $lib3 = $libraries[] = $this->create_library_record('Library3', 'Lib3', 3, 2, 1, '', null, null, null, true, 0); 172 $lib4 = $libraries[] = $this->create_library_record('Library4', 'Lib4', 1, 1); 173 $lib5 = $libraries[] = $this->create_library_record('Library5', 'Lib5', 1, 3); 174 175 if ($createlibraryfiles) { 176 foreach ($libraries as $lib) { 177 // Create and save the library files on the filesystem. 178 $this->save_library($lib); 179 } 180 } 181 182 // Create h5p content. 183 $h5p = $this->create_h5p_record($mainlib->id, null, null, $filerecord); 184 // Create h5p content library dependencies. 185 $this->create_contents_libraries_record($h5p, $mainlib->id); 186 $this->create_contents_libraries_record($h5p, $lib1->id); 187 $this->create_contents_libraries_record($h5p, $lib2->id); 188 $this->create_contents_libraries_record($h5p, $lib3->id); 189 $this->create_contents_libraries_record($h5p, $lib4->id); 190 // Create library dependencies for $mainlib. 191 $this->create_library_dependency_record($mainlib->id, $lib1->id); 192 $this->create_library_dependency_record($mainlib->id, $lib2->id); 193 $this->create_library_dependency_record($mainlib->id, $lib3->id); 194 // Create library dependencies for $lib1. 195 $this->create_library_dependency_record($lib1->id, $lib2->id); 196 $this->create_library_dependency_record($lib1->id, $lib3->id); 197 $this->create_library_dependency_record($lib1->id, $lib4->id); 198 // Create library dependencies for $lib3. 199 $this->create_library_dependency_record($lib3->id, $lib5->id); 200 201 return (object) [ 202 'h5pcontent' => (object) array( 203 'h5pid' => $h5p, 204 'contentdependencies' => array($mainlib, $lib1, $lib2, $lib3, $lib4) 205 ), 206 'mainlib' => (object) array( 207 'data' => $mainlib, 208 'dependencies' => array($lib1, $lib2, $lib3) 209 ), 210 'lib1' => (object) array( 211 'data' => $lib1, 212 'dependencies' => array($lib2, $lib3, $lib4) 213 ), 214 'lib2' => (object) array( 215 'data' => $lib2, 216 'dependencies' => array() 217 ), 218 'lib3' => (object) array( 219 'data' => $lib3, 220 'dependencies' => array($lib5) 221 ), 222 'lib4' => (object) array( 223 'data' => $lib4, 224 'dependencies' => array() 225 ), 226 'lib5' => (object) array( 227 'data' => $lib5, 228 'dependencies' => array() 229 ), 230 ]; 231 } 232 233 /** 234 * Create a record in the h5p_libraries database table. 235 * 236 * @param string $machinename The library machine name 237 * @param string $title The library's name 238 * @param int $majorversion The library's major version 239 * @param int $minorversion The library's minor version 240 * @param int $patchversion The library's patch version 241 * @param string $semantics Json describing the content structure for the library 242 * @param string $addto The plugin configuration data 243 * @param string $tutorial The tutorial URL 244 * @param string $examlpe The example URL 245 * @param bool $enabled Whether the library is enabled or not 246 * @param int $runnable Whether the library is runnable (1) or not (0) 247 * @return stdClass An object representing the added library record 248 */ 249 public function create_library_record(string $machinename, string $title, int $majorversion = 1, 250 int $minorversion = 0, int $patchversion = 1, string $semantics = '', string $addto = null, 251 string $tutorial = null, string $example = null, bool $enabled = true, int $runnable = 1): stdClass { 252 global $DB; 253 254 $content = [ 255 'machinename' => $machinename, 256 'title' => $title, 257 'majorversion' => $majorversion, 258 'minorversion' => $minorversion, 259 'patchversion' => $patchversion, 260 'runnable' => $runnable, 261 'fullscreen' => 1, 262 'preloadedjs' => 'js/example.js', 263 'preloadedcss' => 'css/example.css', 264 'droplibrarycss' => '', 265 'semantics' => $semantics, 266 'addto' => $addto, 267 'tutorial' => $tutorial, 268 'example' => $example, 269 'enabled' => $enabled, 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 * @param array|null $filerecord The file associated to the H5P entry. 284 * @return int The ID of the added record 285 */ 286 public function create_h5p_record(int $mainlibid, string $jsoncontent = null, string $filtered = null, 287 ?array $filerecord = null): int { 288 global $DB; 289 290 if (!$jsoncontent) { 291 $jsoncontent = json_encode( 292 array( 293 'text' => '<p>Dummy text<\/p>\n', 294 'questions' => '<p>Test question<\/p>\n' 295 ) 296 ); 297 } 298 299 if (!$filtered) { 300 $filtered = json_encode( 301 array( 302 'text' => 'Dummy text', 303 'questions' => 'Test question' 304 ) 305 ); 306 } 307 308 // Load the H5P file into DB. 309 $pathnamehash = sha1('pathname'); 310 $contenthash = sha1('content'); 311 if ($filerecord) { 312 $fs = get_file_storage(); 313 if (!$fs->get_file( 314 $filerecord['contextid'], 315 $filerecord['component'], 316 $filerecord['filearea'], 317 $filerecord['itemid'], 318 $filerecord['filepath'], 319 $filerecord['filename'])) { 320 $file = $fs->create_file_from_string($filerecord, $jsoncontent); 321 $pathnamehash = $file->get_pathnamehash(); 322 $contenthash = $file->get_contenthash(); 323 if (array_key_exists('addxapistate', $filerecord) && $filerecord['addxapistate']) { 324 // Save some xAPI state associated to this H5P content. 325 $params = [ 326 'component' => $filerecord['component'], 327 'activity' => item_activity::create_from_id($filerecord['contextid']), 328 ]; 329 global $CFG; 330 require_once($CFG->dirroot.'/lib/xapi/tests/helper.php'); 331 \core_xapi\test_helper::create_state($params, true); 332 } 333 } 334 } 335 336 return $DB->insert_record( 337 'h5p', 338 [ 339 'jsoncontent' => $jsoncontent, 340 'displayoptions' => 8, 341 'mainlibraryid' => $mainlibid, 342 'timecreated' => time(), 343 'timemodified' => time(), 344 'filtered' => $filtered, 345 'pathnamehash' => $pathnamehash, 346 'contenthash' => $contenthash, 347 ] 348 ); 349 } 350 351 /** 352 * Create a record in the h5p_contents_libraries database table. 353 * 354 * @param string $h5pid The ID of the H5P content 355 * @param int $libid The ID of the library 356 * @param string $dependencytype The dependency type 357 * @return int The ID of the added record 358 */ 359 public function create_contents_libraries_record(string $h5pid, int $libid, 360 string $dependencytype = 'preloaded'): int { 361 global $DB; 362 363 return $DB->insert_record( 364 'h5p_contents_libraries', 365 array( 366 'h5pid' => $h5pid, 367 'libraryid' => $libid, 368 'dependencytype' => $dependencytype, 369 'dropcss' => 0, 370 'weight' => 1 371 ) 372 ); 373 } 374 375 /** 376 * Create a record in the h5p_library_dependencies database table. 377 * 378 * @param int $libid The ID of the library 379 * @param int $requiredlibid The ID of the required library 380 * @param string $dependencytype The dependency type 381 * @return int The ID of the added record 382 */ 383 public function create_library_dependency_record(int $libid, int $requiredlibid, 384 string $dependencytype = 'preloaded'): int { 385 global $DB; 386 387 return $DB->insert_record( 388 'h5p_library_dependencies', 389 array( 390 'libraryid' => $libid, 391 'requiredlibraryid' => $requiredlibid, 392 'dependencytype' => $dependencytype 393 ) 394 ); 395 } 396 397 /** 398 * Create H5P content type records in the h5p_libraries database table. 399 * 400 * @param array $typestonotinstall H5P content types that should not be installed 401 * @param core $core h5p_test_core instance required to use the exttests URL 402 * @return array Data of the content types not installed. 403 * 404 * @throws invalid_response_exception If request to get the latest content types fails (usually due to a transient error) 405 */ 406 public function create_content_types(array $typestonotinstall, core $core): array { 407 global $DB; 408 409 autoloader::register(); 410 411 // Get info of latest content types versions. 412 $response = $core->get_latest_content_types(); 413 if (!empty($response->error)) { 414 throw new invalid_response_exception($response->error); 415 } 416 417 $installedtypes = 0; 418 419 // Fake installation of all other H5P content types. 420 foreach ($response->contentTypes as $contenttype) { 421 // Don't install pending content types. 422 if (in_array($contenttype->id, $typestonotinstall)) { 423 continue; 424 } 425 $library = [ 426 'machinename' => $contenttype->id, 427 'majorversion' => $contenttype->version->major, 428 'minorversion' => $contenttype->version->minor, 429 'patchversion' => $contenttype->version->patch, 430 'runnable' => 1, 431 'coremajor' => $contenttype->coreApiVersionNeeded->major, 432 'coreminor' => $contenttype->coreApiVersionNeeded->minor 433 ]; 434 $DB->insert_record('h5p_libraries', (object) $library); 435 $installedtypes++; 436 } 437 438 return [$installedtypes, count($typestonotinstall)]; 439 } 440 441 /** 442 * Add a record on files table for a file that belongs to 443 * 444 * @param string $file File name and path inside the filearea. 445 * @param string $filearea The filearea in which the file is ("editor" or "content"). 446 * @param int $contentid Id of the H5P content to which the file belongs. null if the file is in the editor. 447 * 448 * @return stored_file; 449 * @throws coding_exception 450 */ 451 public function create_content_file(string $file, string $filearea, int $contentid = 0): stored_file { 452 global $USER; 453 454 $filepath = '/'.dirname($file).'/'; 455 $filename = basename($file); 456 457 if (($filearea === 'content') && ($contentid == 0)) { 458 throw new coding_exception('Files belonging to an H5P content must specify the H5P content id'); 459 } 460 461 if ($filearea === 'draft') { 462 $usercontext = \context_user::instance($USER->id); 463 $context = $usercontext->id; 464 $component = 'user'; 465 $itemid = 0; 466 } else { 467 $systemcontext = context_system::instance(); 468 $context = $systemcontext->id; 469 $component = \core_h5p\file_storage::COMPONENT; 470 $itemid = $contentid; 471 } 472 473 $content = 'fake content'; 474 475 $filerecord = array( 476 'contextid' => $context, 477 'component' => $component, 478 'filearea' => $filearea, 479 'itemid' => $itemid, 480 'filepath' => $filepath, 481 'filename' => $filename, 482 ); 483 484 $fs = new file_storage(); 485 return $fs->create_file_from_string($filerecord, $content); 486 } 487 488 /** 489 * Create a fake export H5P deployed file. 490 * 491 * @param string $filename Name of the H5P file to deploy. 492 * @param int $contextid Context id of the H5P activity. 493 * @param string $component component. 494 * @param string $filearea file area. 495 * @param int $typeurl Type of url to create the export url plugin file. 496 * @return array return deployed file information. 497 */ 498 public function create_export_file(string $filename, int $contextid, 499 string $component, 500 string $filearea, 501 int $typeurl = self::WSPLUGINFILE): array { 502 global $CFG; 503 504 // We need the autoloader for H5P player. 505 autoloader::register(); 506 507 $path = $CFG->dirroot.'/h5p/tests/fixtures/'.$filename; 508 $filerecord = [ 509 'contextid' => $contextid, 510 'component' => $component, 511 'filearea' => $filearea, 512 'itemid' => 0, 513 'filepath' => '/', 514 'filename' => $filename, 515 ]; 516 // Load the h5p file into DB. 517 $fs = get_file_storage(); 518 if (!$fs->get_file($contextid, $component, $filearea, $filerecord['itemid'], $filerecord['filepath'], $filename)) { 519 $fs->create_file_from_pathname($filerecord, $path); 520 } 521 522 // Make the URL to pass to the player. 523 if ($typeurl == self::WSPLUGINFILE) { 524 $url = \moodle_url::make_webservice_pluginfile_url( 525 $filerecord['contextid'], 526 $filerecord['component'], 527 $filerecord['filearea'], 528 $filerecord['itemid'], 529 $filerecord['filepath'], 530 $filerecord['filename'] 531 ); 532 } else { 533 $includetoken = false; 534 if ($typeurl == self::TOKENPLUGINFILE) { 535 $includetoken = true; 536 } 537 $url = \moodle_url::make_pluginfile_url( 538 $filerecord['contextid'], 539 $filerecord['component'], 540 $filerecord['filearea'], 541 $filerecord['itemid'], 542 $filerecord['filepath'], 543 $filerecord['filename'], 544 false, 545 $includetoken 546 ); 547 } 548 549 $config = new stdClass(); 550 $h5pplayer = new player($url->out(false), $config); 551 // We need to add assets to page to create the export file. 552 $h5pplayer->add_assets_to_page(); 553 554 // Call the method. We need the id of the new H5P content. 555 $rc = new \ReflectionClass(player::class); 556 $rcp = $rc->getProperty('h5pid'); 557 $rcp->setAccessible(true); 558 $h5pid = $rcp->getValue($h5pplayer); 559 560 // Get the info export file. 561 $factory = new factory(); 562 $core = $factory->get_core(); 563 $content = $core->loadContent($h5pid); 564 $slug = $content['slug'] ? $content['slug'] . '-' : ''; 565 $exportfilename = "{$slug}{$h5pid}.h5p"; 566 $fileh5p = $core->fs->get_export_file($exportfilename); 567 $deployedfile = []; 568 $deployedfile['filename'] = $fileh5p->get_filename(); 569 $deployedfile['filepath'] = $fileh5p->get_filepath(); 570 $deployedfile['mimetype'] = $fileh5p->get_mimetype(); 571 $deployedfile['filesize'] = $fileh5p->get_filesize(); 572 $deployedfile['timemodified'] = $fileh5p->get_timemodified(); 573 574 // Create the url depending the request was made through typeurl. 575 if ($typeurl == self::WSPLUGINFILE) { 576 $url = \moodle_url::make_webservice_pluginfile_url( 577 $fileh5p->get_contextid(), 578 $fileh5p->get_component(), 579 $fileh5p->get_filearea(), 580 '', 581 '', 582 $fileh5p->get_filename() 583 ); 584 } else { 585 $includetoken = false; 586 if ($typeurl == self::TOKENPLUGINFILE) { 587 $includetoken = true; 588 } 589 $url = \moodle_url::make_pluginfile_url( 590 $fileh5p->get_contextid(), 591 $fileh5p->get_component(), 592 $fileh5p->get_filearea(), 593 '', 594 '', 595 $fileh5p->get_filename(), 596 false, 597 $includetoken 598 ); 599 } 600 $deployedfile['fileurl'] = $url->out(false); 601 602 return $deployedfile; 603 } 604 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body