MDL-65448 block_social_activities: Restyle moving.
[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         list($file, $this->h5pid) = api::create_content_from_pluginfile_url(
127             $url,
128             $config,
129             $this->factory,
130             $this->messages
131         );
132         if ($file) {
133             $this->context = \context::instance_by_id($file->get_contextid());
134             if ($this->h5pid) {
135                 // Load the content of the H5P content associated to this $url.
136                 $this->content = $this->core->loadContent($this->h5pid);
138                 // Get the embedtype to use for displaying the H5P content.
139                 $this->embedtype = core::determineEmbedType($this->content['embedType'], $this->content['library']['embedTypes']);
140             }
141         }
142     }
144     /**
145      * Get the encoded URL for embeding this H5P content.
146      *
147      * @param string $url Local URL of the H5P file to display.
148      * @param stdClass $config Configuration for H5P buttons.
149      * @param bool $preventredirect Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions
150      * @param string $component optional moodle component to sent xAPI tracking
151      *
152      * @return string The embedable code to display a H5P file.
153      */
154     public static function display(string $url, \stdClass $config, bool $preventredirect = true,
155             string $component = ''): string {
156         global $OUTPUT;
157         $params = [
158                 'url' => $url,
159                 'preventredirect' => $preventredirect,
160                 'component' => $component,
161             ];
163         $optparams = ['frame', 'export', 'embed', 'copyright'];
164         foreach ($optparams as $optparam) {
165             if (!empty($config->$optparam)) {
166                 $params[$optparam] = $config->$optparam;
167             }
168         }
169         $fileurl = new \moodle_url('/h5p/embed.php', $params);
171         $template = new \stdClass();
172         $template->embedurl = $fileurl->out(false);
174         $result = $OUTPUT->render_from_template('core_h5p/h5pembed', $template);
175         $result .= self::get_resize_code();
176         return $result;
177     }
179     /**
180      * Get the error messages stored in our H5P framework.
181      *
182      * @return stdClass with framework error messages.
183      */
184     public function get_messages(): \stdClass {
185         return helper::get_messages($this->messages, $this->factory);
186     }
188     /**
189      * Create the H5PIntegration variable that will be included in the page. This variable is used as the
190      * main H5P config variable.
191      */
192     public function add_assets_to_page() {
193         global $PAGE;
195         $cid = $this->get_cid();
196         $systemcontext = \context_system::instance();
198         $disable = array_key_exists('disable', $this->content) ? $this->content['disable'] : core::DISABLE_NONE;
199         $displayoptions = $this->core->getDisplayOptionsForView($disable, $this->h5pid);
201         $contenturl = \moodle_url::make_pluginfile_url($systemcontext->id, \core_h5p\file_storage::COMPONENT,
202             \core_h5p\file_storage::CONTENT_FILEAREA, $this->h5pid, null, null);
203         $exporturl = $this->get_export_settings($displayoptions[ core::DISPLAY_OPTION_DOWNLOAD ]);
204         $xapiobject = item_activity::create_from_id($this->context->id);
205         $contentsettings = [
206             'library'         => core::libraryToString($this->content['library']),
207             'fullScreen'      => $this->content['library']['fullscreen'],
208             'exportUrl'       => ($exporturl instanceof \moodle_url) ? $exporturl->out(false) : '',
209             'embedCode'       => $this->get_embed_code($this->url->out(),
210                 $displayoptions[ core::DISPLAY_OPTION_EMBED ]),
211             'resizeCode'      => self::get_resize_code(),
212             'title'           => $this->content['slug'],
213             'displayOptions'  => $displayoptions,
214             'url'             => $xapiobject->get_data()->id,
215             'contentUrl'      => $contenturl->out(),
216             'metadata'        => $this->content['metadata'],
217             'contentUserData' => [0 => ['state' => '{}']]
218         ];
219         // Get the core H5P assets, needed by the H5P classes to render the H5P content.
220         $settings = $this->get_assets();
221         $settings['contents'][$cid] = array_merge($settings['contents'][$cid], $contentsettings);
223         // Print JavaScript settings to page.
224         $PAGE->requires->data_for_js('H5PIntegration', $settings, true);
225     }
227     /**
228      * Outputs H5P wrapper HTML.
229      *
230      * @return string The HTML code to display this H5P content.
231      */
232     public function output(): string {
233         global $OUTPUT, $USER;
235         $template = new \stdClass();
236         $template->h5pid = $this->h5pid;
237         if ($this->embedtype === 'div') {
238             $h5phtml = $OUTPUT->render_from_template('core_h5p/h5pdiv', $template);
239         } else {
240             $h5phtml = $OUTPUT->render_from_template('core_h5p/h5piframe', $template);
241         }
243         // Trigger capability_assigned event.
244         \core_h5p\event\h5p_viewed::create([
245             'objectid' => $this->h5pid,
246             'userid' => $USER->id,
247             'context' => $this->get_context(),
248             'other' => [
249                 'url' => $this->url->out(),
250                 'time' => time()
251             ]
252         ])->trigger();
254         return $h5phtml;
255     }
257     /**
258      * Get the title of the H5P content to display.
259      *
260      * @return string the title
261      */
262     public function get_title(): string {
263         return $this->content['title'];
264     }
266     /**
267      * Get the context where the .h5p file belongs.
268      *
269      * @return context The context.
270      */
271     public function get_context(): \context {
272         return $this->context;
273     }
275     /**
276      * Delete an H5P package.
277      *
278      * @param stdClass $content The H5P package to delete.
279      */
280     private function delete_h5p(\stdClass $content) {
281         $h5pstorage = $this->factory->get_storage();
282         // Add an empty slug to the content if it's not defined, because the H5P library requires this field exists.
283         // It's not used when deleting a package, so the real slug value is not required at this point.
284         $content->slug = $content->slug ?? '';
285         $h5pstorage->deletePackage( (array) $content);
286     }
288     /**
289      * Export path for settings
290      *
291      * @param bool $downloadenabled Whether the option to export the H5P content is enabled.
292      *
293      * @return \moodle_url|null The URL of the exported file.
294      */
295     private function get_export_settings(bool $downloadenabled): ?\moodle_url {
297         if (!$downloadenabled) {
298             return null;
299         }
301         $systemcontext = \context_system::instance();
302         $slug = $this->content['slug'] ? $this->content['slug'] . '-' : '';
303         // We have to build the right URL.
304         // Depending the request was made through webservice/pluginfile.php or pluginfile.php.
305         if (strpos($this->url, '/webservice/pluginfile.php')) {
306             $url  = \moodle_url::make_webservice_pluginfile_url(
307                 $systemcontext->id,
308                 \core_h5p\file_storage::COMPONENT,
309                 \core_h5p\file_storage::EXPORT_FILEAREA,
310                 '',
311                 '',
312                 "{$slug}{$this->content['id']}.h5p"
313             );
314         } else {
315             // If the request is made by tokenpluginfile.php we need to indicates to generate a token for current user.
316             $includetoken = false;
317             if (strpos($this->url, '/tokenpluginfile.php')) {
318                 $includetoken = true;
319             }
320             $url  = \moodle_url::make_pluginfile_url(
321                 $systemcontext->id,
322                 \core_h5p\file_storage::COMPONENT,
323                 \core_h5p\file_storage::EXPORT_FILEAREA,
324                 '',
325                 '',
326                 "{$slug}{$this->content['id']}.h5p",
327                 false,
328                 $includetoken
329             );
330         }
332         return $url;
333     }
335     /**
336      * Get the identifier for the H5P content, to be used in the arrays as index.
337      *
338      * @return string The identifier.
339      */
340     private function get_cid(): string {
341         return 'cid-' . $this->h5pid;
342     }
344     /**
345      * Get the core H5P assets, including all core H5P JavaScript and CSS.
346      *
347      * @return Array core H5P assets.
348      */
349     private function get_assets(): array {
350         // Get core assets.
351         $settings = helper::get_core_assets();
352         // Added here because in the helper we don't have the h5p content id.
353         $settings['moodleLibraryPaths'] = $this->core->get_dependency_roots($this->h5pid);
354         // Add also the Moodle component where the results will be tracked.
355         $settings['moodleComponent'] = $this->component;
357         $cid = $this->get_cid();
358         // The filterParameters function should be called before getting the dependencyfiles because it rebuild content
359         // dependency cache and export file.
360         $settings['contents'][$cid]['jsonContent'] = $this->get_filtered_parameters();
362         $files = $this->get_dependency_files();
363         if ($this->embedtype === 'div') {
364             $systemcontext = \context_system::instance();
365             $h5ppath = "/pluginfile.php/{$systemcontext->id}/core_h5p";
367             // Schedule JavaScripts for loading through Moodle.
368             foreach ($files['scripts'] as $script) {
369                 $url = $script->path . $script->version;
371                 // Add URL prefix if not external.
372                 $isexternal = strpos($script->path, '://');
373                 if ($isexternal === false) {
374                     $url = $h5ppath . $url;
375                 }
376                 $settings['loadedJs'][] = $url;
377                 $this->jsrequires[] = new \moodle_url($isexternal ? $url : $CFG->wwwroot . $url);
378             }
380             // Schedule stylesheets for loading through Moodle.
381             foreach ($files['styles'] as $style) {
382                 $url = $style->path . $style->version;
384                 // Add URL prefix if not external.
385                 $isexternal = strpos($style->path, '://');
386                 if ($isexternal === false) {
387                     $url = $h5ppath . $url;
388                 }
389                 $settings['loadedCss'][] = $url;
390                 $this->cssrequires[] = new \moodle_url($isexternal ? $url : $CFG->wwwroot . $url);
391             }
393         } else {
394             // JavaScripts and stylesheets will be loaded through h5p.js.
395             $settings['contents'][$cid]['scripts'] = $this->core->getAssetsUrls($files['scripts']);
396             $settings['contents'][$cid]['styles']  = $this->core->getAssetsUrls($files['styles']);
397         }
398         return $settings;
399     }
401     /**
402      * Get filtered parameters, modifying them by the renderer if the theme implements the h5p_alter_filtered_parameters function.
403      *
404      * @return string Filtered parameters.
405      */
406     private function get_filtered_parameters(): string {
407         global $PAGE;
409         $safeparams = $this->core->filterParameters($this->content);
410         $decodedparams = json_decode($safeparams);
411         $h5poutput = $PAGE->get_renderer('core_h5p');
412         $h5poutput->h5p_alter_filtered_parameters(
413             $decodedparams,
414             $this->content['library']['name'],
415             $this->content['library']['majorVersion'],
416             $this->content['library']['minorVersion']
417         );
418         $safeparams = json_encode($decodedparams);
420         return $safeparams;
421     }
423     /**
424      * Finds library dependencies of view
425      *
426      * @return array Files that the view has dependencies to
427      */
428     private function get_dependency_files(): array {
429         global $PAGE;
431         $preloadeddeps = $this->core->loadContentDependencies($this->h5pid, 'preloaded');
432         $files = $this->core->getDependenciesFiles($preloadeddeps);
434         // Add additional asset files if required.
435         $h5poutput = $PAGE->get_renderer('core_h5p');
436         $h5poutput->h5p_alter_scripts($files['scripts'], $preloadeddeps, $this->embedtype);
437         $h5poutput->h5p_alter_styles($files['styles'], $preloadeddeps, $this->embedtype);
439         return $files;
440     }
442     /**
443      * Resizing script for settings
444      *
445      * @return string The HTML code with the resize script.
446      */
447     private static function get_resize_code(): string {
448         global $OUTPUT;
450         $template = new \stdClass();
451         $template->resizeurl = autoloader::get_h5p_core_library_url('js/h5p-resizer.js');
453         return $OUTPUT->render_from_template('core_h5p/h5presize', $template);
454     }
456     /**
457      * Embed code for settings
458      *
459      * @param string $url The URL of the .h5p file.
460      * @param bool $embedenabled Whether the option to embed the H5P content is enabled.
461      *
462      * @return string The HTML code to reuse this H5P content in a different place.
463      */
464     private function get_embed_code(string $url, bool $embedenabled): string {
465         global $OUTPUT;
467         if ( ! $embedenabled) {
468             return '';
469         }
471         $template = new \stdClass();
472         $template->embedurl = self::get_embed_url($url, $this->component)->out(false);
474         return $OUTPUT->render_from_template('core_h5p/h5pembed', $template);
475     }
477     /**
478      * Get the encoded URL for embeding this H5P content.
479      * @param  string $url The URL of the .h5p file.
480      * @param string $component optional Moodle component to send xAPI tracking
481      *
482      * @return \moodle_url The embed URL.
483      */
484     public static function get_embed_url(string $url, string $component = ''): \moodle_url {
485         $params = ['url' => $url];
486         if (!empty($component)) {
487             // If component is not empty, it will be passed too, in order to allow tracking too.
488             $params['component'] = $component;
489         }
491         return new \moodle_url('/h5p/embed.php', $params);
492     }
494     /**
495      * Return the info export file for Mobile App.
496      *
497      * @return array
498      */
499     public function get_export_file(): array {
500         // Get the export url.
501         $exporturl = $this->get_export_settings(true);
502         // Get the filename of the export url.
503         $path = $exporturl->out_as_local_url();
504         $parts = explode('/', $path);
505         $filename = array_pop($parts);
506         // Get the required info from the export file to be able to get the export file by third apps.
507         $file = helper::get_export_info($filename, $exporturl);
509         return $file;
510     }