Merge branch 'MDL-67786-master' of git://github.com/aanabit/moodle
[moodle.git] / h5p / classes / player.php
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/>.
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  */
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.
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 {
41     /**
42      * @var string The local H5P URL containing the .h5p file to display.
43      */
44     private $url;
46     /**
47      * @var core The H5PCore object.
48      */
49     private $core;
51     /**
52      * @var int H5P DB id.
53      */
54     private $h5pid;
56     /**
57      * @var array JavaScript requirements for this H5P.
58      */
59     private $jsrequires = [];
61     /**
62      * @var array CSS requirements for this H5P.
63      */
64     private $cssrequires = [];
66     /**
67      * @var array H5P content to display.
68      */
69     private $content;
71     /**
72      * @var string optional component name to send xAPI statements.
73      */
74     private $component;
76     /**
77      * @var string Type of embed object, div or iframe.
78      */
79     private $embedtype;
81     /**
82      * @var context The context object where the .h5p belongs.
83      */
84     private $context;
86     /**
87      * @var factory The \core_h5p\factory object.
88      */
89     private $factory;
91     /**
92      * @var stdClass The error, exception and info messages, raised while preparing and running the player.
93      */
94     private $messages;
96     /**
97      * @var bool Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions.
98      */
99     private $preventredirect;
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      */
109     public function __construct(string $url, \stdClass $config, bool $preventredirect = true, string $component = '') {
110         if (empty($url)) {
111             throw new \moodle_exception('h5pinvalidurl', 'core_h5p');
112         }
113         $this->url = new \moodle_url($url);
114         $this->preventredirect = $preventredirect;
116         $this->factory = new \core_h5p\factory();
118         $this->messages = new \stdClass();
120         $this->component = $component;
122         // Create \core_h5p\core instance.
123         $this->core = $this->factory->get_core();
125         // Get the H5P identifier linked to this URL.
126         if ($this->h5pid = $this->get_h5p_id($url, $config)) {
127             // Load the content of the H5P content associated to this $url.
128             $this->content = $this->core->loadContent($this->h5pid);
130             // Get the embedtype to use for displaying the H5P content.
131             $this->embedtype = core::determineEmbedType($this->content['embedType'], $this->content['library']['embedTypes']);
132         }
133     }
135     /**
136      * Get the encoded URL for embeding this H5P content.
137      *
138      * @param string $url Local URL of the H5P file to display.
139      * @param stdClass $config Configuration for H5P buttons.
140      * @param bool $preventredirect Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions
141      * @param string $component optional moodle component to sent xAPI tracking
142      *
143      * @return string The embedable code to display a H5P file.
144      */
145     public static function display(string $url, \stdClass $config, bool $preventredirect = true,
146             string $component = ''): string {
147         global $OUTPUT;
148         $params = [
149                 'url' => $url,
150                 'preventredirect' => $preventredirect,
151                 'component' => $component,
152             ];
154         $optparams = ['frame', 'export', 'embed', 'copyright'];
155         foreach ($optparams as $optparam) {
156             if (!empty($config->$optparam)) {
157                 $params[$optparam] = $config->$optparam;
158             }
159         }
160         $fileurl = new \moodle_url('/h5p/embed.php', $params);
162         $template = new \stdClass();
163         $template->embedurl = $fileurl->out(false);
165         $result = $OUTPUT->render_from_template('core_h5p/h5pembed', $template);
166         $result .= self::get_resize_code();
167         return $result;
168     }
170     /**
171      * Get the error messages stored in our H5P framework.
172      *
173      * @return stdClass with framework error messages.
174      */
175     public function get_messages(): \stdClass {
176         // Check if there are some errors and store them in $messages.
177         if (empty($this->messages->error)) {
178             $this->messages->error = $this->core->h5pF->getMessages('error') ?: false;
179         } else {
180             $this->messages->error = array_merge($this->messages->error, $this->core->h5pF->getMessages('error'));
181         }
183         if (empty($this->messages->info)) {
184             $this->messages->info = $this->core->h5pF->getMessages('info') ?: false;
185         } else {
186             $this->messages->info = array_merge($this->messages->info, $this->core->h5pF->getMessages('info'));
187         }
189         return $this->messages;
190     }
192     /**
193      * Create the H5PIntegration variable that will be included in the page. This variable is used as the
194      * main H5P config variable.
195      */
196     public function add_assets_to_page() {
197         global $PAGE;
199         $cid = $this->get_cid();
200         $systemcontext = \context_system::instance();
202         $disable = array_key_exists('disable', $this->content) ? $this->content['disable'] : core::DISABLE_NONE;
203         $displayoptions = $this->core->getDisplayOptionsForView($disable, $this->h5pid);
205         $contenturl = \moodle_url::make_pluginfile_url($systemcontext->id, \core_h5p\file_storage::COMPONENT,
206             \core_h5p\file_storage::CONTENT_FILEAREA, $this->h5pid, null, null);
207         $exporturl = $this->get_export_settings($displayoptions[ core::DISPLAY_OPTION_DOWNLOAD ]);
208         $xapiobject = item_activity::create_from_id($this->context->id);
209         $contentsettings = [
210             'library'         => core::libraryToString($this->content['library']),
211             'fullScreen'      => $this->content['library']['fullscreen'],
212             'exportUrl'       => ($exporturl instanceof \moodle_url) ? $exporturl->out(false) : '',
213             'embedCode'       => $this->get_embed_code($this->url->out(),
214                 $displayoptions[ core::DISPLAY_OPTION_EMBED ]),
215             'resizeCode'      => self::get_resize_code(),
216             'title'           => $this->content['slug'],
217             'displayOptions'  => $displayoptions,
218             'url'             => $xapiobject->get_data()->id,
219             'contentUrl'      => $contenturl->out(),
220             'metadata'        => $this->content['metadata'],
221             'contentUserData' => [0 => ['state' => '{}']]
222         ];
223         // Get the core H5P assets, needed by the H5P classes to render the H5P content.
224         $settings = $this->get_assets();
225         $settings['contents'][$cid] = array_merge($settings['contents'][$cid], $contentsettings);
227         foreach ($this->jsrequires as $script) {
228             $PAGE->requires->js($script, true);
229         }
231         foreach ($this->cssrequires as $css) {
232             $PAGE->requires->css($css);
233         }
235         // Print JavaScript settings to page.
236         $PAGE->requires->data_for_js('H5PIntegration', $settings, true);
237     }
239     /**
240      * Outputs H5P wrapper HTML.
241      *
242      * @return string The HTML code to display this H5P content.
243      */
244     public function output(): string {
245         global $OUTPUT, $USER;
247         $template = new \stdClass();
248         $template->h5pid = $this->h5pid;
249         if ($this->embedtype === 'div') {
250             $h5phtml = $OUTPUT->render_from_template('core_h5p/h5pdiv', $template);
251         } else {
252             $h5phtml = $OUTPUT->render_from_template('core_h5p/h5piframe', $template);
253         }
255         // Trigger capability_assigned event.
256         \core_h5p\event\h5p_viewed::create([
257             'objectid' => $this->h5pid,
258             'userid' => $USER->id,
259             'context' => $this->context,
260             'other' => [
261                 'url' => $this->url->out(),
262                 'time' => time()
263             ]
264         ])->trigger();
266         return $h5phtml;
267     }
269     /**
270      * Get the title of the H5P content to display.
271      *
272      * @return string the title
273      */
274     public function get_title(): string {
275         return $this->content['title'];
276     }
278     /**
279      * Get the context where the .h5p file belongs.
280      *
281      * @return context The context.
282      */
283     public function get_context(): \context {
284         return $this->context;
285     }
287     /**
288      * Get the H5P DB instance id for a H5P pluginfile URL. The H5P file will be saved if it doesn't exist previously or
289      * if its content has changed. Besides, the displayoptions in the $config will be also updated when they have changed and
290      * the user has the right permissions.
291      *
292      * @param string $url H5P pluginfile URL.
293      * @param stdClass $config Configuration for H5P buttons.
294      *
295      * @return int|false H5P DB identifier.
296      */
297     private function get_h5p_id(string $url, \stdClass $config) {
298         global $DB, $USER;
300         $fs = get_file_storage();
302         // Deconstruct the URL and get the pathname associated.
303         $pathnamehash = $this->get_pluginfile_hash($url);
304         if (!$pathnamehash) {
305             $this->core->h5pF->setErrorMessage(get_string('h5pfilenotfound', 'core_h5p'));
306             return false;
307         }
309         // Get the file.
310         $file = $fs->get_file_by_hash($pathnamehash);
311         if (!$file) {
312             $this->core->h5pF->setErrorMessage(get_string('h5pfilenotfound', 'core_h5p'));
313             return false;
314         }
316         $h5p = $DB->get_record('h5p', ['pathnamehash' => $pathnamehash]);
317         $contenthash = $file->get_contenthash();
318         if ($h5p && $h5p->contenthash != $contenthash) {
319             // The content exists and it is different from the one deployed previously. The existing one should be removed before
320             // deploying the new version.
321             $this->delete_h5p($h5p);
322             $h5p = false;
323         }
325         if ($h5p) {
326             // The H5P content has been deployed previously.
327             $displayoptions = $this->get_display_options($config);
328             // Check if the user can set the displayoptions.
329             if ($displayoptions != $h5p->displayoptions && has_capability('moodle/h5p:setdisplayoptions', $this->context)) {
330                 // If the displayoptions has changed and the user has permission to modify it, update this information in the DB.
331                 $this->core->h5pF->updateContentFields($h5p->id, ['displayoptions' => $displayoptions]);
332             }
333             return $h5p->id;
334         } else {
335             // The H5P content hasn't been deployed previously.
337             // Check if the user uploading the H5P content is "trustable". If the file hasn't been uploaded by a user with this
338             // capability, the content won't be deployed and an error message will be displayed.
339             if (!helper::can_deploy_package($file)) {
340                 $this->core->h5pF->setErrorMessage(get_string('nopermissiontodeploy', 'core_h5p'));
341                 return false;
342             }
344             // The H5P content can be only deployed if the author of the .h5p file can update libraries or if all the
345             // content-type libraries exist, to avoid users without the h5p:updatelibraries capability upload malicious content.
346             $onlyupdatelibs = !helper::can_update_library($file);
348             // Validate and store the H5P content before displaying it.
349             $h5pid = helper::save_h5p($this->factory, $file, $config, $onlyupdatelibs, false);
350             if (!$h5pid && $file->get_userid() != $USER->id && has_capability('moodle/h5p:updatelibraries', $this->context)) {
351                 // The user has permission to update libraries but the package has been uploaded by a different
352                 // user without this permission. Check if there is some missing required library error.
353                 $missingliberror = false;
354                 $messages = $this->get_messages();
355                 if (!empty($messages->error)) {
356                     foreach ($messages->error as $error) {
357                         if ($error->code == 'missing-required-library') {
358                             $missingliberror = true;
359                             break;
360                         }
361                     }
362                 }
363                 if ($missingliberror) {
364                     // The message about the permissions to upload libraries should be removed.
365                     $infomsg = "Note that the libraries may exist in the file you uploaded, but you're not allowed to upload " .
366                         "new libraries. Contact the site administrator about this.";
367                     if (($key = array_search($infomsg, $messages->info)) !== false) {
368                         unset($messages->info[$key]);
369                     }
371                     // No library will be installed and an error will be displayed, because this content is not trustable.
372                     $this->core->h5pF->setInfoMessage(get_string('notrustablefile', 'core_h5p'));
373                 }
374                 return false;
376             }
377             return $h5pid;
378         }
379     }
381     /**
382      * Get the pathnamehash from an H5P internal URL.
383      *
384      * @param  string $url H5P pluginfile URL poiting to an H5P file.
385      *
386      * @return string|false pathnamehash for the file in the internal URL.
387      */
388     private function get_pluginfile_hash(string $url) {
389         global $USER, $CFG;
391         // Decode the URL before start processing it.
392         $url = new \moodle_url(urldecode($url));
394         // Remove params from the URL (such as the 'forcedownload=1'), to avoid errors.
395         $url->remove_params(array_keys($url->params()));
396         $path = $url->out_as_local_url();
398         // We only need the slasharguments.
399         $path = substr($path, strpos($path, '.php/') + 5);
400         $parts = explode('/', $path);
401         $filename = array_pop($parts);
403         // If the request is made by tokenpluginfile.php we need to avoid userprivateaccesskey.
404         if (strpos($this->url, '/tokenpluginfile.php')) {
405             array_shift($parts);
406         }
407         // Get the contextid, component and filearea.
408         $contextid = array_shift($parts);
409         $component = array_shift($parts);
410         $filearea = array_shift($parts);
412         // Ignore draft files, because they are considered temporary files, so shouldn't be displayed.
413         if ($filearea == 'draft') {
414             return false;
415         }
417         // Get the context.
418         try {
419             list($this->context, $course, $cm) = get_context_info_array($contextid);
420         } catch (\moodle_exception $e) {
421             throw new \moodle_exception('invalidcontextid', 'core_h5p');
422         }
424         // For CONTEXT_USER, such as the private files, raise an exception if the owner of the file is not the current user.
425         if ($this->context->contextlevel == CONTEXT_USER && $USER->id !== $this->context->instanceid) {
426             throw new \moodle_exception('h5pprivatefile', 'core_h5p');
427         }
429         // For CONTEXT_COURSECAT No login necessary - unless login forced everywhere.
430         if ($this->context->contextlevel == CONTEXT_COURSECAT) {
431             if ($CFG->forcelogin) {
432                 require_login(null, true, null, false, true);
433             }
434         }
436         // For CONTEXT_BLOCK.
437         if ($this->context->contextlevel == CONTEXT_BLOCK) {
438             if ($this->context->get_course_context(false)) {
439                 // If block is in course context, then check if user has capability to access course.
440                 require_course_login($course, true, null, false, true);
441             } else if ($CFG->forcelogin) {
442                 // No login necessary - unless login forced everywhere.
443                 require_login(null, true, null, false, true);
444             } else {
445                 // Get parent context and see if user have proper permission.
446                 $parentcontext = $this->context->get_parent_context();
447                 if ($parentcontext->contextlevel === CONTEXT_COURSECAT) {
448                     // Check if category is visible and user can view this category.
449                     if (!core_course_category::get($parentcontext->instanceid, IGNORE_MISSING)) {
450                         send_file_not_found();
451                     }
452                 } else if ($parentcontext->contextlevel === CONTEXT_USER && $parentcontext->instanceid != $USER->id) {
453                     // The block is in the context of a user, it is only visible to the user who it belongs to.
454                     send_file_not_found();
455                 }
456                 if ($filearea !== 'content') {
457                     send_file_not_found();
458                 }
459             }
460         }
462         // For CONTEXT_MODULE and CONTEXT_COURSE check if the user is enrolled in the course.
463         // And for CONTEXT_MODULE has permissions view this .h5p file.
464         if ($this->context->contextlevel == CONTEXT_MODULE ||
465                 $this->context->contextlevel == CONTEXT_COURSE) {
466             // Require login to the course first (without login to the module).
467             require_course_login($course, true, null, !$this->preventredirect, $this->preventredirect);
469             // Now check if module is available OR it is restricted but the intro is shown on the course page.
470             if ($this->context->contextlevel == CONTEXT_MODULE) {
471                 $cminfo = \cm_info::create($cm);
472                 if (!$cminfo->uservisible) {
473                     if (!$cm->showdescription || !$cminfo->is_visible_on_course_page()) {
474                         // Module intro is not visible on the course page and module is not available, show access error.
475                         require_course_login($course, true, $cminfo, !$this->preventredirect, $this->preventredirect);
476                     }
477                 }
478             }
479         }
481         // Some components, such as mod_page or mod_resource, add the revision to the URL to prevent caching problems.
482         // So the URL contains this revision number as itemid but a 0 is always stored in the files table.
483         // In order to get the proper hash, a callback should be done (looking for those exceptions).
484         $pathdata = null;
485         if ($this->context->contextlevel == CONTEXT_MODULE || $this->context->contextlevel == CONTEXT_BLOCK) {
486             $pathdata = component_callback($component, 'get_path_from_pluginfile', [$filearea, $parts], null);
487         }
488         if (null === $pathdata) {
489             // Look for the components and fileareas which have empty itemid defined in xxx_pluginfile.
490             $hasnullitemid = false;
491             $hasnullitemid = $hasnullitemid || ($component === 'user' && ($filearea === 'private' || $filearea === 'profile'));
492             $hasnullitemid = $hasnullitemid || (substr($component, 0, 4) === 'mod_' && $filearea === 'intro');
493             $hasnullitemid = $hasnullitemid || ($component === 'course' &&
494                     ($filearea === 'summary' || $filearea === 'overviewfiles'));
495             $hasnullitemid = $hasnullitemid || ($component === 'coursecat' && $filearea === 'description');
496             $hasnullitemid = $hasnullitemid || ($component === 'backup' &&
497                     ($filearea === 'course' || $filearea === 'activity' || $filearea === 'automated'));
498             if ($hasnullitemid) {
499                 $itemid = 0;
500             } else {
501                 $itemid = array_shift($parts);
502             }
504             if (empty($parts)) {
505                 $filepath = '/';
506             } else {
507                 $filepath = '/' . implode('/', $parts) . '/';
508             }
509         } else {
510             // The itemid and filepath have been returned by the component callback.
511             [
512                 'itemid' => $itemid,
513                 'filepath' => $filepath,
514             ] = $pathdata;
515         }
517         $fs = get_file_storage();
518         return $fs->get_pathname_hash($contextid, $component, $filearea, $itemid, $filepath, $filename);
519     }
521     /**
522      * Get the representation of display options as int.
523      * @param stdClass $config Button options config.
524      *
525      * @return int The representation of display options as int.
526      */
527     private function get_display_options(\stdClass $config): int {
528         $export = isset($config->export) ? $config->export : 0;
529         $embed = isset($config->embed) ? $config->embed : 0;
530         $copyright = isset($config->copyright) ? $config->copyright : 0;
531         $frame = ($export || $embed || $copyright);
532         if (!$frame) {
533             $frame = isset($config->frame) ? $config->frame : 0;
534         }
536         $disableoptions = [
537             core::DISPLAY_OPTION_FRAME     => $frame,
538             core::DISPLAY_OPTION_DOWNLOAD  => $export,
539             core::DISPLAY_OPTION_EMBED     => $embed,
540             core::DISPLAY_OPTION_COPYRIGHT => $copyright,
541         ];
543         return $this->core->getStorableDisplayOptions($disableoptions, 0);
544     }
546     /**
547      * Delete an H5P package.
548      *
549      * @param stdClass $content The H5P package to delete.
550      */
551     private function delete_h5p(\stdClass $content) {
552         $h5pstorage = $this->factory->get_storage();
553         // Add an empty slug to the content if it's not defined, because the H5P library requires this field exists.
554         // It's not used when deleting a package, so the real slug value is not required at this point.
555         $content->slug = $content->slug ?? '';
556         $h5pstorage->deletePackage( (array) $content);
557     }
559     /**
560      * Export path for settings
561      *
562      * @param bool $downloadenabled Whether the option to export the H5P content is enabled.
563      *
564      * @return \moodle_url|null The URL of the exported file.
565      */
566     private function get_export_settings(bool $downloadenabled): ?\moodle_url {
568         if (!$downloadenabled) {
569             return null;
570         }
572         $systemcontext = \context_system::instance();
573         $slug = $this->content['slug'] ? $this->content['slug'] . '-' : '';
574         // We have to build the right URL.
575         // Depending the request was made through webservice/pluginfile.php or pluginfile.php.
576         if (strpos($this->url, '/webservice/pluginfile.php')) {
577             $url  = \moodle_url::make_webservice_pluginfile_url(
578                 $systemcontext->id,
579                 \core_h5p\file_storage::COMPONENT,
580                 \core_h5p\file_storage::EXPORT_FILEAREA,
581                 '',
582                 '',
583                 "{$slug}{$this->content['id']}.h5p"
584             );
585         } else {
586             // If the request is made by tokenpluginfile.php we need to indicates to generate a token for current user.
587             $includetoken = false;
588             if (strpos($this->url, '/tokenpluginfile.php')) {
589                 $includetoken = true;
590             }
591             $url  = \moodle_url::make_pluginfile_url(
592                 $systemcontext->id,
593                 \core_h5p\file_storage::COMPONENT,
594                 \core_h5p\file_storage::EXPORT_FILEAREA,
595                 '',
596                 '',
597                 "{$slug}{$this->content['id']}.h5p",
598                 false,
599                 $includetoken
600             );
601         }
603         return $url;
604     }
606     /**
607      * Get a query string with the theme revision number to include at the end
608      * of URLs. This is used to force the browser to reload the asset when the
609      * theme caches are cleared.
610      *
611      * @return string
612      */
613     private function get_cache_buster(): string {
614         global $CFG;
615         return '?ver=' . $CFG->themerev;
616     }
618     /**
619      * Get the identifier for the H5P content, to be used in the arrays as index.
620      *
621      * @return string The identifier.
622      */
623     private function get_cid(): string {
624         return 'cid-' . $this->h5pid;
625     }
627     /**
628      * Get the core H5P assets, including all core H5P JavaScript and CSS.
629      *
630      * @return Array core H5P assets.
631      */
632     private function get_assets(): array {
633         global $CFG;
635         // Get core settings.
636         $settings = $this->get_core_settings();
637         $settings['core'] = [
638           'styles' => [],
639           'scripts' => []
640         ];
641         $settings['loadedJs'] = [];
642         $settings['loadedCss'] = [];
644         // Make sure files are reloaded for each plugin update.
645         $cachebuster = $this->get_cache_buster();
647         // Use relative URL to support both http and https.
648         $liburl = autoloader::get_h5p_core_library_url()->out();
649         $relpath = '/' . preg_replace('/^[^:]+:\/\/[^\/]+\//', '', $liburl);
651         // Add core stylesheets.
652         foreach (core::$styles as $style) {
653             $settings['core']['styles'][] = $relpath . $style . $cachebuster;
654             $this->cssrequires[] = autoloader::get_h5p_core_library_url($style, [
655                 'ver' => $cachebuster,
656             ]);
657         }
658         // Add core JavaScript.
659         foreach (core::get_scripts() as $script) {
660             $settings['core']['scripts'][] = $script->out(false);
661             $this->jsrequires[] = $script;
662         }
664         $cid = $this->get_cid();
665         // The filterParameters function should be called before getting the dependencyfiles because it rebuild content
666         // dependency cache and export file.
667         $settings['contents'][$cid]['jsonContent'] = $this->core->filterParameters($this->content);
669         $files = $this->get_dependency_files();
670         if ($this->embedtype === 'div') {
671             $systemcontext = \context_system::instance();
672             $h5ppath = "/pluginfile.php/{$systemcontext->id}/core_h5p";
674             // Schedule JavaScripts for loading through Moodle.
675             foreach ($files['scripts'] as $script) {
676                 $url = $script->path . $script->version;
678                 // Add URL prefix if not external.
679                 $isexternal = strpos($script->path, '://');
680                 if ($isexternal === false) {
681                     $url = $h5ppath . $url;
682                 }
683                 $settings['loadedJs'][] = $url;
684                 $this->jsrequires[] = new \moodle_url($isexternal ? $url : $CFG->wwwroot . $url);
685             }
687             // Schedule stylesheets for loading through Moodle.
688             foreach ($files['styles'] as $style) {
689                 $url = $style->path . $style->version;
691                 // Add URL prefix if not external.
692                 $isexternal = strpos($style->path, '://');
693                 if ($isexternal === false) {
694                     $url = $h5ppath . $url;
695                 }
696                 $settings['loadedCss'][] = $url;
697                 $this->cssrequires[] = new \moodle_url($isexternal ? $url : $CFG->wwwroot . $url);
698             }
700         } else {
701             // JavaScripts and stylesheets will be loaded through h5p.js.
702             $settings['contents'][$cid]['scripts'] = $this->core->getAssetsUrls($files['scripts']);
703             $settings['contents'][$cid]['styles']  = $this->core->getAssetsUrls($files['styles']);
704         }
705         return $settings;
706     }
708     /**
709      * Get the settings needed by the H5P library.
710      *
711      * @return array The settings.
712      */
713     private function get_core_settings(): array {
714         global $CFG, $USER;
716         $basepath = $CFG->wwwroot . '/';
717         $systemcontext = \context_system::instance();
719         // Generate AJAX paths.
720         $ajaxpaths = [];
721         $ajaxpaths['xAPIResult'] = '';
722         $ajaxpaths['contentUserData'] = '';
724         $settings = array(
725             'baseUrl' => $basepath,
726             'url' => "{$basepath}pluginfile.php/{$systemcontext->instanceid}/core_h5p",
727             'urlLibraries' => "{$basepath}pluginfile.php/{$systemcontext->id}/core_h5p/libraries",
728             'postUserStatistics' => false,
729             'ajax' => $ajaxpaths,
730             'saveFreq' => false,
731             'siteUrl' => $CFG->wwwroot,
732             'l10n' => array('H5P' => $this->core->getLocalization()),
733             'user' => ['name' => $USER->username, 'mail' => $USER->email],
734             'hubIsEnabled' => false,
735             'reportingIsEnabled' => false,
736             'crossorigin' => null,
737             'libraryConfig' => $this->core->h5pF->getLibraryConfig(),
738             'pluginCacheBuster' => $this->get_cache_buster(),
739             'libraryUrl' => autoloader::get_h5p_core_library_url('js'),
740             'moodleLibraryPaths' => $this->core->get_dependency_roots($this->h5pid),
741             'moodleComponent' => $this->component,
742         );
744         return $settings;
745     }
747     /**
748      * Finds library dependencies of view
749      *
750      * @return array Files that the view has dependencies to
751      */
752     private function get_dependency_files(): array {
753         $preloadeddeps = $this->core->loadContentDependencies($this->h5pid, 'preloaded');
754         $files = $this->core->getDependenciesFiles($preloadeddeps);
756         return $files;
757     }
759     /**
760      * Resizing script for settings
761      *
762      * @return string The HTML code with the resize script.
763      */
764     private static function get_resize_code(): string {
765         global $OUTPUT;
767         $template = new \stdClass();
768         $template->resizeurl = autoloader::get_h5p_core_library_url('js/h5p-resizer.js');
770         return $OUTPUT->render_from_template('core_h5p/h5presize', $template);
771     }
773     /**
774      * Embed code for settings
775      *
776      * @param string $url The URL of the .h5p file.
777      * @param bool $embedenabled Whether the option to embed the H5P content is enabled.
778      *
779      * @return string The HTML code to reuse this H5P content in a different place.
780      */
781     private function get_embed_code(string $url, bool $embedenabled): string {
782         global $OUTPUT;
784         if ( ! $embedenabled) {
785             return '';
786         }
788         $template = new \stdClass();
789         $template->embedurl = self::get_embed_url($url)->out();
791         return $OUTPUT->render_from_template('core_h5p/h5pembed', $template);
792     }
794     /**
795      * Get the encoded URL for embeding this H5P content.
796      * @param  string $url The URL of the .h5p file.
797      *
798      * @return \moodle_url The embed URL.
799      */
800     public static function get_embed_url(string $url): \moodle_url {
801         return new \moodle_url('/h5p/embed.php', ['url' => $url]);
802     }
804     /**
805      * Return the export file for Mobile App.
806      *
807      * @return array
808      */
809     public function get_export_file(): array {
810         // Get the export url.
811         $exporturl = $this->get_export_settings(true);
812         // Get the filename of the export url.
813         $path = $exporturl->out_as_local_url();
814         $parts = explode('/', $path);
815         $filename = array_pop($parts);
816         // Get the the export file.
817         $systemcontext = \context_system::instance();
818         $fs = get_file_storage();
819         $fileh5p = $fs->get_file($systemcontext->id,
820             \core_h5p\file_storage::COMPONENT,
821             \core_h5p\file_storage::EXPORT_FILEAREA,
822             0,
823             '/',
824             $filename);
825         // Get the options that the Mobile App needs.
826         $file = [];
827         $file['filename'] = $fileh5p->get_filename();
828         $file['filepath'] = $fileh5p->get_filepath();
829         $file['mimetype'] = $fileh5p->get_mimetype();
830         $file['filesize'] = $fileh5p->get_filesize();
831         $file['timemodified'] = $fileh5p->get_timemodified();
832         $file['fileurl'] = $exporturl->out(false);
834         return $file;
835     }