Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

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

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

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