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 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   * \core_h5p\framework class
  19   *
  20   * @package    core_h5p
  21   * @copyright  2019 Mihail Geshoski <mihail@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_h5p;
  26  
  27  use Moodle\H5PFrameworkInterface;
  28  use Moodle\H5PCore;
  29  /**
  30   * Moodle's implementation of the H5P framework interface.
  31   *
  32   * @package    core_h5p
  33   * @copyright  2019 Mihail Geshoski <mihail@moodle.com>
  34   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class framework implements H5PFrameworkInterface {
  37  
  38      /** @var string The path to the last uploaded h5p */
  39      private $lastuploadedfolder;
  40  
  41      /** @var string The path to the last uploaded h5p file */
  42      private $lastuploadedfile;
  43  
  44      /** @var stored_file The .h5p file */
  45      private $file;
  46  
  47      /**
  48       * Returns info for the current platform.
  49       * Implements getPlatformInfo.
  50       *
  51       * @return array An associative array containing:
  52       *               - name: The name of the platform, for instance "Moodle"
  53       *               - version: The version of the platform, for instance "3.8"
  54       *               - h5pVersion: The version of the H5P component
  55       */
  56      public function getPlatformInfo() {
  57          global $CFG;
  58  
  59          return array(
  60              'name' => 'Moodle',
  61              'version' => $CFG->version,
  62              'h5pVersion' => $CFG->version,
  63          );
  64      }
  65  
  66      /**
  67       * Fetches a file from a remote server using HTTP GET.
  68       * Implements fetchExternalData.
  69       *
  70       * @param string $url Where you want to get or send data
  71       * @param array $data Data to post to the URL
  72       * @param bool $blocking Set to 'FALSE' to instantly time out (fire and forget)
  73       * @param string $stream Path to where the file should be saved
  74       * @return string The content (response body). NULL if something went wrong
  75       */
  76      public function fetchExternalData($url, $data = null, $blocking = true, $stream = null) {
  77  
  78          if ($stream === null) {
  79              // Download file.
  80              set_time_limit(0);
  81  
  82              // Get the extension of the remote file.
  83              $parsedurl = parse_url($url);
  84              $ext = pathinfo($parsedurl['path'], PATHINFO_EXTENSION);
  85  
  86              // Generate local tmp file path.
  87              $fs = new \core_h5p\file_storage();
  88              $localfolder = $fs->getTmpPath();
  89              $stream = $localfolder;
  90  
  91              // Add the remote file's extension to the temp file.
  92              if ($ext) {
  93                  $stream .= '.' . $ext;
  94              }
  95  
  96              $this->getUploadedH5pFolderPath($localfolder);
  97              $this->getUploadedH5pPath($stream);
  98          }
  99  
 100          $response = download_file_content($url, null, $data, true, 300, 20,
 101                  false, $stream);
 102  
 103          if (empty($response->error) && ($response->status != '404')) {
 104              return $response->results;
 105          } else {
 106              $this->setErrorMessage($response->error, 'failed-fetching-external-data');
 107          }
 108      }
 109  
 110      /**
 111       * Set the tutorial URL for a library. All versions of the library is set.
 112       * Implements setLibraryTutorialUrl.
 113       *
 114       * @param string $libraryname
 115       * @param string $url
 116       */
 117      public function setLibraryTutorialUrl($libraryname, $url) {
 118          global $DB;
 119  
 120          $sql = 'UPDATE {h5p_libraries}
 121                     SET tutorial = :tutorial
 122                   WHERE machinename = :machinename';
 123          $params = [
 124              'tutorial' => $url,
 125              'machinename' => $libraryname,
 126          ];
 127          $DB->execute($sql, $params);
 128      }
 129  
 130      /**
 131       * Set an error message.
 132       * Implements setErrorMessage.
 133       *
 134       * @param string $message The error message
 135       * @param string $code An optional code
 136       */
 137      public function setErrorMessage($message, $code = null) {
 138          if ($message !== null) {
 139              $this->set_message('error', $message, $code);
 140          }
 141      }
 142  
 143      /**
 144       * Set an info message.
 145       * Implements setInfoMessage.
 146       *
 147       * @param string $message The info message
 148       */
 149      public function setInfoMessage($message) {
 150          if ($message !== null) {
 151              $this->set_message('info', $message);
 152          }
 153      }
 154  
 155      /**
 156       * Return messages.
 157       * Implements getMessages.
 158       *
 159       * @param string $type The message type, e.g. 'info' or 'error'
 160       * @return string[] Array of messages
 161       */
 162      public function getMessages($type) {
 163          global $SESSION;
 164  
 165          // Return and reset messages.
 166          $messages = array();
 167          if (isset($SESSION->core_h5p_messages[$type])) {
 168              $messages = $SESSION->core_h5p_messages[$type];
 169              unset($SESSION->core_h5p_messages[$type]);
 170              if (empty($SESSION->core_h5p_messages)) {
 171                  unset($SESSION->core_h5p_messages);
 172              }
 173          }
 174  
 175          return $messages;
 176      }
 177  
 178      /**
 179       * Translation function.
 180       * The purpose of this function is to map the strings used in the core h5p methods
 181       * and replace them with the translated ones. If a translation for a particular string
 182       * is not available, the default message (key) will be returned.
 183       * Implements t.
 184       *
 185       * @param string $message The english string to be translated
 186       * @param array $replacements An associative array of replacements to make after translation
 187       * @return string Translated string or the english string if a translation is not available
 188       */
 189      public function t($message, $replacements = array()) {
 190  
 191          // Create mapping.
 192          $translationsmap = [
 193              'The file you uploaded is not a valid HTML5 Package (It does not have the .h5p file extension)' => 'noextension',
 194              'The file you uploaded is not a valid HTML5 Package (We are unable to unzip it)' => 'nounzip',
 195              'The main h5p.json file is not valid' => 'nojson',
 196              'Library directory name must match machineName or machineName-majorVersion.minorVersion (from library.json).' .
 197                  ' (Directory: %directoryName , machineName: %machineName, majorVersion: %majorVersion, minorVersion:' .
 198                  ' %minorVersion)'
 199                  => 'librarydirectoryerror',
 200              'A valid content folder is missing' => 'missingcontentfolder',
 201              'A valid main h5p.json file is missing' => 'invalidmainjson',
 202              'Missing required library @library' => 'missinglibrary',
 203              "Note that the libraries may exist in the file you uploaded, but you're not allowed to upload new libraries." .
 204                  ' Contact the site administrator about this.' => 'missinguploadpermissions',
 205              'Invalid library name: %name' => 'invalidlibraryname',
 206              'Could not find library.json file with valid json format for library %name' => 'missinglibraryjson',
 207              'Invalid semantics.json file has been included in the library %name' => 'invalidsemanticsjson',
 208              'Invalid language file %file in library %library' => 'invalidlanguagefile',
 209              'Invalid language file %languageFile has been included in the library %name' => 'invalidlanguagefile2',
 210              'The file "%file" is missing from library: "%name"' => 'missinglibraryfile',
 211              'The system was unable to install the <em>%component</em> component from the package, it requires a newer' .
 212                  ' version of the H5P plugin. This site is currently running version %current, whereas the required version' .
 213                  ' is %required or higher. You should consider upgrading and then try again.' => 'missingcoreversion',
 214              "Invalid data provided for %property in %library. Boolean expected." => 'invalidlibrarydataboolean',
 215              "Invalid data provided for %property in %library" => 'invalidlibrarydata',
 216              "Can't read the property %property in %library" => 'invalidlibraryproperty',
 217              'The required property %property is missing from %library' => 'missinglibraryproperty',
 218              'Illegal option %option in %library' => 'invalidlibraryoption',
 219              'Added %new new H5P library and updated %old old one.' => 'addedandupdatedss',
 220              'Added %new new H5P library and updated %old old ones.' => 'addedandupdatedsp',
 221              'Added %new new H5P libraries and updated %old old one.' => 'addedandupdatedps',
 222              'Added %new new H5P libraries and updated %old old ones.' => 'addedandupdatedpp',
 223              'Added %new new H5P library.' => 'addednewlibrary',
 224              'Added %new new H5P libraries.' => 'addednewlibraries',
 225              'Updated %old H5P library.' => 'updatedlibrary',
 226              'Updated %old H5P libraries.' => 'updatedlibraries',
 227              'Missing dependency @dep required by @lib.' => 'missingdependency',
 228              'Provided string is not valid according to regexp in semantics. (value: "%value", regexp: "%regexp")'
 229                  => 'invalidstring',
 230              'File "%filename" not allowed. Only files with the following extensions are allowed: %files-allowed.'
 231                  => 'invalidfile',
 232              'Invalid selected option in multi-select.' => 'invalidmultiselectoption',
 233              'Invalid selected option in select.' => 'invalidselectoption',
 234              'H5P internal error: unknown content type "@type" in semantics. Removing content!' => 'invalidsemanticstype',
 235              'Copyright information' => 'copyrightinfo',
 236              'Title' => 'title',
 237              'Author' => 'author',
 238              'Year(s)' => 'years',
 239              'Year' => 'year',
 240              'Source' => 'source',
 241              'License' => 'license',
 242              'Undisclosed' => 'undisclosed',
 243              'General Public License v3' => 'gpl',
 244              'Public Domain' => 'pd',
 245              'Public Domain Dedication and Licence' => 'pddl',
 246              'Public Domain Mark' => 'pdm',
 247              'Public Domain Mark (PDM)' => 'pdm',
 248              'Copyright' => 'copyrightstring',
 249              'The mbstring PHP extension is not loaded. H5P need this to function properly' => 'missingmbstring',
 250              'The version of the H5P library %machineName used in this content is not valid. Content contains %contentLibrary, ' .
 251                  'but it should be %semanticsLibrary.' => 'wrongversion',
 252              'The H5P library %library used in the content is not valid' => 'invalidlibrarynamed',
 253              'Fullscreen' => 'fullscreen',
 254              'Disable fullscreen' => 'disablefullscreen',
 255              'Download' => 'download',
 256              'Rights of use' => 'copyright',
 257              'Embed' => 'embed',
 258              'Size' => 'size',
 259              'Show advanced' => 'showadvanced',
 260              'Hide advanced' => 'hideadvanced',
 261              'Include this script on your website if you want dynamic sizing of the embedded content:' => 'resizescript',
 262              'Close' => 'close',
 263              'Thumbnail' => 'thumbnail',
 264              'No copyright information available for this content.' => 'nocopyright',
 265              'Download this content as a H5P file.' => 'downloadtitle',
 266              'View copyright information for this content.' => 'copyrighttitle',
 267              'View the embed code for this content.' => 'embedtitle',
 268              'Visit H5P.org to check out more cool content.' => 'h5ptitle',
 269              'This content has changed since you last used it.' => 'contentchanged',
 270              "You'll be starting over." => 'startingover',
 271              'by' => 'by',
 272              'Show more' => 'showmore',
 273              'Show less' => 'showless',
 274              'Sublevel' => 'sublevel',
 275              'Confirm action' => 'confirmdialogheader',
 276              'Please confirm that you wish to proceed. This action is not reversible.' => 'confirmdialogbody',
 277              'Cancel' => 'cancellabel',
 278              'Confirm' => 'confirmlabel',
 279              '4.0 International' => 'licenseCC40',
 280              '3.0 Unported' => 'licenseCC30',
 281              '2.5 Generic' => 'licenseCC25',
 282              '2.0 Generic' => 'licenseCC20',
 283              '1.0 Generic' => 'licenseCC10',
 284              'General Public License' => 'licenseGPL',
 285              'Version 3' => 'licenseV3',
 286              'Version 2' => 'licenseV2',
 287              'Version 1' => 'licenseV1',
 288              'CC0 1.0 Universal (CC0 1.0) Public Domain Dedication' => 'licenseCC010',
 289              'CC0 1.0 Universal' => 'licenseCC010U',
 290              'License Version' => 'licenseversion',
 291              'Creative Commons' => 'creativecommons',
 292              'Attribution' => 'ccattribution',
 293              'Attribution (CC BY)' => 'ccattribution',
 294              'Attribution-ShareAlike' => 'ccattributionsa',
 295              'Attribution-ShareAlike (CC BY-SA)' => 'ccattributionsa',
 296              'Attribution-NoDerivs' => 'ccattributionnd',
 297              'Attribution-NoDerivs (CC BY-ND)' => 'ccattributionnd',
 298              'Attribution-NonCommercial' => 'ccattributionnc',
 299              'Attribution-NonCommercial (CC BY-NC)' => 'ccattributionnc',
 300              'Attribution-NonCommercial-ShareAlike' => 'ccattributionncsa',
 301              'Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)' => 'ccattributionncsa',
 302              'Attribution-NonCommercial-NoDerivs' => 'ccattributionncnd',
 303              'Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)' => 'ccattributionncnd',
 304              'Public Domain Dedication (CC0)' => 'ccpdd',
 305              'Years (from)' => 'yearsfrom',
 306              'Years (to)' => 'yearsto',
 307              "Author's name" => 'authorname',
 308              "Author's role" => 'authorrole',
 309              'Editor' => 'editor',
 310              'Licensee' => 'licensee',
 311              'Originator' => 'originator',
 312              'Any additional information about the license' => 'additionallicenseinfo',
 313              'License Extras' => 'licenseextras',
 314              'Changelog' => 'changelog',
 315              'Content Type' => 'contenttype',
 316              'Date' => 'date',
 317              'Changed by' => 'changedby',
 318              'Description of change' => 'changedescription',
 319              'Photo cropped, text changed, etc.' => 'changeplaceholder',
 320              'Author comments' => 'authorcomments',
 321              'Comments for the editor of the content (This text will not be published as a part of copyright info)'
 322                  => 'authorcommentsdescription',
 323              'Reuse' => 'reuse',
 324              'Reuse Content' => 'reuseContent',
 325              'Reuse this content.' => 'reuseDescription',
 326              'Content is copied to the clipboard' => 'contentCopied',
 327              'Connection lost. Results will be stored and sent when you regain connection.' => 'connectionLost',
 328              'Connection reestablished.' => 'connectionReestablished',
 329              'Attempting to submit stored results.' => 'resubmitScores',
 330              'Your connection to the server was lost' => 'offlineDialogHeader',
 331              'We were unable to send information about your completion of this task. Please check your internet connection.'
 332                  => 'offlineDialogBody',
 333              'Retrying in :num....' => 'offlineDialogRetryMessage',
 334              'Retry now' => 'offlineDialogRetryButtonLabel',
 335              'Successfully submitted results.' => 'offlineSuccessfulSubmit',
 336              'One of the files inside the package exceeds the maximum file size allowed. (%file %used > %max)'
 337                  => 'fileExceedsMaxSize',
 338              'The total size of the unpacked files exceeds the maximum size allowed. (%used > %max)'
 339                  => 'unpackedFilesExceedsMaxSize',
 340              'Unable to read file from the package: %fileName' => 'couldNotReadFileFromZip',
 341              'Unable to parse JSON from the package: %fileName' => 'couldNotParseJSONFromZip',
 342              'A problem with the server write access was detected. Please make sure that your server can write to your data folder.' => 'nowriteaccess',
 343              'H5P hub communication has been disabled because one or more H5P requirements failed.' => 'hubcommunicationdisabled',
 344              'Site could not be registered with the hub. Please contact your site administrator.' => 'sitecouldnotberegistered',
 345              'The H5P Hub has been disabled until this problem can be resolved. You may still upload libraries through the "H5P Libraries" page.' => 'hubisdisableduploadlibraries',
 346              'When you have revised your server setup you may re-enable H5P hub communication in H5P Settings.' => 'reviseserversetupandretry',
 347              'You have been provided a unique key that identifies you with the Hub when receiving new updates. The key is available for viewing in the "H5P Settings" page.' => 'sitekeyregistered',
 348              'Your PHP max post size is quite small. With your current setup, you may not upload files larger than {$a->%number} MB. This might be a problem when trying to upload H5Ps, images and videos. Please consider to increase it to more than 5MB' => 'maxpostsizetoosmall',
 349              'Your PHP max upload size is bigger than your max post size. This is known to cause issues in some installations.' => 'uploadsizelargerthanpostsize',
 350              'Your PHP max upload size is quite small. With your current setup, you may not upload files larger than {$a->%number} MB. This might be a problem when trying to upload H5Ps, images and videos. Please consider to increase it to more than 5MB.' => 'maxuploadsizetoosmall',
 351              'Your PHP version does not support ZipArchive.' => 'noziparchive',
 352              'Your PHP version is outdated. H5P requires version 5.2 to function properly. Version 5.6 or later is recommended.' => 'oldphpversion',
 353              'Your server does not have SSL enabled. SSL should be enabled to ensure a secure connection with the H5P hub.' => 'sslnotenabled',
 354              'Your site was successfully registered with the H5P Hub.' => 'successfullyregisteredwithhub'
 355          ];
 356  
 357          if (isset($translationsmap[$message])) {
 358              return get_string($translationsmap[$message], 'core_h5p', $replacements);
 359          }
 360  
 361          debugging("String translation cannot be found. Please add a string definition for '" .
 362              $message . "' in the core_h5p component.", DEBUG_DEVELOPER);
 363  
 364          return $message;
 365      }
 366  
 367      /**
 368       * Get URL to file in the specifimake_pluginfile_urlc library.
 369       * Implements getLibraryFileUrl.
 370       *
 371       * @param string $libraryfoldername The name or path of the library's folder
 372       * @param string $filename The file name
 373       * @return string URL to file
 374       */
 375      public function getLibraryFileUrl($libraryfoldername, $filename) {
 376          global $DB;
 377  
 378          // Remove unnecessary slashes (first and last, if present) from the path to the folder
 379          // of the library file.
 380          $libraryfilepath = trim($libraryfoldername, '/');
 381  
 382          // Get the folder name of the library from the path.
 383          // The first element should represent the folder name of the library.
 384          $libfoldername = explode('/', $libraryfilepath)[0];
 385  
 386          $factory = new \core_h5p\factory();
 387          $core = $factory->get_core();
 388  
 389          // The provided folder name of the library must have a valid format (can be parsed).
 390          // The folder name is parsed with a purpose of getting the library related information
 391          // such as 'machineName', 'majorVersion' and 'minorVersion'.
 392          // This information is later used to retrieve the library ID.
 393          if (!$libdata = $core->libraryFromString($libfoldername, true)) {
 394              debugging('The provided string value "' . $libfoldername .
 395                  '" is not a valid name for a library folder.', DEBUG_DEVELOPER);
 396  
 397              return;
 398          }
 399  
 400          $params = array(
 401              'machinename' => $libdata['machineName'],
 402              'majorversion' => $libdata['majorVersion'],
 403              'minorversion' => $libdata['minorVersion']
 404          );
 405  
 406          $libraries = $DB->get_records('h5p_libraries', $params, 'patchversion DESC', 'id',
 407              0, 1);
 408  
 409          if (!$library = reset($libraries)) {
 410              debugging('The library "' . $libfoldername . '" does not exist.', DEBUG_DEVELOPER);
 411  
 412              return;
 413          }
 414  
 415          $context = \context_system::instance();
 416  
 417          return \moodle_url::make_pluginfile_url($context->id, 'core_h5p', 'libraries',
 418              $library->id, '/' . $libraryfilepath . '/', $filename)->out();
 419      }
 420  
 421      /**
 422       * Get the Path to the last uploaded h5p.
 423       * Implements getUploadedH5PFolderPath.
 424       *
 425       * @param string $setpath The path to the folder of the last uploaded h5p
 426       * @return string Path to the folder where the last uploaded h5p for this session is located
 427       */
 428      public function getUploadedH5pFolderPath($setpath = null) {
 429          if ($setpath !== null) {
 430              $this->lastuploadedfolder = $setpath;
 431          }
 432  
 433          if (!isset($this->lastuploadedfolder)) {
 434              throw new \coding_exception('Using getUploadedH5pFolderPath() before path is set');
 435          }
 436  
 437          return $this->lastuploadedfolder;
 438      }
 439  
 440      /**
 441       * Get the path to the last uploaded h5p file.
 442       * Implements getUploadedH5PPath.
 443       *
 444       * @param string $setpath The path to the last uploaded h5p
 445       * @return string Path to the last uploaded h5p
 446       */
 447      public function getUploadedH5pPath($setpath = null) {
 448          if ($setpath !== null) {
 449              $this->lastuploadedfile = $setpath;
 450          }
 451  
 452          if (!isset($this->lastuploadedfile)) {
 453              throw new \coding_exception('Using getUploadedH5pPath() before path is set');
 454          }
 455  
 456          return $this->lastuploadedfile;
 457      }
 458  
 459      /**
 460       * Load addon libraries.
 461       * Implements loadAddons.
 462       *
 463       * @return array The array containing the addon libraries
 464       */
 465      public function loadAddons() {
 466          global $DB;
 467  
 468          $addons = array();
 469  
 470          $records = $DB->get_records_sql(
 471                  "SELECT l1.id AS library_id,
 472                              l1.machinename AS machine_name,
 473                              l1.majorversion AS major_version,
 474                              l1.minorversion AS minor_version,
 475                              l1.patchversion AS patch_version,
 476                              l1.addto AS add_to,
 477                              l1.preloadedjs AS preloaded_js,
 478                              l1.preloadedcss AS preloaded_css
 479                         FROM {h5p_libraries} l1
 480                    LEFT JOIN {h5p_libraries} l2
 481                           ON l1.machinename = l2.machinename
 482                          AND (l1.majorversion < l2.majorversion
 483                               OR (l1.majorversion = l2.majorversion
 484                                   AND l1.minorversion < l2.minorversion))
 485                        WHERE l1.addto IS NOT NULL
 486                          AND l2.machinename IS NULL");
 487  
 488          // NOTE: These are treated as library objects but are missing the following properties:
 489          // title, droplibrarycss, fullscreen, runnable, semantics.
 490  
 491          // Extract num from records.
 492          foreach ($records as $addon) {
 493              $addons[] = H5PCore::snakeToCamel($addon);
 494          }
 495  
 496          return $addons;
 497      }
 498  
 499      /**
 500       * Load config for libraries.
 501       * Implements getLibraryConfig.
 502       *
 503       * @param array|null $libraries List of libraries
 504       * @return array|null The library config if it exists, null otherwise
 505       */
 506      public function getLibraryConfig($libraries = null) {
 507          global $CFG;
 508          return isset($CFG->core_h5p_library_config) ? $CFG->core_h5p_library_config : null;
 509      }
 510  
 511      /**
 512       * Get a list of the current installed libraries.
 513       * Implements loadLibraries.
 514       *
 515       * @return array Associative array containing one entry per machine name.
 516       *               For each machineName there is a list of libraries(with different versions).
 517       */
 518      public function loadLibraries() {
 519          global $DB;
 520  
 521          $results = $DB->get_records('h5p_libraries', [], 'title ASC, majorversion ASC, minorversion ASC',
 522              'id, machinename AS machine_name, majorversion AS major_version, minorversion AS minor_version,
 523              patchversion AS patch_version, runnable, title, enabled');
 524  
 525          $libraries = array();
 526          foreach ($results as $library) {
 527              $libraries[$library->machine_name][] = $library;
 528          }
 529  
 530          return $libraries;
 531      }
 532  
 533      /**
 534       * Returns the URL to the library admin page.
 535       * Implements getAdminUrl.
 536       *
 537       * @return string URL to admin page
 538       */
 539      public function getAdminUrl() {
 540          // Not supported.
 541      }
 542  
 543      /**
 544       * Return the library's ID.
 545       * Implements getLibraryId.
 546       *
 547       * @param string $machinename The librarys machine name
 548       * @param string $majorversion Major version number for library (optional)
 549       * @param string $minorversion Minor version number for library (optional)
 550       * @return int|bool Identifier, or false if non-existent
 551       */
 552      public function getLibraryId($machinename, $majorversion = null, $minorversion = null) {
 553          global $DB;
 554  
 555          $params = array(
 556              'machinename' => $machinename
 557          );
 558  
 559          if ($majorversion !== null) {
 560              $params['majorversion'] = $majorversion;
 561          }
 562  
 563          if ($minorversion !== null) {
 564              $params['minorversion'] = $minorversion;
 565          }
 566  
 567          $libraries = $DB->get_records('h5p_libraries', $params,
 568              'majorversion DESC, minorversion DESC, patchversion DESC', 'id', 0, 1);
 569  
 570          // Get the latest version which matches the input parameters.
 571          if ($libraries) {
 572              $library = reset($libraries);
 573              return $library->id ?? false;
 574          }
 575  
 576          return false;
 577      }
 578  
 579      /**
 580       * Get allowed file extension list.
 581       * Implements getWhitelist.
 582       *
 583       * The default extension list is part of h5p, but admins should be allowed to modify it.
 584       *
 585       * @param boolean $islibrary TRUE if this is the whitelist for a library. FALSE if it is the whitelist
 586       *                           for the content folder we are getting.
 587       * @param string $defaultcontentwhitelist A string of file extensions separated by whitespace.
 588       * @param string $defaultlibrarywhitelist A string of file extensions separated by whitespace.
 589       * @return string A string containing the allowed file extensions separated by whitespace.
 590       */
 591      public function getWhitelist($islibrary, $defaultcontentwhitelist, $defaultlibrarywhitelist) {
 592          return $defaultcontentwhitelist . ($islibrary ? ' ' . $defaultlibrarywhitelist : '');
 593      }
 594  
 595      /**
 596       * Is the library a patched version of an existing library?
 597       * Implements isPatchedLibrary.
 598       *
 599       * @param array $library An associative array containing:
 600       *                       - machineName: The library machine name
 601       *                       - majorVersion: The librarys major version
 602       *                       - minorVersion: The librarys minor version
 603       *                       - patchVersion: The librarys patch version
 604       * @return boolean TRUE if the library is a patched version of an existing library FALSE otherwise
 605       */
 606      public function isPatchedLibrary($library) {
 607          global $DB;
 608  
 609          $sql = "SELECT id
 610                    FROM {h5p_libraries}
 611                   WHERE machinename = :machinename
 612                     AND majorversion = :majorversion
 613                     AND minorversion = :minorversion
 614                     AND patchversion < :patchversion";
 615  
 616          $library = $DB->get_records_sql(
 617              $sql,
 618              array(
 619                  'machinename' => $library['machineName'],
 620                  'majorversion' => $library['majorVersion'],
 621                  'minorversion' => $library['minorVersion'],
 622                  'patchversion' => $library['patchVersion']
 623              ),
 624              0,
 625              1
 626          );
 627  
 628          return !empty($library);
 629      }
 630  
 631      /**
 632       * Is H5P in development mode?
 633       * Implements isInDevMode.
 634       *
 635       * @return boolean TRUE if H5P development mode is active FALSE otherwise
 636       */
 637      public function isInDevMode() {
 638          return false; // Not supported (Files in moodle not editable).
 639      }
 640  
 641      /**
 642       * Is the current user allowed to update libraries?
 643       * Implements mayUpdateLibraries.
 644       *
 645       * @return boolean TRUE if the user is allowed to update libraries,
 646       *                 FALSE if the user is not allowed to update libraries.
 647       */
 648      public function mayUpdateLibraries() {
 649          return helper::can_update_library($this->get_file());
 650      }
 651  
 652      /**
 653       * Get the .h5p file.
 654       *
 655       * @return stored_file The .h5p file.
 656       */
 657      public function get_file(): \stored_file {
 658          if (!isset($this->file)) {
 659              throw new \coding_exception('Using get_file() before file is set');
 660          }
 661  
 662          return $this->file;
 663      }
 664  
 665      /**
 666       * Set the .h5p file.
 667       *
 668       * @param  stored_file $file The .h5p file.
 669       */
 670      public function set_file(\stored_file $file): void {
 671          $this->file = $file;
 672      }
 673  
 674      /**
 675       * Store data about a library.
 676       * Implements saveLibraryData.
 677       *
 678       * Also fills in the libraryId in the libraryData object if the object is new.
 679       *
 680       * @param array $librarydata Associative array containing:
 681       *                           - libraryId: The id of the library if it is an existing library
 682       *                           - title: The library's name
 683       *                           - machineName: The library machineName
 684       *                           - majorVersion: The library's majorVersion
 685       *                           - minorVersion: The library's minorVersion
 686       *                           - patchVersion: The library's patchVersion
 687       *                           - runnable: 1 if the library is a content type, 0 otherwise
 688       *                           - fullscreen(optional): 1 if the library supports fullscreen, 0 otherwise
 689       *                           - embedtypes: list of supported embed types
 690       *                           - preloadedJs(optional): list of associative arrays containing:
 691       *                             - path: path to a js file relative to the library root folder
 692       *                           - preloadedCss(optional): list of associative arrays containing:
 693       *                             - path: path to css file relative to the library root folder
 694       *                           - dropLibraryCss(optional): list of associative arrays containing:
 695       *                             - machineName: machine name for the librarys that are to drop their css
 696       *                           - semantics(optional): Json describing the content structure for the library
 697       *                           - metadataSettings(optional): object containing:
 698       *                             - disable: 1 if metadata is disabled completely
 699       *                             - disableExtraTitleField: 1 if the title field is hidden in the form
 700       * @param bool $new Whether it is a new or existing library.
 701       */
 702      public function saveLibraryData(&$librarydata, $new = true) {
 703          global $DB;
 704  
 705          // Some special properties needs some checking and converting before they can be saved.
 706          $preloadedjs = $this->library_parameter_values_to_csv($librarydata, 'preloadedJs', 'path');
 707          $preloadedcss = $this->library_parameter_values_to_csv($librarydata, 'preloadedCss', 'path');
 708          $droplibrarycss = $this->library_parameter_values_to_csv($librarydata, 'dropLibraryCss', 'machineName');
 709  
 710          if (!isset($librarydata['semantics'])) {
 711              $librarydata['semantics'] = '';
 712          }
 713          if (!isset($librarydata['fullscreen'])) {
 714              $librarydata['fullscreen'] = 0;
 715          }
 716          $embedtypes = '';
 717          if (isset($librarydata['embedTypes'])) {
 718              $embedtypes = implode(', ', $librarydata['embedTypes']);
 719          }
 720  
 721          $library = (object) array(
 722              'title' => $librarydata['title'],
 723              'machinename' => $librarydata['machineName'],
 724              'majorversion' => $librarydata['majorVersion'],
 725              'minorversion' => $librarydata['minorVersion'],
 726              'patchversion' => $librarydata['patchVersion'],
 727              'runnable' => $librarydata['runnable'],
 728              'fullscreen' => $librarydata['fullscreen'],
 729              'embedtypes' => $embedtypes,
 730              'preloadedjs' => $preloadedjs,
 731              'preloadedcss' => $preloadedcss,
 732              'droplibrarycss' => $droplibrarycss,
 733              'semantics' => $librarydata['semantics'],
 734              'addto' => isset($librarydata['addTo']) ? json_encode($librarydata['addTo']) : null,
 735              'coremajor' => isset($librarydata['coreApi']['majorVersion']) ? $librarydata['coreApi']['majorVersion'] : null,
 736              'coreminor' => isset($librarydata['coreApi']['majorVersion']) ? $librarydata['coreApi']['minorVersion'] : null,
 737              'metadatasettings' => isset($librarydata['metadataSettings']) ? $librarydata['metadataSettings'] : null,
 738          );
 739  
 740          if ($new) {
 741              // Create new library and keep track of id.
 742              $library->id = $DB->insert_record('h5p_libraries', $library);
 743              $librarydata['libraryId'] = $library->id;
 744          } else {
 745              // Update library data.
 746              $library->id = $librarydata['libraryId'];
 747              // Save library data.
 748              $DB->update_record('h5p_libraries', $library);
 749              // Remove old dependencies.
 750              $this->deleteLibraryDependencies($librarydata['libraryId']);
 751          }
 752      }
 753  
 754      /**
 755       * Insert new content.
 756       * Implements insertContent.
 757       *
 758       * @param array $content An associative array containing:
 759       *                       - id: The content id
 760       *                       - params: The content in json format
 761       *                       - library: An associative array containing:
 762       *                         - libraryId: The id of the main library for this content
 763       *                       - disable: H5P Button display options
 764       *                       - pathnamehash: The pathnamehash linking the record with the entry in the mdl_files table
 765       *                       - contenthash: The contenthash linking the record with the entry in the mdl_files table
 766       * @param int $contentmainid Main id for the content if this is a system that supports versions
 767       * @return int The ID of the newly inserted content
 768       */
 769      public function insertContent($content, $contentmainid = null) {
 770          return $this->updateContent($content);
 771      }
 772  
 773      /**
 774       * Update old content or insert new content.
 775       * Implements updateContent.
 776       *
 777       * @param array $content An associative array containing:
 778       *                       - id: The content id
 779       *                       - params: The content in json format
 780       *                       - library: An associative array containing:
 781       *                         - libraryId: The id of the main library for this content
 782       *                       - disable: H5P Button display options
 783       *                       - pathnamehash: The pathnamehash linking the record with the entry in the mdl_files table
 784       *                       - contenthash: The contenthash linking the record with the entry in the mdl_files table
 785       * @param int $contentmainid Main id for the content if this is a system that supports versions
 786       * @return int The ID of the newly inserted or updated content
 787       */
 788      public function updateContent($content, $contentmainid = null) {
 789          global $DB;
 790  
 791          if (!isset($content['pathnamehash'])) {
 792              $content['pathnamehash'] = '';
 793          }
 794  
 795          if (!isset($content['contenthash'])) {
 796              $content['contenthash'] = '';
 797          }
 798  
 799          // If the libraryid declared in the package is empty, get the latest version.
 800          if (empty($content['library']['libraryId'])) {
 801              $mainlibrary = $this->get_latest_library_version($content['library']['machineName']);
 802              if (empty($mainlibrary)) {
 803                  // Raise an error if the main library is not defined and the latest version doesn't exist.
 804                  $message = $this->t('Missing required library @library', ['@library' => $content['library']['machineName']]);
 805                  $this->setErrorMessage($message, 'missing-required-library');
 806                  return false;
 807              }
 808              $content['library']['libraryId'] = $mainlibrary->id;
 809          }
 810  
 811          $content['disable'] = $content['disable'] ?? null;
 812          // Add title to 'params' to use in the editor.
 813          if (!empty($content['title'])) {
 814              $params = json_decode($content['params']);
 815              $params->title = $content['title'];
 816              $content['params'] = json_encode($params);
 817          }
 818          $data = [
 819              'jsoncontent' => $content['params'],
 820              'displayoptions' => $content['disable'],
 821              'mainlibraryid' => $content['library']['libraryId'],
 822              'timemodified' => time(),
 823              'filtered' => null,
 824              'pathnamehash' => $content['pathnamehash'],
 825              'contenthash' => $content['contenthash']
 826          ];
 827  
 828          if (!isset($content['id'])) {
 829              $data['timecreated'] = $data['timemodified'];
 830              $id = $DB->insert_record('h5p', $data);
 831          } else {
 832              $id = $data['id'] = $content['id'];
 833              $DB->update_record('h5p', $data);
 834          }
 835  
 836          return $id;
 837      }
 838  
 839      /**
 840       * Resets marked user data for the given content.
 841       * Implements resetContentUserData.
 842       *
 843       * @param int $contentid The h5p content id
 844       */
 845      public function resetContentUserData($contentid) {
 846          // Currently, we do not store user data for a content.
 847      }
 848  
 849      /**
 850       * Save what libraries a library is depending on.
 851       * Implements saveLibraryDependencies.
 852       *
 853       * @param int $libraryid Library Id for the library we're saving dependencies for
 854       * @param array $dependencies List of dependencies as associative arrays containing:
 855       *                            - machineName: The library machineName
 856       *                            - majorVersion: The library's majorVersion
 857       *                            - minorVersion: The library's minorVersion
 858       * @param string $dependencytype The type of dependency
 859       */
 860      public function saveLibraryDependencies($libraryid, $dependencies, $dependencytype) {
 861          global $DB;
 862  
 863          foreach ($dependencies as $dependency) {
 864              // Find dependency library.
 865              $dependencylibrary = $DB->get_record('h5p_libraries',
 866                  array(
 867                      'machinename' => $dependency['machineName'],
 868                      'majorversion' => $dependency['majorVersion'],
 869                      'minorversion' => $dependency['minorVersion']
 870                  )
 871              );
 872  
 873              // Create relation.
 874              $DB->insert_record('h5p_library_dependencies', array(
 875                  'libraryid' => $libraryid,
 876                  'requiredlibraryid' => $dependencylibrary->id,
 877                  'dependencytype' => $dependencytype
 878              ));
 879          }
 880      }
 881  
 882      /**
 883       * Give an H5P the same library dependencies as a given H5P.
 884       * Implements copyLibraryUsage.
 885       *
 886       * @param int $contentid Id identifying the content
 887       * @param int $copyfromid Id identifying the content to be copied
 888       * @param int $contentmainid Main id for the content, typically used in frameworks
 889       */
 890      public function copyLibraryUsage($contentid, $copyfromid, $contentmainid = null) {
 891          // Currently not being called.
 892      }
 893  
 894      /**
 895       * Deletes content data.
 896       * Implements deleteContentData.
 897       *
 898       * @param int $contentid Id identifying the content
 899       */
 900      public function deleteContentData($contentid) {
 901          global $DB;
 902  
 903          // Remove content.
 904          $DB->delete_records('h5p', array('id' => $contentid));
 905  
 906          // Remove content library dependencies.
 907          $this->deleteLibraryUsage($contentid);
 908      }
 909  
 910      /**
 911       * Delete what libraries a content item is using.
 912       * Implements deleteLibraryUsage.
 913       *
 914       * @param int $contentid Content Id of the content we'll be deleting library usage for
 915       */
 916      public function deleteLibraryUsage($contentid) {
 917          global $DB;
 918  
 919          $DB->delete_records('h5p_contents_libraries', array('h5pid' => $contentid));
 920      }
 921  
 922      /**
 923       * Saves what libraries the content uses.
 924       * Implements saveLibraryUsage.
 925       *
 926       * @param int $contentid Id identifying the content
 927       * @param array $librariesinuse List of libraries the content uses
 928       */
 929      public function saveLibraryUsage($contentid, $librariesinuse) {
 930          global $DB;
 931  
 932          $droplibrarycsslist = array();
 933          foreach ($librariesinuse as $dependency) {
 934              if (!empty($dependency['library']['dropLibraryCss'])) {
 935                  $droplibrarycsslist = array_merge($droplibrarycsslist,
 936                          explode(', ', $dependency['library']['dropLibraryCss']));
 937              }
 938          }
 939  
 940          foreach ($librariesinuse as $dependency) {
 941              $dropcss = in_array($dependency['library']['machineName'], $droplibrarycsslist) ? 1 : 0;
 942              $DB->insert_record('h5p_contents_libraries', array(
 943                  'h5pid' => $contentid,
 944                  'libraryid' => $dependency['library']['libraryId'],
 945                  'dependencytype' => $dependency['type'],
 946                  'dropcss' => $dropcss,
 947                  'weight' => $dependency['weight']
 948              ));
 949          }
 950      }
 951  
 952      /**
 953       * Get number of content/nodes using a library, and the number of dependencies to other libraries.
 954       * Implements getLibraryUsage.
 955       *
 956       * @param int $id Library identifier
 957       * @param boolean $skipcontent Optional. Set as true to get number of content instances for library
 958       * @return array The array contains two elements, keyed by 'content' and 'libraries'.
 959       *               Each element contains a number
 960       */
 961      public function getLibraryUsage($id, $skipcontent = false) {
 962          global $DB;
 963  
 964          if ($skipcontent) {
 965              $content = -1;
 966          } else {
 967              $sql = "SELECT COUNT(distinct c.id)
 968                        FROM {h5p_libraries} l
 969                        JOIN {h5p_contents_libraries} cl ON l.id = cl.libraryid
 970                        JOIN {h5p} c ON cl.h5pid = c.id
 971                       WHERE l.id = :libraryid";
 972  
 973              $sqlargs = array(
 974                  'libraryid' => $id
 975              );
 976  
 977              $content = $DB->count_records_sql($sql, $sqlargs);
 978          }
 979  
 980          $libraries = $DB->count_records('h5p_library_dependencies', ['requiredlibraryid' => $id]);
 981  
 982          return array(
 983              'content' => $content,
 984              'libraries' => $libraries,
 985          );
 986      }
 987  
 988      /**
 989       * Loads a library.
 990       * Implements loadLibrary.
 991       *
 992       * @param string $machinename The library's machine name
 993       * @param int $majorversion The library's major version
 994       * @param int $minorversion The library's minor version
 995       * @return array|bool Returns FALSE if the library does not exist
 996       *                     Otherwise an associative array containing:
 997       *                     - libraryId: The id of the library if it is an existing library,
 998       *                     - title: The library's name,
 999       *                     - machineName: The library machineName
1000       *                     - majorVersion: The library's majorVersion
1001       *                     - minorVersion: The library's minorVersion
1002       *                     - patchVersion: The library's patchVersion
1003       *                     - runnable: 1 if the library is a content type, 0 otherwise
1004       *                     - fullscreen: 1 if the library supports fullscreen, 0 otherwise
1005       *                     - embedTypes: list of supported embed types
1006       *                     - preloadedJs: comma separated string with js file paths
1007       *                     - preloadedCss: comma separated sting with css file paths
1008       *                     - dropLibraryCss: list of associative arrays containing:
1009       *                       - machineName: machine name for the librarys that are to drop their css
1010       *                     - semantics: Json describing the content structure for the library
1011       *                     - preloadedDependencies(optional): list of associative arrays containing:
1012       *                       - machineName: Machine name for a library this library is depending on
1013       *                       - majorVersion: Major version for a library this library is depending on
1014       *                       - minorVersion: Minor for a library this library is depending on
1015       *                     - dynamicDependencies(optional): list of associative arrays containing:
1016       *                       - machineName: Machine name for a library this library is depending on
1017       *                       - majorVersion: Major version for a library this library is depending on
1018       *                       - minorVersion: Minor for a library this library is depending on
1019       */
1020      public function loadLibrary($machinename, $majorversion, $minorversion) {
1021          global $DB;
1022  
1023          $library = $DB->get_record('h5p_libraries', array(
1024              'machinename' => $machinename,
1025              'majorversion' => $majorversion,
1026              'minorversion' => $minorversion
1027          ));
1028  
1029          if (!$library) {
1030              return false;
1031          }
1032  
1033          $librarydata = array(
1034              'libraryId' => $library->id,
1035              'title' => $library->title,
1036              'machineName' => $library->machinename,
1037              'majorVersion' => $library->majorversion,
1038              'minorVersion' => $library->minorversion,
1039              'patchVersion' => $library->patchversion,
1040              'runnable' => $library->runnable,
1041              'fullscreen' => $library->fullscreen,
1042              'embedTypes' => $library->embedtypes,
1043              'preloadedJs' => $library->preloadedjs,
1044              'preloadedCss' => $library->preloadedcss,
1045              'dropLibraryCss' => $library->droplibrarycss,
1046              'semantics'     => $library->semantics
1047          );
1048  
1049          $sql = 'SELECT hl.id, hl.machinename, hl.majorversion, hl.minorversion, hll.dependencytype
1050                    FROM {h5p_library_dependencies} hll
1051                    JOIN {h5p_libraries} hl ON hll.requiredlibraryid = hl.id
1052                   WHERE hll.libraryid = :libraryid
1053                ORDER BY hl.id ASC';
1054  
1055          $sqlargs = array(
1056              'libraryid' => $library->id
1057          );
1058  
1059          $dependencies = $DB->get_records_sql($sql, $sqlargs);
1060  
1061          foreach ($dependencies as $dependency) {
1062              $librarydata[$dependency->dependencytype . 'Dependencies'][] = array(
1063                  'machineName' => $dependency->machinename,
1064                  'majorVersion' => $dependency->majorversion,
1065                  'minorVersion' => $dependency->minorversion
1066              );
1067          }
1068  
1069          return $librarydata;
1070      }
1071  
1072      /**
1073       * Loads library semantics.
1074       * Implements loadLibrarySemantics.
1075       *
1076       * @param string $name Machine name for the library
1077       * @param int $majorversion The library's major version
1078       * @param int $minorversion The library's minor version
1079       * @return string The library's semantics as json
1080       */
1081      public function loadLibrarySemantics($name, $majorversion, $minorversion) {
1082          global $DB;
1083  
1084          $semantics = $DB->get_field('h5p_libraries', 'semantics',
1085              array(
1086                  'machinename' => $name,
1087                  'majorversion' => $majorversion,
1088                  'minorversion' => $minorversion
1089              )
1090          );
1091  
1092          return ($semantics === false ? null : $semantics);
1093      }
1094  
1095      /**
1096       * Makes it possible to alter the semantics, adding custom fields, etc.
1097       * Implements alterLibrarySemantics.
1098       *
1099       * @param array $semantics Associative array representing the semantics
1100       * @param string $name The library's machine name
1101       * @param int $majorversion The library's major version
1102       * @param int $minorversion The library's minor version
1103       */
1104      public function alterLibrarySemantics(&$semantics, $name, $majorversion, $minorversion) {
1105          global $PAGE;
1106  
1107          $renderer = $PAGE->get_renderer('core_h5p');
1108          $renderer->h5p_alter_semantics($semantics, $name, $majorversion, $minorversion);
1109      }
1110  
1111      /**
1112       * Delete all dependencies belonging to given library.
1113       * Implements deleteLibraryDependencies.
1114       *
1115       * @param int $libraryid Library identifier
1116       */
1117      public function deleteLibraryDependencies($libraryid) {
1118          global $DB;
1119  
1120          $DB->delete_records('h5p_library_dependencies', array('libraryid' => $libraryid));
1121      }
1122  
1123      /**
1124       * Start an atomic operation against the dependency storage.
1125       * Implements lockDependencyStorage.
1126       */
1127      public function lockDependencyStorage() {
1128          // Library development mode not supported.
1129      }
1130  
1131      /**
1132       * Start an atomic operation against the dependency storage.
1133       * Implements unlockDependencyStorage.
1134       */
1135      public function unlockDependencyStorage() {
1136          // Library development mode not supported.
1137      }
1138  
1139      /**
1140       * Delete a library from database and file system.
1141       * Implements deleteLibrary.
1142       *
1143       * @param stdClass $library Library object with id, name, major version and minor version
1144       */
1145      public function deleteLibrary($library) {
1146          $factory = new \core_h5p\factory();
1147          \core_h5p\api::delete_library($factory, $library);
1148      }
1149  
1150      /**
1151       * Load content.
1152       * Implements loadContent.
1153       *
1154       * @param int $id Content identifier
1155       * @return array Associative array containing:
1156       *               - id: Identifier for the content
1157       *               - params: json content as string
1158       *               - embedType: list of supported embed types
1159       *               - disable: H5P Button display options
1160       *               - title: H5P content title
1161       *               - slug: Human readable content identifier that is unique
1162       *               - libraryId: Id for the main library
1163       *               - libraryName: The library machine name
1164       *               - libraryMajorVersion: The library's majorVersion
1165       *               - libraryMinorVersion: The library's minorVersion
1166       *               - libraryEmbedTypes: CSV of the main library's embed types
1167       *               - libraryFullscreen: 1 if fullscreen is supported. 0 otherwise
1168       *               - metadata: The content's metadata
1169       */
1170      public function loadContent($id) {
1171          global $DB;
1172  
1173          $sql = "SELECT hc.id, hc.jsoncontent, hc.displayoptions, hl.id AS libraryid,
1174                         hl.machinename, hl.title, hl.majorversion, hl.minorversion, hl.fullscreen,
1175                         hl.embedtypes, hl.semantics, hc.filtered, hc.pathnamehash
1176                    FROM {h5p} hc
1177                    JOIN {h5p_libraries} hl ON hl.id = hc.mainlibraryid
1178                   WHERE hc.id = :h5pid";
1179  
1180          $sqlargs = array(
1181              'h5pid' => $id
1182          );
1183  
1184          $data = $DB->get_record_sql($sql, $sqlargs);
1185  
1186          // Return null if not found.
1187          if ($data === false) {
1188              return null;
1189          }
1190  
1191          // Some databases do not support camelCase, so we need to manually
1192          // map the values to the camelCase names used by the H5P core.
1193          $content = array(
1194              'id' => $data->id,
1195              'params' => $data->jsoncontent,
1196              // It has been decided that the embedtype will be always set to 'iframe' (at least for now) because the 'div'
1197              // may cause conflicts with CSS and JS in some cases.
1198              'embedType' => 'iframe',
1199              'disable' => $data->displayoptions,
1200              'title' => $data->title,
1201              'slug' => H5PCore::slugify($data->title) . '-' . $data->id,
1202              'filtered' => $data->filtered,
1203              'libraryId' => $data->libraryid,
1204              'libraryName' => $data->machinename,
1205              'libraryMajorVersion' => $data->majorversion,
1206              'libraryMinorVersion' => $data->minorversion,
1207              'libraryEmbedTypes' => $data->embedtypes,
1208              'libraryFullscreen' => $data->fullscreen,
1209              'metadata' => '',
1210              'pathnamehash' => $data->pathnamehash
1211          );
1212  
1213          $params = json_decode($data->jsoncontent);
1214          if (empty($params->metadata)) {
1215              $params->metadata = new \stdClass();
1216          }
1217          // Add title to metadata.
1218          if (!empty($params->title) && empty($params->metadata->title)) {
1219              $params->metadata->title = $params->title;
1220          }
1221          $content['metadata'] = $params->metadata;
1222          $content['params'] = json_encode($params->params ?? $params);
1223  
1224          return $content;
1225      }
1226  
1227      /**
1228       * Load dependencies for the given content of the given type.
1229       * Implements loadContentDependencies.
1230       *
1231       * @param int $id Content identifier
1232       * @param int $type The dependency type
1233       * @return array List of associative arrays containing:
1234       *               - libraryId: The id of the library if it is an existing library
1235       *               - machineName: The library machineName
1236       *               - majorVersion: The library's majorVersion
1237       *               - minorVersion: The library's minorVersion
1238       *               - patchVersion: The library's patchVersion
1239       *               - preloadedJs(optional): comma separated string with js file paths
1240       *               - preloadedCss(optional): comma separated sting with css file paths
1241       *               - dropCss(optional): csv of machine names
1242       *               - dependencyType: The dependency type
1243       */
1244      public function loadContentDependencies($id, $type = null) {
1245          global $DB;
1246  
1247          $query = "SELECT hcl.id AS unidepid, hl.id AS library_id, hl.machinename AS machine_name,
1248                           hl.majorversion AS major_version, hl.minorversion AS minor_version,
1249                           hl.patchversion AS patch_version, hl.preloadedcss AS preloaded_css,
1250                           hl.preloadedjs AS preloaded_js, hcl.dropcss AS drop_css,
1251                           hcl.dependencytype as dependency_type
1252                      FROM {h5p_contents_libraries} hcl
1253                      JOIN {h5p_libraries} hl ON hcl.libraryid = hl.id
1254                     WHERE hcl.h5pid = :h5pid";
1255          $queryargs = array(
1256              'h5pid' => $id
1257          );
1258  
1259          if ($type !== null) {
1260              $query .= " AND hcl.dependencytype = :dependencytype";
1261              $queryargs['dependencytype'] = $type;
1262          }
1263  
1264          $query .= " ORDER BY hcl.weight";
1265          $data = $DB->get_records_sql($query, $queryargs);
1266  
1267          $dependencies = array();
1268          foreach ($data as $dependency) {
1269              unset($dependency->unidepid);
1270              $dependencies[$dependency->machine_name] = H5PCore::snakeToCamel($dependency);
1271          }
1272  
1273          return $dependencies;
1274      }
1275  
1276      /**
1277       * Get stored setting.
1278       * Implements getOption.
1279       *
1280       * To avoid updating the cache libraries when using the Hub selector,
1281       * {@see \Moodle\H5PEditorAjax::isContentTypeCacheUpdated}, the setting content_type_cache_updated_at
1282       * always return the current time.
1283       *
1284       * @param string $name Identifier for the setting
1285       * @param string $default Optional default value if settings is not set
1286       * @return mixed Return  Whatever has been stored as the setting
1287       */
1288      public function getOption($name, $default = false) {
1289          if ($name == core::DISPLAY_OPTION_DOWNLOAD || $name == core::DISPLAY_OPTION_EMBED) {
1290              // For now, the download and the embed displayoptions are disabled by default, so only will be rendered when
1291              // defined in the displayoptions DB field.
1292              // This check should be removed if they are added as new H5P settings, to let admins to define the default value.
1293              return \Moodle\H5PDisplayOptionBehaviour::CONTROLLED_BY_AUTHOR_DEFAULT_OFF;
1294          }
1295  
1296          // To avoid update the libraries cache using the Hub selector.
1297          if ($name == 'content_type_cache_updated_at') {
1298              return time();
1299          }
1300  
1301          $value = get_config('core_h5p', $name);
1302          if ($value === false) {
1303              return $default;
1304          }
1305          return $value;
1306      }
1307  
1308      /**
1309       * Stores the given setting.
1310       * For example when did we last check h5p.org for updates to our libraries.
1311       * Implements setOption.
1312       *
1313       * @param string $name Identifier for the setting
1314       * @param mixed $value Data Whatever we want to store as the setting
1315       */
1316      public function setOption($name, $value) {
1317          set_config($name, $value, 'core_h5p');
1318      }
1319  
1320      /**
1321       * This will update selected fields on the given content.
1322       * Implements updateContentFields().
1323       *
1324       * @param int $id Content identifier
1325       * @param array $fields Content fields, e.g. filtered
1326       */
1327      public function updateContentFields($id, $fields) {
1328          global $DB;
1329  
1330          $content = new \stdClass();
1331          $content->id = $id;
1332  
1333          foreach ($fields as $name => $value) {
1334              // Skip 'slug' as it currently does not exist in the h5p content table.
1335              if ($name == 'slug') {
1336                  continue;
1337              }
1338  
1339              $content->$name = $value;
1340          }
1341  
1342          $DB->update_record('h5p', $content);
1343      }
1344  
1345      /**
1346       * Will clear filtered params for all the content that uses the specified.
1347       * libraries. This means that the content dependencies will have to be rebuilt and the parameters re-filtered.
1348       * Implements clearFilteredParameters().
1349       *
1350       * @param array $libraryids Array of library ids
1351       */
1352      public function clearFilteredParameters($libraryids) {
1353          global $DB;
1354  
1355          if (empty($libraryids)) {
1356              return;
1357          }
1358  
1359          list($insql, $inparams) = $DB->get_in_or_equal($libraryids);
1360  
1361          $DB->set_field_select('h5p', 'filtered', null,
1362              "mainlibraryid $insql", $inparams);
1363      }
1364  
1365      /**
1366       * Get number of contents that has to get their content dependencies rebuilt.
1367       * and parameters re-filtered.
1368       * Implements getNumNotFiltered().
1369       *
1370       * @return int The number of contents that has to get their content dependencies rebuilt
1371       *             and parameters re-filtered
1372       */
1373      public function getNumNotFiltered() {
1374          global $DB;
1375  
1376          $sql = "SELECT COUNT(id)
1377                    FROM {h5p}
1378                   WHERE " . $DB->sql_compare_text('filtered') . " IS NULL";
1379  
1380          return $DB->count_records_sql($sql);
1381      }
1382  
1383      /**
1384       * Get number of contents using library as main library.
1385       * Implements getNumContent().
1386       *
1387       * @param int $libraryid The library ID
1388       * @param array $skip The array of h5p content ID's that should be ignored
1389       * @return int The number of contents using library as main library
1390       */
1391      public function getNumContent($libraryid, $skip = null) {
1392          global $DB;
1393  
1394          $notinsql = '';
1395          $params = array();
1396  
1397          if (!empty($skip)) {
1398              list($sql, $params) = $DB->get_in_or_equal($skip, SQL_PARAMS_NAMED, 'param', false);
1399              $notinsql = " AND id {$sql}";
1400          }
1401  
1402          $sql = "SELECT COUNT(id)
1403                    FROM {h5p}
1404                   WHERE mainlibraryid = :libraryid {$notinsql}";
1405  
1406          $params['libraryid'] = $libraryid;
1407  
1408          return $DB->count_records_sql($sql, $params);
1409      }
1410  
1411      /**
1412       * Determines if content slug is used.
1413       * Implements isContentSlugAvailable.
1414       *
1415       * @param string $slug The content slug
1416       * @return boolean Whether the content slug is used
1417       */
1418      public function isContentSlugAvailable($slug) {
1419          // By default the slug should be available as it's currently generated as a unique
1420          // value for each h5p content (not stored in the h5p table).
1421          return true;
1422      }
1423  
1424      /**
1425       * Generates statistics from the event log per library.
1426       * Implements getLibraryStats.
1427       *
1428       * @param string $type Type of event to generate stats for
1429       * @return array Number values indexed by library name and version
1430       */
1431      public function getLibraryStats($type) {
1432          // Event logs are not being stored.
1433      }
1434  
1435      /**
1436       * Aggregate the current number of H5P authors.
1437       * Implements getNumAuthors.
1438       *
1439       * @return int The current number of H5P authors
1440       */
1441      public function getNumAuthors() {
1442          // Currently, H5P authors are not being stored.
1443      }
1444  
1445      /**
1446       * Stores hash keys for cached assets, aggregated JavaScripts and
1447       * stylesheets, and connects it to libraries so that we know which cache file
1448       * to delete when a library is updated.
1449       * Implements saveCachedAssets.
1450       *
1451       * @param string $key Hash key for the given libraries
1452       * @param array $libraries List of dependencies(libraries) used to create the key
1453       */
1454      public function saveCachedAssets($key, $libraries) {
1455          global $DB;
1456  
1457          foreach ($libraries as $library) {
1458              $cachedasset = new \stdClass();
1459              $cachedasset->libraryid = $library['libraryId'];
1460              $cachedasset->hash = $key;
1461  
1462              $DB->insert_record('h5p_libraries_cachedassets', $cachedasset);
1463          }
1464      }
1465  
1466      /**
1467       * Locate hash keys for given library and delete them.
1468       * Used when cache file are deleted.
1469       * Implements deleteCachedAssets.
1470       *
1471       * @param int $libraryid Library identifier
1472       * @return array List of hash keys removed
1473       */
1474      public function deleteCachedAssets($libraryid) {
1475          global $DB;
1476  
1477          // Get all the keys so we can remove the files.
1478          $results = $DB->get_records('h5p_libraries_cachedassets', ['libraryid' => $libraryid]);
1479  
1480          $hashes = array_map(function($result) {
1481              return $result->hash;
1482          }, $results);
1483  
1484          if (!empty($hashes)) {
1485              list($sql, $params) = $DB->get_in_or_equal($hashes, SQL_PARAMS_NAMED);
1486              // Remove all invalid keys.
1487              $DB->delete_records_select('h5p_libraries_cachedassets', 'hash ' . $sql, $params);
1488  
1489              // Remove also the cachedassets files.
1490              $fs = new file_storage();
1491              $fs->deleteCachedAssets($hashes);
1492          }
1493  
1494          return $hashes;
1495      }
1496  
1497      /**
1498       * Get the amount of content items associated to a library.
1499       * Implements getLibraryContentCount.
1500       *
1501       * return array The number of content items associated to a library
1502       */
1503      public function getLibraryContentCount() {
1504          global $DB;
1505  
1506          $contentcount = array();
1507  
1508          $sql = "SELECT h.mainlibraryid,
1509                         l.machinename,
1510                         l.majorversion,
1511                         l.minorversion,
1512                         COUNT(h.id) AS count
1513                    FROM {h5p} h
1514               LEFT JOIN {h5p_libraries} l
1515                      ON h.mainlibraryid = l.id
1516                GROUP BY h.mainlibraryid, l.machinename, l.majorversion, l.minorversion";
1517  
1518          // Count content using the same content type.
1519          $res = $DB->get_records_sql($sql);
1520  
1521          // Extract results.
1522          foreach ($res as $lib) {
1523              $contentcount["{$lib->machinename} {$lib->majorversion}.{$lib->minorversion}"] = $lib->count;
1524          }
1525  
1526          return $contentcount;
1527      }
1528  
1529      /**
1530       * Will trigger after the export file is created.
1531       * Implements afterExportCreated.
1532       *
1533       * @param array $content The content
1534       * @param string $filename The file name
1535       */
1536      public function afterExportCreated($content, $filename) {
1537          // Not being used.
1538      }
1539  
1540      /**
1541       * Check whether a user has permissions to execute an action, such as embed H5P content.
1542       * Implements hasPermission.
1543       *
1544       * @param  H5PPermission $permission Permission type
1545       * @param  int $id Id need by platform to determine permission
1546       * @return boolean true if the user can execute the action defined in $permission; false otherwise
1547       */
1548      public function hasPermission($permission, $id = null) {
1549          // H5P capabilities have not been introduced.
1550      }
1551  
1552      /**
1553       * Replaces existing content type cache with the one passed in.
1554       * Implements replaceContentTypeCache.
1555       *
1556       * @param object $contenttypecache Json with an array called 'libraries' containing the new content type cache
1557       *                                 that should replace the old one
1558       */
1559      public function replaceContentTypeCache($contenttypecache) {
1560          // Currently, content type caches are not being stored.
1561      }
1562  
1563      /**
1564       * Checks if the given library has a higher version.
1565       * Implements libraryHasUpgrade.
1566       *
1567       * @param array $library An associative array containing:
1568       *                       - machineName: The library machineName
1569       *                       - majorVersion: The library's majorVersion
1570       *                       - minorVersion: The library's minorVersion
1571       * @return boolean Whether the library has a higher version
1572       */
1573      public function libraryHasUpgrade($library) {
1574          global $DB;
1575  
1576          $sql = "SELECT id
1577                    FROM {h5p_libraries}
1578                   WHERE machinename = :machinename
1579                     AND (majorversion > :majorversion1
1580                      OR (majorversion = :majorversion2 AND minorversion > :minorversion))";
1581  
1582          $results = $DB->get_records_sql(
1583              $sql,
1584              array(
1585                  'machinename' => $library['machineName'],
1586                  'majorversion1' => $library['majorVersion'],
1587                  'majorversion2' => $library['majorVersion'],
1588                  'minorversion' => $library['minorVersion']
1589              ),
1590              0,
1591              1
1592          );
1593  
1594          return !empty($results);
1595      }
1596  
1597      /**
1598       * Get current H5P language code.
1599       *
1600       * @return string Language Code
1601       */
1602      public static function get_language() {
1603          static $map;
1604  
1605          if (empty($map)) {
1606              // Create mapping for "converting" language codes.
1607              $map = array(
1608                  'no' => 'nb'
1609              );
1610          }
1611  
1612          // Get current language in Moodle.
1613          $language = str_replace('_', '-', strtolower(\current_language()));
1614  
1615          // Try to map.
1616          return $map[$language] ?? $language;
1617      }
1618  
1619      /**
1620       * Store messages until they can be printed to the current user.
1621       *
1622       * @param string $type Type of messages, e.g. 'info', 'error', etc
1623       * @param string $newmessage The message
1624       * @param string $code The message code
1625       */
1626      private function set_message(string $type, string $newmessage = null, string $code = null) {
1627          global $SESSION;
1628  
1629          // We expect to get out an array of strings when getting info
1630          // and an array of objects when getting errors for consistency across platforms.
1631          // This implementation should be improved for consistency across the data type returned here.
1632          if ($type === 'error') {
1633              $SESSION->core_h5p_messages[$type][] = (object) array(
1634                  'code' => $code,
1635                  'message' => $newmessage
1636              );
1637          } else {
1638              $SESSION->core_h5p_messages[$type][] = $newmessage;
1639          }
1640      }
1641  
1642      /**
1643       * Convert list of library parameter values to csv.
1644       *
1645       * @param array $librarydata Library data as found in library.json files
1646       * @param string $key Key that should be found in $librarydata
1647       * @param string $searchparam The library parameter (Default: 'path')
1648       * @return string Library parameter values separated by ', '
1649       */
1650      private function library_parameter_values_to_csv(array $librarydata, string $key, string $searchparam = 'path'): string {
1651          if (isset($librarydata[$key])) {
1652              $parametervalues = array();
1653              foreach ($librarydata[$key] as $file) {
1654                  foreach ($file as $index => $value) {
1655                      if ($index === $searchparam) {
1656                          $parametervalues[] = $value;
1657                      }
1658                  }
1659              }
1660              return implode(', ', $parametervalues);
1661          }
1662          return '';
1663      }
1664  
1665      /**
1666       * Get the latest library version.
1667       *
1668       * @param  string $machinename The library's machine name
1669       * @return stdClass|null An object with the latest library version
1670       */
1671      public function get_latest_library_version(string $machinename): ?\stdClass {
1672          global $DB;
1673  
1674          $libraries = $DB->get_records('h5p_libraries', ['machinename' => $machinename],
1675              'majorversion DESC, minorversion DESC, patchversion DESC', '*', 0, 1);
1676          if ($libraries) {
1677              return reset($libraries);
1678          }
1679  
1680          return null;
1681      }
1682  }