Merge branch 'MDL-75244-311' of https://github.com/andrewnicols/moodle into MOODLE_31...
[moodle.git] / h5p / classes / player.php
blobbbd523e7bc2ab720ce35c1b88d76ff73f3074947
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.
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 * H5P player class.
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
25 namespace core_h5p;
27 defined('MOODLE_INTERNAL') || die();
29 use core_h5p\local\library\autoloader;
30 use core_xapi\local\statement\item_activity;
32 /**
33 * H5P player class, for displaying any local H5P content.
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
39 class player {
41 /**
42 * @var string The local H5P URL containing the .h5p file to display.
44 private $url;
46 /**
47 * @var core The H5PCore object.
49 private $core;
51 /**
52 * @var int H5P DB id.
54 private $h5pid;
56 /**
57 * @var array JavaScript requirements for this H5P.
59 private $jsrequires = [];
61 /**
62 * @var array CSS requirements for this H5P.
64 private $cssrequires = [];
66 /**
67 * @var array H5P content to display.
69 private $content;
71 /**
72 * @var string optional component name to send xAPI statements.
74 private $component;
76 /**
77 * @var string Type of embed object, div or iframe.
79 private $embedtype;
81 /**
82 * @var context The context object where the .h5p belongs.
84 private $context;
86 /**
87 * @var factory The \core_h5p\factory object.
89 private $factory;
91 /**
92 * @var stdClass The error, exception and info messages, raised while preparing and running the player.
94 private $messages;
96 /**
97 * @var bool Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions.
99 private $preventredirect;
102 * Inits the H5P player for rendering the content.
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.
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');
116 $this->url = new \moodle_url($url);
117 $this->preventredirect = $preventredirect;
119 $this->factory = new \core_h5p\factory();
121 $this->messages = new \stdClass();
123 $this->component = $component;
125 // Create \core_h5p\core instance.
126 $this->core = $this->factory->get_core();
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
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);
143 // Get the embedtype to use for displaying the H5P content.
144 $this->embedtype = core::determineEmbedType($this->content['embedType'], $this->content['library']['embedTypes']);
150 * Get the encoded URL for embeding this H5P content.
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
157 * @return string The embedable code to display a H5P file.
159 public static function display(string $url, \stdClass $config, bool $preventredirect = true,
160 string $component = ''): string {
161 global $OUTPUT;
162 $params = [
163 'url' => $url,
164 'preventredirect' => $preventredirect,
165 'component' => $component,
168 $optparams = ['frame', 'export', 'embed', 'copyright'];
169 foreach ($optparams as $optparam) {
170 if (!empty($config->$optparam)) {
171 $params[$optparam] = $config->$optparam;
174 $fileurl = new \moodle_url('/h5p/embed.php', $params);
176 $template = new \stdClass();
177 $template->embedurl = $fileurl->out(false);
179 $result = $OUTPUT->render_from_template('core_h5p/h5pembed', $template);
180 $result .= self::get_resize_code();
181 return $result;
185 * Get the error messages stored in our H5P framework.
187 * @return stdClass with framework error messages.
189 public function get_messages(): \stdClass {
190 return helper::get_messages($this->messages, $this->factory);
194 * Create the H5PIntegration variable that will be included in the page. This variable is used as the
195 * main H5P config variable.
197 public function add_assets_to_page() {
198 global $PAGE;
200 $cid = $this->get_cid();
201 $systemcontext = \context_system::instance();
203 $disable = array_key_exists('disable', $this->content) ? $this->content['disable'] : core::DISABLE_NONE;
204 $displayoptions = $this->core->getDisplayOptionsForView($disable, $this->h5pid);
206 $contenturl = \moodle_url::make_pluginfile_url($systemcontext->id, \core_h5p\file_storage::COMPONENT,
207 \core_h5p\file_storage::CONTENT_FILEAREA, $this->h5pid, null, null);
208 $exporturl = $this->get_export_settings($displayoptions[ core::DISPLAY_OPTION_DOWNLOAD ]);
209 $xapiobject = item_activity::create_from_id($this->context->id);
210 $contentsettings = [
211 'library' => core::libraryToString($this->content['library']),
212 'fullScreen' => $this->content['library']['fullscreen'],
213 'exportUrl' => ($exporturl instanceof \moodle_url) ? $exporturl->out(false) : '',
214 'embedCode' => $this->get_embed_code($this->url->out(),
215 $displayoptions[ core::DISPLAY_OPTION_EMBED ]),
216 'resizeCode' => self::get_resize_code(),
217 'title' => $this->content['slug'],
218 'displayOptions' => $displayoptions,
219 'url' => $xapiobject->get_data()->id,
220 'contentUrl' => $contenturl->out(),
221 'metadata' => $this->content['metadata'],
222 'contentUserData' => [0 => ['state' => '{}']]
224 // Get the core H5P assets, needed by the H5P classes to render the H5P content.
225 $settings = $this->get_assets();
226 $settings['contents'][$cid] = array_merge($settings['contents'][$cid], $contentsettings);
228 // Print JavaScript settings to page.
229 $PAGE->requires->data_for_js('H5PIntegration', $settings, true);
233 * Outputs H5P wrapper HTML.
235 * @return string The HTML code to display this H5P content.
237 public function output(): string {
238 global $OUTPUT, $USER;
240 $template = new \stdClass();
241 $template->h5pid = $this->h5pid;
242 if ($this->embedtype === 'div') {
243 $h5phtml = $OUTPUT->render_from_template('core_h5p/h5pdiv', $template);
244 } else {
245 $h5phtml = $OUTPUT->render_from_template('core_h5p/h5piframe', $template);
248 // Trigger capability_assigned event.
249 \core_h5p\event\h5p_viewed::create([
250 'objectid' => $this->h5pid,
251 'userid' => $USER->id,
252 'context' => $this->get_context(),
253 'other' => [
254 'url' => $this->url->out(),
255 'time' => time()
257 ])->trigger();
259 return $h5phtml;
263 * Get the title of the H5P content to display.
265 * @return string the title
267 public function get_title(): string {
268 return $this->content['title'];
272 * Get the context where the .h5p file belongs.
274 * @return context The context.
276 public function get_context(): \context {
277 return $this->context;
281 * Delete an H5P package.
283 * @param stdClass $content The H5P package to delete.
285 private function delete_h5p(\stdClass $content) {
286 $h5pstorage = $this->factory->get_storage();
287 // Add an empty slug to the content if it's not defined, because the H5P library requires this field exists.
288 // It's not used when deleting a package, so the real slug value is not required at this point.
289 $content->slug = $content->slug ?? '';
290 $h5pstorage->deletePackage( (array) $content);
294 * Export path for settings
296 * @param bool $downloadenabled Whether the option to export the H5P content is enabled.
298 * @return \moodle_url|null The URL of the exported file.
300 private function get_export_settings(bool $downloadenabled): ?\moodle_url {
302 if (!$downloadenabled) {
303 return null;
306 $systemcontext = \context_system::instance();
307 $slug = $this->content['slug'] ? $this->content['slug'] . '-' : '';
308 // We have to build the right URL.
309 // Depending the request was made through webservice/pluginfile.php or pluginfile.php.
310 if (strpos($this->url, '/webservice/pluginfile.php')) {
311 $url = \moodle_url::make_webservice_pluginfile_url(
312 $systemcontext->id,
313 \core_h5p\file_storage::COMPONENT,
314 \core_h5p\file_storage::EXPORT_FILEAREA,
317 "{$slug}{$this->content['id']}.h5p"
319 } else {
320 // If the request is made by tokenpluginfile.php we need to indicates to generate a token for current user.
321 $includetoken = false;
322 if (strpos($this->url, '/tokenpluginfile.php')) {
323 $includetoken = true;
325 $url = \moodle_url::make_pluginfile_url(
326 $systemcontext->id,
327 \core_h5p\file_storage::COMPONENT,
328 \core_h5p\file_storage::EXPORT_FILEAREA,
331 "{$slug}{$this->content['id']}.h5p",
332 false,
333 $includetoken
337 return $url;
341 * Get the identifier for the H5P content, to be used in the arrays as index.
343 * @return string The identifier.
345 private function get_cid(): string {
346 return 'cid-' . $this->h5pid;
350 * Get the core H5P assets, including all core H5P JavaScript and CSS.
352 * @return Array core H5P assets.
354 private function get_assets(): array {
355 // Get core assets.
356 $settings = helper::get_core_assets();
357 // Added here because in the helper we don't have the h5p content id.
358 $settings['moodleLibraryPaths'] = $this->core->get_dependency_roots($this->h5pid);
359 // Add also the Moodle component where the results will be tracked.
360 $settings['moodleComponent'] = $this->component;
361 if (!empty($settings['moodleComponent'])) {
362 $settings['reportingIsEnabled'] = true;
365 $cid = $this->get_cid();
366 // The filterParameters function should be called before getting the dependencyfiles because it rebuild content
367 // dependency cache and export file.
368 $settings['contents'][$cid]['jsonContent'] = $this->get_filtered_parameters();
370 $files = $this->get_dependency_files();
371 if ($this->embedtype === 'div') {
372 $systemcontext = \context_system::instance();
373 $h5ppath = "/pluginfile.php/{$systemcontext->id}/core_h5p";
375 // Schedule JavaScripts for loading through Moodle.
376 foreach ($files['scripts'] as $script) {
377 $url = $script->path . $script->version;
379 // Add URL prefix if not external.
380 $isexternal = strpos($script->path, '://');
381 if ($isexternal === false) {
382 $url = $h5ppath . $url;
384 $settings['loadedJs'][] = $url;
385 $this->jsrequires[] = new \moodle_url($isexternal ? $url : $CFG->wwwroot . $url);
388 // Schedule stylesheets for loading through Moodle.
389 foreach ($files['styles'] as $style) {
390 $url = $style->path . $style->version;
392 // Add URL prefix if not external.
393 $isexternal = strpos($style->path, '://');
394 if ($isexternal === false) {
395 $url = $h5ppath . $url;
397 $settings['loadedCss'][] = $url;
398 $this->cssrequires[] = new \moodle_url($isexternal ? $url : $CFG->wwwroot . $url);
401 } else {
402 // JavaScripts and stylesheets will be loaded through h5p.js.
403 $settings['contents'][$cid]['scripts'] = $this->core->getAssetsUrls($files['scripts']);
404 $settings['contents'][$cid]['styles'] = $this->core->getAssetsUrls($files['styles']);
406 return $settings;
410 * Get filtered parameters, modifying them by the renderer if the theme implements the h5p_alter_filtered_parameters function.
412 * @return string Filtered parameters.
414 private function get_filtered_parameters(): string {
415 global $PAGE;
417 $safeparams = $this->core->filterParameters($this->content);
418 $decodedparams = json_decode($safeparams);
419 $h5poutput = $PAGE->get_renderer('core_h5p');
420 $h5poutput->h5p_alter_filtered_parameters(
421 $decodedparams,
422 $this->content['library']['name'],
423 $this->content['library']['majorVersion'],
424 $this->content['library']['minorVersion']
426 $safeparams = json_encode($decodedparams);
428 return $safeparams;
432 * Finds library dependencies of view
434 * @return array Files that the view has dependencies to
436 private function get_dependency_files(): array {
437 global $PAGE;
439 $preloadeddeps = $this->core->loadContentDependencies($this->h5pid, 'preloaded');
440 $files = $this->core->getDependenciesFiles($preloadeddeps);
442 // Add additional asset files if required.
443 $h5poutput = $PAGE->get_renderer('core_h5p');
444 $h5poutput->h5p_alter_scripts($files['scripts'], $preloadeddeps, $this->embedtype);
445 $h5poutput->h5p_alter_styles($files['styles'], $preloadeddeps, $this->embedtype);
447 return $files;
451 * Resizing script for settings
453 * @return string The HTML code with the resize script.
455 private static function get_resize_code(): string {
456 global $OUTPUT;
458 $template = new \stdClass();
459 $template->resizeurl = autoloader::get_h5p_core_library_url('js/h5p-resizer.js');
461 return $OUTPUT->render_from_template('core_h5p/h5presize', $template);
465 * Embed code for settings
467 * @param string $url The URL of the .h5p file.
468 * @param bool $embedenabled Whether the option to embed the H5P content is enabled.
470 * @return string The HTML code to reuse this H5P content in a different place.
472 private function get_embed_code(string $url, bool $embedenabled): string {
473 global $OUTPUT;
475 if ( ! $embedenabled) {
476 return '';
479 $template = new \stdClass();
480 $template->embedurl = self::get_embed_url($url, $this->component)->out(false);
482 return $OUTPUT->render_from_template('core_h5p/h5pembed', $template);
486 * Get the encoded URL for embeding this H5P content.
487 * @param string $url The URL of the .h5p file.
488 * @param string $component optional Moodle component to send xAPI tracking
490 * @return \moodle_url The embed URL.
492 public static function get_embed_url(string $url, string $component = ''): \moodle_url {
493 $params = ['url' => $url];
494 if (!empty($component)) {
495 // If component is not empty, it will be passed too, in order to allow tracking too.
496 $params['component'] = $component;
499 return new \moodle_url('/h5p/embed.php', $params);
503 * Return the info export file for Mobile App.
505 * @return array
507 public function get_export_file(): array {
508 // Get the export url.
509 $exporturl = $this->get_export_settings(true);
510 // Get the filename of the export url.
511 $path = $exporturl->out_as_local_url();
512 $parts = explode('/', $path);
513 $filename = array_pop($parts);
514 // Get the required info from the export file to be able to get the export file by third apps.
515 $file = helper::get_export_info($filename, $exporturl);
517 return $file;