Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

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