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