See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 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 * H5P player class. 19 * 20 * @package core_h5p 21 * @copyright 2019 Sara Arjona <sara@moodle.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace core_h5p; 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 use core_h5p\local\library\autoloader; 30 use core_xapi\local\statement\item_activity; 31 32 /** 33 * H5P player class, for displaying any local H5P content. 34 * 35 * @package core_h5p 36 * @copyright 2019 Sara Arjona <sara@moodle.com> 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class player { 40 41 /** 42 * @var string The local H5P URL containing the .h5p file to display. 43 */ 44 private $url; 45 46 /** 47 * @var core The H5PCore object. 48 */ 49 private $core; 50 51 /** 52 * @var int H5P DB id. 53 */ 54 private $h5pid; 55 56 /** 57 * @var array JavaScript requirements for this H5P. 58 */ 59 private $jsrequires = []; 60 61 /** 62 * @var array CSS requirements for this H5P. 63 */ 64 private $cssrequires = []; 65 66 /** 67 * @var array H5P content to display. 68 */ 69 private $content; 70 71 /** 72 * @var string optional component name to send xAPI statements. 73 */ 74 private $component; 75 76 /** 77 * @var string Type of embed object, div or iframe. 78 */ 79 private $embedtype; 80 81 /** 82 * @var context The context object where the .h5p belongs. 83 */ 84 private $context; 85 86 /** 87 * @var factory The \core_h5p\factory object. 88 */ 89 private $factory; 90 91 /** 92 * @var stdClass The error, exception and info messages, raised while preparing and running the player. 93 */ 94 private $messages; 95 96 /** 97 * @var bool Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions. 98 */ 99 private $preventredirect; 100 101 /** 102 * Inits the H5P player for rendering the content. 103 * 104 * @param string $url Local URL of the H5P file to display. 105 * @param stdClass $config Configuration for H5P buttons. 106 * @param bool $preventredirect Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions 107 * @param string $component optional moodle component to sent xAPI tracking 108 * @param bool $skipcapcheck Whether capabilities should be checked or not to get the pluginfile URL because sometimes they 109 * might be controlled before calling this method. 110 */ 111 public function __construct(string $url, \stdClass $config, bool $preventredirect = true, string $component = '', 112 bool $skipcapcheck = false) { 113 if (empty($url)) { 114 throw new \moodle_exception('h5pinvalidurl', 'core_h5p'); 115 } 116 $this->url = new \moodle_url($url); 117 $this->preventredirect = $preventredirect; 118 119 $this->factory = new \core_h5p\factory(); 120 121 $this->messages = new \stdClass(); 122 123 $this->component = $component; 124 125 // Create \core_h5p\core instance. 126 $this->core = $this->factory->get_core(); 127 128 // Get the H5P identifier linked to this URL. 129 list($file, $this->h5pid) = api::create_content_from_pluginfile_url( 130 $url, 131 $config, 132 $this->factory, 133 $this->messages, 134 $this->preventredirect, 135 $skipcapcheck 136 ); 137 if ($file) { 138 $this->context = \context::instance_by_id($file->get_contextid()); 139 if ($this->h5pid) { 140 // Load the content of the H5P content associated to this $url. 141 $this->content = $this->core->loadContent($this->h5pid); 142 143 // Get the embedtype to use for displaying the H5P content. 144 $this->embedtype = core::determineEmbedType($this->content['embedType'], $this->content['library']['embedTypes']); 145 } 146 } 147 } 148 149 /** 150 * Get the encoded URL for embeding this H5P content. 151 * 152 * @param string $url Local URL of the H5P file to display. 153 * @param stdClass $config Configuration for H5P buttons. 154 * @param bool $preventredirect Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions 155 * @param string $component optional moodle component to sent xAPI tracking 156 * @param bool $displayedit Whether the edit button should be displayed below the H5P content. 157 * 158 * @return string The embedable code to display a H5P file. 159 */ 160 public static function display(string $url, \stdClass $config, bool $preventredirect = true, 161 string $component = '', bool $displayedit = false): string { 162 global $OUTPUT, $CFG; 163 164 $params = [ 165 'url' => $url, 166 'preventredirect' => $preventredirect, 167 'component' => $component, 168 ]; 169 170 $optparams = ['frame', 'export', 'embed', 'copyright']; 171 foreach ($optparams as $optparam) { 172 if (!empty($config->$optparam)) { 173 $params[$optparam] = $config->$optparam; 174 } 175 } 176 $fileurl = new \moodle_url('/h5p/embed.php', $params); 177 178 $template = new \stdClass(); 179 $template->embedurl = $fileurl->out(false); 180 181 if ($displayedit) { 182 list($originalfile, $h5p) = api::get_original_content_from_pluginfile_url($url, $preventredirect, true); 183 if ($originalfile) { 184 // Check if the user can edit this content. 185 if (api::can_edit_content($originalfile)) { 186 $template->editurl = $CFG->wwwroot . '/h5p/edit.php?url=' . $url; 187 } 188 } 189 } 190 191 $result = $OUTPUT->render_from_template('core_h5p/h5pembed', $template); 192 $result .= self::get_resize_code(); 193 return $result; 194 } 195 196 /** 197 * Get the error messages stored in our H5P framework. 198 * 199 * @return stdClass with framework error messages. 200 */ 201 public function get_messages(): \stdClass { 202 return helper::get_messages($this->messages, $this->factory); 203 } 204 205 /** 206 * Create the H5PIntegration variable that will be included in the page. This variable is used as the 207 * main H5P config variable. 208 */ 209 public function add_assets_to_page() { 210 global $PAGE; 211 212 $cid = $this->get_cid(); 213 $systemcontext = \context_system::instance(); 214 215 $disable = array_key_exists('disable', $this->content) ? $this->content['disable'] : core::DISABLE_NONE; 216 $displayoptions = $this->core->getDisplayOptionsForView($disable, $this->h5pid); 217 218 $contenturl = \moodle_url::make_pluginfile_url($systemcontext->id, \core_h5p\file_storage::COMPONENT, 219 \core_h5p\file_storage::CONTENT_FILEAREA, $this->h5pid, null, null); 220 $exporturl = $this->get_export_settings($displayoptions[ core::DISPLAY_OPTION_DOWNLOAD ]); 221 $xapiobject = item_activity::create_from_id($this->context->id); 222 $contentsettings = [ 223 'library' => core::libraryToString($this->content['library']), 224 'fullScreen' => $this->content['library']['fullscreen'], 225 'exportUrl' => ($exporturl instanceof \moodle_url) ? $exporturl->out(false) : '', 226 'embedCode' => $this->get_embed_code($this->url->out(), 227 $displayoptions[ core::DISPLAY_OPTION_EMBED ]), 228 'resizeCode' => self::get_resize_code(), 229 'title' => $this->content['slug'], 230 'displayOptions' => $displayoptions, 231 'url' => $xapiobject->get_data()->id, 232 'contentUrl' => $contenturl->out(), 233 'metadata' => $this->content['metadata'], 234 'contentUserData' => [0 => ['state' => '{}']] 235 ]; 236 // Get the core H5P assets, needed by the H5P classes to render the H5P content. 237 $settings = $this->get_assets(); 238 $settings['contents'][$cid] = array_merge($settings['contents'][$cid], $contentsettings); 239 240 // Print JavaScript settings to page. 241 $PAGE->requires->data_for_js('H5PIntegration', $settings, true); 242 } 243 244 /** 245 * Outputs H5P wrapper HTML. 246 * 247 * @return string The HTML code to display this H5P content. 248 */ 249 public function output(): string { 250 global $OUTPUT, $USER; 251 252 $template = new \stdClass(); 253 $template->h5pid = $this->h5pid; 254 if ($this->embedtype === 'div') { 255 $h5phtml = $OUTPUT->render_from_template('core_h5p/h5pdiv', $template); 256 } else { 257 $h5phtml = $OUTPUT->render_from_template('core_h5p/h5piframe', $template); 258 } 259 260 // Trigger capability_assigned event. 261 \core_h5p\event\h5p_viewed::create([ 262 'objectid' => $this->h5pid, 263 'userid' => $USER->id, 264 'context' => $this->get_context(), 265 'other' => [ 266 'url' => $this->url->out(), 267 'time' => time() 268 ] 269 ])->trigger(); 270 271 return $h5phtml; 272 } 273 274 /** 275 * Get the title of the H5P content to display. 276 * 277 * @return string the title 278 */ 279 public function get_title(): string { 280 return $this->content['title']; 281 } 282 283 /** 284 * Get the context where the .h5p file belongs. 285 * 286 * @return context The context. 287 */ 288 public function get_context(): \context { 289 return $this->context; 290 } 291 292 /** 293 * Delete an H5P package. 294 * 295 * @param stdClass $content The H5P package to delete. 296 */ 297 private function delete_h5p(\stdClass $content) { 298 $h5pstorage = $this->factory->get_storage(); 299 // Add an empty slug to the content if it's not defined, because the H5P library requires this field exists. 300 // It's not used when deleting a package, so the real slug value is not required at this point. 301 $content->slug = $content->slug ?? ''; 302 $h5pstorage->deletePackage( (array) $content); 303 } 304 305 /** 306 * Export path for settings 307 * 308 * @param bool $downloadenabled Whether the option to export the H5P content is enabled. 309 * 310 * @return \moodle_url|null The URL of the exported file. 311 */ 312 private function get_export_settings(bool $downloadenabled): ?\moodle_url { 313 314 if (!$downloadenabled) { 315 return null; 316 } 317 318 $systemcontext = \context_system::instance(); 319 $slug = $this->content['slug'] ? $this->content['slug'] . '-' : ''; 320 $filename = "{$slug}{$this->content['id']}.h5p"; 321 // We have to build the right URL. 322 // Depending the request was made through webservice/pluginfile.php or pluginfile.php. 323 if (strpos($this->url, '/webservice/pluginfile.php')) { 324 $url = \moodle_url::make_webservice_pluginfile_url( 325 $systemcontext->id, 326 \core_h5p\file_storage::COMPONENT, 327 \core_h5p\file_storage::EXPORT_FILEAREA, 328 '', 329 '', 330 $filename 331 ); 332 } else { 333 // If the request is made by tokenpluginfile.php we need to indicates to generate a token for current user. 334 $includetoken = false; 335 if (strpos($this->url, '/tokenpluginfile.php')) { 336 $includetoken = true; 337 } 338 $url = \moodle_url::make_pluginfile_url( 339 $systemcontext->id, 340 \core_h5p\file_storage::COMPONENT, 341 \core_h5p\file_storage::EXPORT_FILEAREA, 342 '', 343 '', 344 $filename, 345 false, 346 $includetoken 347 ); 348 } 349 350 // Get the required info from the export file to be able to get the export file by third apps. 351 $file = helper::get_export_info($filename, $url); 352 if ($file) { 353 $url->param('modified', $file['timemodified']); 354 } 355 return $url; 356 } 357 358 /** 359 * Get the identifier for the H5P content, to be used in the arrays as index. 360 * 361 * @return string The identifier. 362 */ 363 private function get_cid(): string { 364 return 'cid-' . $this->h5pid; 365 } 366 367 /** 368 * Get the core H5P assets, including all core H5P JavaScript and CSS. 369 * 370 * @return Array core H5P assets. 371 */ 372 private function get_assets(): array { 373 // Get core assets. 374 $settings = helper::get_core_assets(); 375 // Added here because in the helper we don't have the h5p content id. 376 $settings['moodleLibraryPaths'] = $this->core->get_dependency_roots($this->h5pid); 377 // Add also the Moodle component where the results will be tracked. 378 $settings['moodleComponent'] = $this->component; 379 if (!empty($settings['moodleComponent'])) { 380 $settings['reportingIsEnabled'] = true; 381 } 382 383 $cid = $this->get_cid(); 384 // The filterParameters function should be called before getting the dependencyfiles because it rebuild content 385 // dependency cache and export file. 386 $settings['contents'][$cid]['jsonContent'] = $this->get_filtered_parameters(); 387 388 $files = $this->get_dependency_files(); 389 if ($this->embedtype === 'div') { 390 $systemcontext = \context_system::instance(); 391 $h5ppath = "/pluginfile.php/{$systemcontext->id}/core_h5p"; 392 393 // Schedule JavaScripts for loading through Moodle. 394 foreach ($files['scripts'] as $script) { 395 $url = $script->path . $script->version; 396 397 // Add URL prefix if not external. 398 $isexternal = strpos($script->path, '://'); 399 if ($isexternal === false) { 400 $url = $h5ppath . $url; 401 } 402 $settings['loadedJs'][] = $url; 403 $this->jsrequires[] = new \moodle_url($isexternal ? $url : $CFG->wwwroot . $url); 404 } 405 406 // Schedule stylesheets for loading through Moodle. 407 foreach ($files['styles'] as $style) { 408 $url = $style->path . $style->version; 409 410 // Add URL prefix if not external. 411 $isexternal = strpos($style->path, '://'); 412 if ($isexternal === false) { 413 $url = $h5ppath . $url; 414 } 415 $settings['loadedCss'][] = $url; 416 $this->cssrequires[] = new \moodle_url($isexternal ? $url : $CFG->wwwroot . $url); 417 } 418 419 } else { 420 // JavaScripts and stylesheets will be loaded through h5p.js. 421 $settings['contents'][$cid]['scripts'] = $this->core->getAssetsUrls($files['scripts']); 422 $settings['contents'][$cid]['styles'] = $this->core->getAssetsUrls($files['styles']); 423 } 424 return $settings; 425 } 426 427 /** 428 * Get filtered parameters, modifying them by the renderer if the theme implements the h5p_alter_filtered_parameters function. 429 * 430 * @return string Filtered parameters. 431 */ 432 private function get_filtered_parameters(): string { 433 global $PAGE; 434 435 $safeparams = $this->core->filterParameters($this->content); 436 $decodedparams = json_decode($safeparams); 437 $h5poutput = $PAGE->get_renderer('core_h5p'); 438 $h5poutput->h5p_alter_filtered_parameters( 439 $decodedparams, 440 $this->content['library']['name'], 441 $this->content['library']['majorVersion'], 442 $this->content['library']['minorVersion'] 443 ); 444 $safeparams = json_encode($decodedparams); 445 446 return $safeparams; 447 } 448 449 /** 450 * Finds library dependencies of view 451 * 452 * @return array Files that the view has dependencies to 453 */ 454 private function get_dependency_files(): array { 455 global $PAGE; 456 457 $preloadeddeps = $this->core->loadContentDependencies($this->h5pid, 'preloaded'); 458 $files = $this->core->getDependenciesFiles($preloadeddeps); 459 460 // Add additional asset files if required. 461 $h5poutput = $PAGE->get_renderer('core_h5p'); 462 $h5poutput->h5p_alter_scripts($files['scripts'], $preloadeddeps, $this->embedtype); 463 $h5poutput->h5p_alter_styles($files['styles'], $preloadeddeps, $this->embedtype); 464 465 return $files; 466 } 467 468 /** 469 * Resizing script for settings 470 * 471 * @return string The HTML code with the resize script. 472 */ 473 private static function get_resize_code(): string { 474 global $OUTPUT; 475 476 $template = new \stdClass(); 477 $template->resizeurl = autoloader::get_h5p_core_library_url('js/h5p-resizer.js'); 478 479 return $OUTPUT->render_from_template('core_h5p/h5presize', $template); 480 } 481 482 /** 483 * Embed code for settings 484 * 485 * @param string $url The URL of the .h5p file. 486 * @param bool $embedenabled Whether the option to embed the H5P content is enabled. 487 * 488 * @return string The HTML code to reuse this H5P content in a different place. 489 */ 490 private function get_embed_code(string $url, bool $embedenabled): string { 491 global $OUTPUT; 492 493 if ( ! $embedenabled) { 494 return ''; 495 } 496 497 $template = new \stdClass(); 498 $template->embedurl = self::get_embed_url($url, $this->component)->out(false); 499 500 return $OUTPUT->render_from_template('core_h5p/h5pembed', $template); 501 } 502 503 /** 504 * Get the encoded URL for embeding this H5P content. 505 * @param string $url The URL of the .h5p file. 506 * @param string $component optional Moodle component to send xAPI tracking 507 * 508 * @return \moodle_url The embed URL. 509 */ 510 public static function get_embed_url(string $url, string $component = ''): \moodle_url { 511 $params = ['url' => $url]; 512 if (!empty($component)) { 513 // If component is not empty, it will be passed too, in order to allow tracking too. 514 $params['component'] = $component; 515 } 516 517 return new \moodle_url('/h5p/embed.php', $params); 518 } 519 520 /** 521 * Return the info export file for Mobile App. 522 * 523 * @return array or null 524 */ 525 public function get_export_file(): ?array { 526 // Get the export url. 527 $exporturl = $this->get_export_settings(true); 528 // Get the filename of the export url. 529 $path = $exporturl->out_as_local_url(); 530 // Check if the URL has parameters. 531 $parts = explode('?', $path); 532 $path = array_shift($parts); 533 $parts = explode('/', $path); 534 $filename = array_pop($parts); 535 // Get the required info from the export file to be able to get the export file by third apps. 536 $file = helper::get_export_info($filename, $exporturl); 537 538 return $file; 539 } 540 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body