Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * H5P player class.
  19   *
  20   * @package    core_h5p
  21   * @copyright  2019 Sara Arjona <sara@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_h5p;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  use core_h5p\local\library\autoloader;
  30  use core_xapi\local\statement\item_activity;
  31  
  32  /**
  33   * H5P player class, for displaying any local H5P content.
  34   *
  35   * @package    core_h5p
  36   * @copyright  2019 Sara Arjona <sara@moodle.com>
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class player {
  40  
  41      /**
  42       * @var string The local H5P URL containing the .h5p file to display.
  43       */
  44      private $url;
  45  
  46      /**
  47       * @var core The H5PCore object.
  48       */
  49      private $core;
  50  
  51      /**
  52       * @var int H5P DB id.
  53       */
  54      private $h5pid;
  55  
  56      /**
  57       * @var array JavaScript requirements for this H5P.
  58       */
  59      private $jsrequires = [];
  60  
  61      /**
  62       * @var array CSS requirements for this H5P.
  63       */
  64      private $cssrequires = [];
  65  
  66      /**
  67       * @var array H5P content to display.
  68       */
  69      private $content;
  70  
  71      /**
  72       * @var string optional component name to send xAPI statements.
  73       */
  74      private $component;
  75  
  76      /**
  77       * @var string Type of embed object, div or iframe.
  78       */
  79      private $embedtype;
  80  
  81      /**
  82       * @var context The context object where the .h5p belongs.
  83       */
  84      private $context;
  85  
  86      /**
  87       * @var factory The \core_h5p\factory object.
  88       */
  89      private $factory;
  90  
  91      /**
  92       * @var stdClass The error, exception and info messages, raised while preparing and running the player.
  93       */
  94      private $messages;
  95  
  96      /**
  97       * @var bool Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions.
  98       */
  99      private $preventredirect;
 100  
 101      /**
 102       * Inits the H5P player for rendering the content.
 103       *
 104       * @param string $url Local URL of the H5P file to display.
 105       * @param stdClass $config Configuration for H5P buttons.
 106       * @param bool $preventredirect Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions
 107       * @param string $component optional moodle component to sent xAPI tracking
 108       * @param bool $skipcapcheck Whether capabilities should be checked or not to get the pluginfile URL because sometimes they
 109       *     might be controlled before calling this method.
 110       */
 111      public function __construct(string $url, \stdClass $config, bool $preventredirect = true, string $component = '',
 112              bool $skipcapcheck = false) {
 113          if (empty($url)) {
 114              throw new \moodle_exception('h5pinvalidurl', 'core_h5p');
 115          }
 116          $this->url = new \moodle_url($url);
 117          $this->preventredirect = $preventredirect;
 118  
 119          $this->factory = new \core_h5p\factory();
 120  
 121          $this->messages = new \stdClass();
 122  
 123          $this->component = $component;
 124  
 125          // Create \core_h5p\core instance.
 126          $this->core = $this->factory->get_core();
 127  
 128          // Get the H5P identifier linked to this URL.
 129          list($file, $this->h5pid) = api::create_content_from_pluginfile_url(
 130              $url,
 131              $config,
 132              $this->factory,
 133              $this->messages,
 134              $this->preventredirect,
 135              $skipcapcheck
 136          );
 137          if ($file) {
 138              $this->context = \context::instance_by_id($file->get_contextid());
 139              if ($this->h5pid) {
 140                  // Load the content of the H5P content associated to this $url.
 141                  $this->content = $this->core->loadContent($this->h5pid);
 142  
 143                  // Get the embedtype to use for displaying the H5P content.
 144                  $this->embedtype = core::determineEmbedType($this->content['embedType'], $this->content['library']['embedTypes']);
 145              }
 146          }
 147      }
 148  
 149      /**
 150       * Get the encoded URL for embeding this H5P content.
 151       *
 152       * @param string $url Local URL of the H5P file to display.
 153       * @param stdClass $config Configuration for H5P buttons.
 154       * @param bool $preventredirect Set to true in scripts that can not redirect (CLI, RSS feeds, etc.), throws exceptions
 155       * @param string $component optional moodle component to sent xAPI tracking
 156       *
 157       * @return string The embedable code to display a H5P file.
 158       */
 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,
 166              ];
 167  
 168          $optparams = ['frame', 'export', 'embed', 'copyright'];
 169          foreach ($optparams as $optparam) {
 170              if (!empty($config->$optparam)) {
 171                  $params[$optparam] = $config->$optparam;
 172              }
 173          }
 174          $fileurl = new \moodle_url('/h5p/embed.php', $params);
 175  
 176          $template = new \stdClass();
 177          $template->embedurl = $fileurl->out(false);
 178  
 179          $result = $OUTPUT->render_from_template('core_h5p/h5pembed', $template);
 180          $result .= self::get_resize_code();
 181          return $result;
 182      }
 183  
 184      /**
 185       * Get the error messages stored in our H5P framework.
 186       *
 187       * @return stdClass with framework error messages.
 188       */
 189      public function get_messages(): \stdClass {
 190          return helper::get_messages($this->messages, $this->factory);
 191      }
 192  
 193      /**
 194       * Create the H5PIntegration variable that will be included in the page. This variable is used as the
 195       * main H5P config variable.
 196       */
 197      public function add_assets_to_page() {
 198          global $PAGE;
 199  
 200          $cid = $this->get_cid();
 201          $systemcontext = \context_system::instance();
 202  
 203          $disable = array_key_exists('disable', $this->content) ? $this->content['disable'] : core::DISABLE_NONE;
 204          $displayoptions = $this->core->getDisplayOptionsForView($disable, $this->h5pid);
 205  
 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' => '{}']]
 223          ];
 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);
 227  
 228          // Print JavaScript settings to page.
 229          $PAGE->requires->data_for_js('H5PIntegration', $settings, true);
 230      }
 231  
 232      /**
 233       * Outputs H5P wrapper HTML.
 234       *
 235       * @return string The HTML code to display this H5P content.
 236       */
 237      public function output(): string {
 238          global $OUTPUT, $USER;
 239  
 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);
 246          }
 247  
 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()
 256              ]
 257          ])->trigger();
 258  
 259          return $h5phtml;
 260      }
 261  
 262      /**
 263       * Get the title of the H5P content to display.
 264       *
 265       * @return string the title
 266       */
 267      public function get_title(): string {
 268          return $this->content['title'];
 269      }
 270  
 271      /**
 272       * Get the context where the .h5p file belongs.
 273       *
 274       * @return context The context.
 275       */
 276      public function get_context(): \context {
 277          return $this->context;
 278      }
 279  
 280      /**
 281       * Delete an H5P package.
 282       *
 283       * @param stdClass $content The H5P package to delete.
 284       */
 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);
 291      }
 292  
 293      /**
 294       * Export path for settings
 295       *
 296       * @param bool $downloadenabled Whether the option to export the H5P content is enabled.
 297       *
 298       * @return \moodle_url|null The URL of the exported file.
 299       */
 300      private function get_export_settings(bool $downloadenabled): ?\moodle_url {
 301  
 302          if (!$downloadenabled) {
 303              return null;
 304          }
 305  
 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,
 315                  '',
 316                  '',
 317                  "{$slug}{$this->content['id']}.h5p"
 318              );
 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;
 324              }
 325              $url  = \moodle_url::make_pluginfile_url(
 326                  $systemcontext->id,
 327                  \core_h5p\file_storage::COMPONENT,
 328                  \core_h5p\file_storage::EXPORT_FILEAREA,
 329                  '',
 330                  '',
 331                  "{$slug}{$this->content['id']}.h5p",
 332                  false,
 333                  $includetoken
 334              );
 335          }
 336  
 337          return $url;
 338      }
 339  
 340      /**
 341       * Get the identifier for the H5P content, to be used in the arrays as index.
 342       *
 343       * @return string The identifier.
 344       */
 345      private function get_cid(): string {
 346          return 'cid-' . $this->h5pid;
 347      }
 348  
 349      /**
 350       * Get the core H5P assets, including all core H5P JavaScript and CSS.
 351       *
 352       * @return Array core H5P assets.
 353       */
 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;
 363          }
 364  
 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();
 369  
 370          $files = $this->get_dependency_files();
 371          if ($this->embedtype === 'div') {
 372              $systemcontext = \context_system::instance();
 373              $h5ppath = "/pluginfile.php/{$systemcontext->id}/core_h5p";
 374  
 375              // Schedule JavaScripts for loading through Moodle.
 376              foreach ($files['scripts'] as $script) {
 377                  $url = $script->path . $script->version;
 378  
 379                  // Add URL prefix if not external.
 380                  $isexternal = strpos($script->path, '://');
 381                  if ($isexternal === false) {
 382                      $url = $h5ppath . $url;
 383                  }
 384                  $settings['loadedJs'][] = $url;
 385                  $this->jsrequires[] = new \moodle_url($isexternal ? $url : $CFG->wwwroot . $url);
 386              }
 387  
 388              // Schedule stylesheets for loading through Moodle.
 389              foreach ($files['styles'] as $style) {
 390                  $url = $style->path . $style->version;
 391  
 392                  // Add URL prefix if not external.
 393                  $isexternal = strpos($style->path, '://');
 394                  if ($isexternal === false) {
 395                      $url = $h5ppath . $url;
 396                  }
 397                  $settings['loadedCss'][] = $url;
 398                  $this->cssrequires[] = new \moodle_url($isexternal ? $url : $CFG->wwwroot . $url);
 399              }
 400  
 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']);
 405          }
 406          return $settings;
 407      }
 408  
 409      /**
 410       * Get filtered parameters, modifying them by the renderer if the theme implements the h5p_alter_filtered_parameters function.
 411       *
 412       * @return string Filtered parameters.
 413       */
 414      private function get_filtered_parameters(): string {
 415          global $PAGE;
 416  
 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']
 425          );
 426          $safeparams = json_encode($decodedparams);
 427  
 428          return $safeparams;
 429      }
 430  
 431      /**
 432       * Finds library dependencies of view
 433       *
 434       * @return array Files that the view has dependencies to
 435       */
 436      private function get_dependency_files(): array {
 437          global $PAGE;
 438  
 439          $preloadeddeps = $this->core->loadContentDependencies($this->h5pid, 'preloaded');
 440          $files = $this->core->getDependenciesFiles($preloadeddeps);
 441  
 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);
 446  
 447          return $files;
 448      }
 449  
 450      /**
 451       * Resizing script for settings
 452       *
 453       * @return string The HTML code with the resize script.
 454       */
 455      private static function get_resize_code(): string {
 456          global $OUTPUT;
 457  
 458          $template = new \stdClass();
 459          $template->resizeurl = autoloader::get_h5p_core_library_url('js/h5p-resizer.js');
 460  
 461          return $OUTPUT->render_from_template('core_h5p/h5presize', $template);
 462      }
 463  
 464      /**
 465       * Embed code for settings
 466       *
 467       * @param string $url The URL of the .h5p file.
 468       * @param bool $embedenabled Whether the option to embed the H5P content is enabled.
 469       *
 470       * @return string The HTML code to reuse this H5P content in a different place.
 471       */
 472      private function get_embed_code(string $url, bool $embedenabled): string {
 473          global $OUTPUT;
 474  
 475          if ( ! $embedenabled) {
 476              return '';
 477          }
 478  
 479          $template = new \stdClass();
 480          $template->embedurl = self::get_embed_url($url, $this->component)->out(false);
 481  
 482          return $OUTPUT->render_from_template('core_h5p/h5pembed', $template);
 483      }
 484  
 485      /**
 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
 489       *
 490       * @return \moodle_url The embed URL.
 491       */
 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;
 497          }
 498  
 499          return new \moodle_url('/h5p/embed.php', $params);
 500      }
 501  
 502      /**
 503       * Return the info export file for Mobile App.
 504       *
 505       * @return array
 506       */
 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);
 516  
 517          return $file;
 518      }
 519  }