Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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