Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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 core 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  require_once("$CFG->libdir/filelib.php");
  30  
  31  use H5PCore;
  32  use H5PFrameworkInterface;
  33  use stdClass;
  34  use moodle_url;
  35  use core_h5p\local\library\autoloader;
  36  
  37  /**
  38   * H5P core class, containing functions and storage shared by the other H5P classes.
  39   *
  40   * @package    core_h5p
  41   * @copyright  2019 Sara Arjona <sara@moodle.com>
  42   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  43   */
  44  class core extends \H5PCore {
  45  
  46      /** @var array The array containing all the present libraries */
  47      protected $libraries;
  48  
  49      /**
  50       * Constructor for core_h5p/core.
  51       *
  52       * @param H5PFrameworkInterface $framework The frameworks implementation of the H5PFrameworkInterface
  53       * @param string|\H5PFileStorage $path The H5P file storage directory or class
  54       * @param string $url The URL to the file storage directory
  55       * @param string $language The language code. Defaults to english
  56       * @param boolean $export Whether export is enabled
  57       */
  58      public function __construct(H5PFrameworkInterface $framework, $path, string $url, string $language = 'en',
  59              bool $export = false) {
  60  
  61          parent::__construct($framework, $path, $url, $language, $export);
  62  
  63          // Aggregate the assets by default.
  64          $this->aggregateAssets = true;
  65      }
  66  
  67      /**
  68       * Get the path to the dependency.
  69       *
  70       * @param array $dependency An array containing the information of the requested dependency library
  71       * @return string The path to the dependency library
  72       */
  73      protected function getDependencyPath(array $dependency): string {
  74          $library = $this->find_library($dependency);
  75  
  76          return "libraries/{$library->id}/{$library->machinename}-{$library->majorversion}.{$library->minorversion}";
  77      }
  78  
  79      /**
  80       * Get the paths to the content dependencies.
  81       *
  82       * @param int $id The H5P content ID
  83       * @return array An array containing the path of each content dependency
  84       */
  85      public function get_dependency_roots(int $id): array {
  86          $roots = [];
  87          $dependencies = $this->h5pF->loadContentDependencies($id);
  88          $context = \context_system::instance();
  89          foreach ($dependencies as $dependency) {
  90              $library = $this->find_library($dependency);
  91              $roots[self::libraryToString($dependency, true)] = (moodle_url::make_pluginfile_url(
  92                  $context->id,
  93                  'core_h5p',
  94                  'libraries',
  95                  $library->id,
  96                  "/" . self::libraryToString($dependency, true),
  97                  ''
  98              ))->out(false);
  99          }
 100  
 101          return $roots;
 102      }
 103  
 104      /**
 105       * Get a particular dependency library.
 106       *
 107       * @param array $dependency An array containing information of the dependency library
 108       * @return stdClass|null The library object if the library dependency exists, null otherwise
 109       */
 110      protected function find_library(array $dependency): ?\stdClass {
 111          global $DB;
 112          if (null === $this->libraries) {
 113              $this->libraries = $DB->get_records('h5p_libraries');
 114          }
 115  
 116          $major = $dependency['majorVersion'];
 117          $minor = $dependency['minorVersion'];
 118          $patch = $dependency['patchVersion'];
 119  
 120          foreach ($this->libraries as $library) {
 121              if ($library->machinename !== $dependency['machineName']) {
 122                  continue;
 123              }
 124  
 125              if ($library->majorversion != $major) {
 126                  continue;
 127              }
 128              if ($library->minorversion != $minor) {
 129                  continue;
 130              }
 131              if ($library->patchversion != $patch) {
 132                  continue;
 133              }
 134  
 135              return $library;
 136          }
 137  
 138          return null;
 139      }
 140  
 141      /**
 142       * Get the list of JS scripts to include on the page.
 143       *
 144       * @return array The array containg urls of the core JavaScript files
 145       */
 146      public static function get_scripts(): array {
 147          global $PAGE;
 148  
 149          $jsrev = $PAGE->requires->get_jsrev();
 150          $urls = [];
 151          foreach (self::$scripts as $script) {
 152              $urls[] = autoloader::get_h5p_core_library_url($script, [
 153                  'ver' => $jsrev,
 154              ]);
 155          }
 156          $urls[] = new moodle_url("/h5p/js/h5p_overrides.js", [
 157              'ver' => $jsrev,
 158          ]);
 159  
 160          return $urls;
 161      }
 162  
 163      /**
 164       * Fetch and install the latest H5P content types libraries from the official H5P repository.
 165       * If the latest version of a content type library is present in the system, nothing is done for that content type.
 166       *
 167       * @return stdClass
 168       */
 169      public function fetch_latest_content_types(): ?\stdClass {
 170  
 171          $contenttypes = $this->get_latest_content_types();
 172          if (!empty($contenttypes->error)) {
 173              return $contenttypes;
 174          }
 175  
 176          $typesinstalled = [];
 177  
 178          $factory = new factory();
 179          $framework = $factory->get_framework();
 180  
 181          foreach ($contenttypes->contentTypes as $type) {
 182              // Don't fetch content types that require a higher H5P core API version.
 183              if (!$this->is_required_core_api($type->coreApiVersionNeeded)) {
 184                  continue;
 185              }
 186  
 187              $library = [
 188                  'machineName' => $type->id,
 189                  'majorVersion' => $type->version->major,
 190                  'minorVersion' => $type->version->minor,
 191                  'patchVersion' => $type->version->patch,
 192              ];
 193  
 194              $shoulddownload = true;
 195              if ($framework->getLibraryId($type->id, $type->version->major, $type->version->minor)) {
 196                  if (!$framework->isPatchedLibrary($library)) {
 197                      $shoulddownload = false;
 198                  }
 199              }
 200  
 201              if ($shoulddownload) {
 202                  $installed['id'] = $this->fetch_content_type($library);
 203                  if ($installed['id']) {
 204                      $installed['name'] = \H5PCore::libraryToString($library);
 205                      $typesinstalled[] = $installed;
 206                  }
 207              }
 208          }
 209  
 210          $result = new stdClass();
 211          $result->error = '';
 212          $result->typesinstalled = $typesinstalled;
 213  
 214          return $result;
 215      }
 216  
 217      /**
 218       * Given an H5P content type machine name, fetch and install the required library from the official H5P repository.
 219       *
 220       * @param array $library Library machineName, majorversion and minorversion.
 221       * @return int|null Returns the id of the content type library installed, null otherwise.
 222       */
 223      public function fetch_content_type(array $library): ?int {
 224          $factory = new factory();
 225  
 226          // Download the latest content type from the H5P official repository.
 227          $fs = get_file_storage();
 228          $file = $fs->create_file_from_url(
 229              (object) [
 230                  'component' => 'core_h5p',
 231                  'filearea' => 'library_sources',
 232                  'itemid' => 0,
 233                  'contextid' => (\context_system::instance())->id,
 234                  'filepath' => '/',
 235                  'filename' => $library['machineName'],
 236              ],
 237              $this->get_api_endpoint($library['machineName']),
 238              null,
 239              true
 240          );
 241  
 242          if (!$file) {
 243              return null;
 244          }
 245  
 246          helper::save_h5p($factory, $file, (object) [], false, true);
 247  
 248          $file->delete();
 249  
 250          $librarykey = static::libraryToString($library);
 251          $libraryid = $factory->get_storage()->h5pC->librariesJsonData[$librarykey]["libraryId"];
 252  
 253          return $libraryid;
 254      }
 255  
 256      /**
 257       * Get H5P endpoints.
 258       *
 259       * If $endpoint = 'content' and $library is null, moodle_url is the endpoint of the latest version of the H5P content
 260       * types; however, if $library is the machine name of a content type, moodle_url is the endpoint to download the content type.
 261       * The SITES endpoint ($endpoint = 'site') may be use to get a site UUID or send site data.
 262       *
 263       * @param string|null $library The machineName of the library whose endpoint is requested.
 264       * @param string $endpoint The endpoint required. Valid values: "site", "content".
 265       * @return moodle_url The endpoint moodle_url object.
 266       */
 267      public function get_api_endpoint(?string $library = null, string $endpoint = 'content'): moodle_url {
 268          if ($endpoint == 'site') {
 269              $h5purl = \H5PHubEndpoints::createURL(\H5PHubEndpoints::SITES );
 270          } else if ($endpoint == 'content') {
 271              $h5purl = \H5PHubEndpoints::createURL(\H5PHubEndpoints::CONTENT_TYPES ) . $library;
 272          }
 273  
 274          return new moodle_url($h5purl);
 275      }
 276  
 277      /**
 278       * Get the latest version of the H5P content types available in the official repository.
 279       *
 280       * @return stdClass An object with 2 properties:
 281       *     - string error: error message when there is any problem, empty otherwise
 282       *     - array contentTypes: an object for each H5P content type with its information
 283       */
 284      public function get_latest_content_types(): \stdClass {
 285          global $CFG;
 286  
 287          $siteuuid = $this->get_site_uuid() ?? md5($CFG->wwwroot);
 288          $postdata = ['uuid' => $siteuuid];
 289  
 290          // Get the latest content-types json.
 291          $endpoint = $this->get_api_endpoint();
 292          $request = download_file_content($endpoint, null, $postdata, true);
 293  
 294          if (!empty($request->error) || $request->status != '200' || empty($request->results)) {
 295              if (empty($request->error)) {
 296                  $request->error = get_string('fetchtypesfailure', 'core_h5p');
 297              }
 298              return $request;
 299          }
 300  
 301          $contenttypes = json_decode($request->results);
 302          $contenttypes->error = '';
 303  
 304          return $contenttypes;
 305      }
 306  
 307      /**
 308       * Get the site UUID. If site UUID is not defined, try to register the site.
 309       *
 310       * return $string The site UUID, null if it is not set.
 311       */
 312      public function get_site_uuid(): ?string {
 313          // Check if the site_uuid is already set.
 314          $siteuuid = get_config('core_h5p', 'site_uuid');
 315  
 316          if (!$siteuuid) {
 317              $siteuuid = $this->register_site();
 318          }
 319  
 320          return $siteuuid;
 321      }
 322  
 323      /**
 324       * Get H5P generated site UUID.
 325       *
 326       * return ?string Returns H5P generated site UUID, null if can't get it.
 327       */
 328      private function register_site(): ?string {
 329          $endpoint = $this->get_api_endpoint(null, 'site');
 330          $siteuuid = download_file_content($endpoint, null, '');
 331  
 332          // Successful UUID retrieval from H5P.
 333          if ($siteuuid) {
 334              $json = json_decode($siteuuid);
 335              if (isset($json->uuid)) {
 336                  set_config('site_uuid', $json->uuid, 'core_h5p');
 337                  return $json->uuid;
 338              }
 339          }
 340  
 341          return null;
 342      }
 343  
 344      /**
 345       * Checks that the required H5P core API version or higher is installed.
 346       *
 347       * @param stdClass $coreapi Object with properties major and minor for the core API version required.
 348       * @return bool True if the required H5P core API version is installed. False if not.
 349       */
 350      public function is_required_core_api($coreapi): bool {
 351          if (isset($coreapi) && !empty($coreapi)) {
 352              if (($coreapi->major > H5PCore::$coreApi['majorVersion']) ||
 353                  (($coreapi->major == H5PCore::$coreApi['majorVersion']) && ($coreapi->minor > H5PCore::$coreApi['minorVersion']))) {
 354                  return false;
 355              }
 356          }
 357          return true;
 358      }
 359  
 360      /**
 361       * Get the library string from a DB library record.
 362       *
 363       * @param  stdClass $record The DB library record.
 364       * @param  bool $foldername If true, use hyphen instead of space in returned string.
 365       * @return string The string name on the form {machineName} {majorVersion}.{minorVersion}.
 366       */
 367      public static function record_to_string(stdClass $record, bool $foldername = false): string {
 368          return static::libraryToString([
 369              'machineName' => $record->machinename,
 370              'majorVersion' => $record->majorversion,
 371              'minorVersion' => $record->minorversion,
 372          ], $foldername);
 373      }
 374  }