Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

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

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