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]

   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   * Class \core_h5p\editor_framework
  19   *
  20   * @package    core_h5p
  21   * @copyright  2020 Victor Deniz <victor@moodle.com>, base on code by Joubel AS
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_h5p;
  26  
  27  use Moodle\H5peditorStorage;
  28  use stdClass;
  29  
  30  /**
  31   * Moodle's implementation of the H5P Editor storage interface.
  32   *
  33   * Makes it possible for the editor's core library to communicate with the
  34   * database used by Moodle.
  35   *
  36   * @package    core_h5p
  37   * @copyright  2020 Victor Deniz <victor@moodle.com>, base on code by Joubel AS
  38   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class editor_framework implements H5peditorStorage {
  41  
  42      /**
  43       * Retrieve library language file from file storage. Note that parent languages will also be checked until a matching
  44       * record is found (e.g. "de_kids" -> "de_du" -> "de")
  45       *
  46       * @param string $name
  47       * @param int $major
  48       * @param int $minor
  49       * @param string $lang
  50       * @return stdClass|bool Translation record if available, false otherwise
  51       */
  52      private function get_language_record(string $name, int $major, int $minor, string $lang) {
  53          global $DB;
  54  
  55          $params = [
  56              file_storage::COMPONENT,
  57              file_storage::LIBRARY_FILEAREA,
  58          ];
  59          $sqllike = $DB->sql_like('f.filepath', '?');
  60          $params[] = '%language%';
  61  
  62          $sql = "SELECT hl.id, f.pathnamehash
  63                    FROM {h5p_libraries} hl
  64               LEFT JOIN {files} f
  65                      ON hl.id = f.itemid AND f.component = ? AND f.filearea = ? AND $sqllike
  66                   WHERE ((hl.machinename = ? AND hl.majorversion = ? AND hl.minorversion = ?)
  67                     AND f.filename = ?)
  68                ORDER BY hl.patchversion DESC";
  69  
  70          $params[] = $name;
  71          $params[] = $major;
  72          $params[] = $minor;
  73          $params[] = $lang.'.json';
  74  
  75          // Add translations, based initially on the given H5P language code. If missing then recurse language dependencies
  76          // until we find a matching H5P language file.
  77          $result = $DB->get_record_sql($sql, $params);
  78          if ($result === false) {
  79  
  80              // Normalise Moodle language using underscore, as opposed to H5P which uses dash.
  81              $moodlelanguage = str_replace('-', '_', $lang);
  82  
  83              $dependencies = get_string_manager()->get_language_dependencies($moodlelanguage);
  84  
  85              // If current language has a dependency, then request it.
  86              if (count($dependencies) > 1) {
  87                  $parentlanguage = get_html_lang_attribute_value($dependencies[count($dependencies) - 2]);
  88                  $result = $this->get_language_record($name, $major, $minor, $parentlanguage);
  89              }
  90          }
  91  
  92          return $result;
  93      }
  94  
  95      /**
  96       * Load language file(JSON).
  97       * Used to translate the editor fields(title, description etc.)
  98       *
  99       * @param string $name The machine readable name of the library(content type)
 100       * @param int $major Major part of version number
 101       * @param int $minor Minor part of version number
 102       * @param string $lang Language code
 103       *
 104       * @return string|boolean Translation in JSON format if available, false otherwise
 105       */
 106      public function getLanguage($name, $major, $minor, $lang) {
 107  
 108          // Check if this information has been saved previously into the cache.
 109          $langcache = \cache::make('core', 'h5p_content_type_translations');
 110          $library = new stdClass();
 111          $library->machinename = $name;
 112          $library->majorversion = $major;
 113          $library->minorversion = $minor;
 114          $librarykey = helper::get_cache_librarykey(core::record_to_string($library));
 115          $cachekey = "{$librarykey}/{$lang}";
 116          $translation = $langcache->get($cachekey);
 117  
 118          if ($translation !== false) {
 119              // When there is no translation we store it in the cache as `null`.
 120              // This API requires it be returned as `false`.
 121              if ($translation === null) {
 122                  return false;
 123              }
 124  
 125              return $translation;
 126          }
 127  
 128          // Get the language file for this library.
 129          $result = $this->get_language_record($name, $major, $minor, $lang);
 130          if (empty($result)) {
 131              // Save the fact that there is no translation into the cache.
 132              // The cache API cannot handle setting a literal `false` value so conver to `null` instead.
 133              $langcache->set($cachekey, null);
 134  
 135              return false;
 136          }
 137  
 138          // Save translation into the cache, and return its content.
 139          $fs = get_file_storage();
 140          $file = $fs->get_file_by_hash($result->pathnamehash);
 141          $translation = $file->get_content();
 142  
 143          $langcache->set($cachekey, $translation);
 144  
 145          return $translation;
 146      }
 147  
 148      /**
 149       * Load a list of available language codes.
 150       *
 151       * Until translations is implemented, only returns the "en" language.
 152       *
 153       * @param string $machinename The machine readable name of the library(content type)
 154       * @param int $major Major part of version number
 155       * @param int $minor Minor part of version number
 156       *
 157       * @return array List of possible language codes
 158       */
 159      public function getAvailableLanguages($machinename, $major, $minor): array {
 160          global $DB;
 161  
 162          // Check if this information has been saved previously into the cache.
 163          $langcache = \cache::make('core', 'h5p_content_type_translations');
 164          $library = new stdClass();
 165          $library->machinename = $machinename;
 166          $library->majorversion = $major;
 167          $library->minorversion = $minor;
 168          $librarykey = helper::get_cache_librarykey(core::record_to_string($library));
 169          $languages = $langcache->get($librarykey);
 170          if ($languages) {
 171              // This contains a list of all of the available languages for the library.
 172              return $languages;
 173          }
 174  
 175          // Get the language files for this library.
 176          $params = [
 177              file_storage::COMPONENT,
 178              file_storage::LIBRARY_FILEAREA,
 179          ];
 180          $filepathsqllike = $DB->sql_like('f.filepath', '?');
 181          $params[] = '%language%';
 182          $filenamesqllike = $DB->sql_like('f.filename', '?');
 183          $params[] = '%.json';
 184  
 185          $sql = "SELECT DISTINCT f.filename
 186                             FROM {h5p_libraries} hl
 187                        LEFT JOIN {files} f
 188                               ON hl.id = f.itemid AND f.component = ? AND f.filearea = ?
 189                              AND $filepathsqllike AND $filenamesqllike
 190                            WHERE hl.machinename = ? AND hl.majorversion = ? AND hl.minorversion = ?";
 191          $params[] = $machinename;
 192          $params[] = $major;
 193          $params[] = $minor;
 194  
 195          $defaultcode = 'en';
 196          $languages = [];
 197  
 198          $results = $DB->get_recordset_sql($sql, $params);
 199          if ($results->valid()) {
 200              // Extract the code language from the JS language files.
 201              foreach ($results as $result) {
 202                  if (!empty($result->filename)) {
 203                      $lang = substr($result->filename, 0, -5);
 204                      $languages[$lang] = $languages;
 205                  }
 206              }
 207              $results->close();
 208  
 209              // Semantics is 'en' by default. It has to be added always.
 210              if (!array_key_exists($defaultcode, $languages)) {
 211                  $languages = array_keys($languages);
 212                  array_unshift($languages, $defaultcode);
 213              }
 214          } else {
 215              $results->close();
 216              $params = [
 217                  'machinename' => $machinename,
 218                  'majorversion' => $major,
 219                  'minorversion' => $minor,
 220              ];
 221              if ($DB->record_exists('h5p_libraries', $params)) {
 222                  // If the library exists (but it doesn't contain any language file), at least defaultcode should be returned.
 223                  $languages[] = $defaultcode;
 224              }
 225          }
 226  
 227          // Save available languages into the cache.
 228          $langcache->set($librarykey, $languages);
 229  
 230          return $languages;
 231      }
 232  
 233      /**
 234       * "Callback" for mark the given file as a permanent file.
 235       *
 236       * Used when saving content that has new uploaded files.
 237       *
 238       * @param int $fileid
 239       */
 240      public function keepFile($fileid): void {
 241          // Temporal files will be removed on a task when they are in the "editor" file area and and are at least one day older.
 242      }
 243  
 244      /**
 245       * Return libraries details.
 246       *
 247       * Two use cases:
 248       * 1. No input, will list all the available content types.
 249       * 2. Libraries supported are specified, load additional data and verify
 250       * that the content types are available. Used by e.g. the Presentation Tool
 251       * Editor that already knows which content types are supported in its
 252       * slides.
 253       *
 254       * @param array $libraries List of library names + version to load info for.
 255       *
 256       * @return array List of all libraries loaded.
 257       */
 258      public function getLibraries($libraries = null): ?array {
 259  
 260          if ($libraries !== null) {
 261              // Get details for the specified libraries.
 262              $librariesin = [];
 263              $fields = 'title, runnable, metadatasettings, example, tutorial';
 264  
 265              foreach ($libraries as $library) {
 266                  $params = [
 267                      'machinename' => $library->name,
 268                      'majorversion' => $library->majorVersion,
 269                      'minorversion' => $library->minorVersion
 270                  ];
 271  
 272                  $details = api::get_library_details($params, true, $fields);
 273  
 274                  if ($details) {
 275                      $library->title = $details->title;
 276                      $library->runnable = $details->runnable;
 277                      $library->metadataSettings = json_decode($details->metadatasettings ?? '');
 278                      $library->example = $details->example;
 279                      $library->tutorial = $details->tutorial;
 280                      $librariesin[] = $library;
 281                  }
 282              }
 283          } else {
 284              $fields = 'id, machinename as name, title, majorversion, minorversion, metadatasettings, example, tutorial';
 285              $librariesin = api::get_contenttype_libraries($fields);
 286          }
 287  
 288          return $librariesin;
 289      }
 290  
 291      /**
 292       * Allow for other plugins to decide which styles and scripts are attached.
 293       *
 294       * This is useful for adding and/or modifying the functionality and look of
 295       * the content types.
 296       *
 297       * @param array $files List of files as objects with path and version as properties.
 298       * @param array $libraries List of libraries indexed by machineName with objects as values. The objects have majorVersion and
 299       *     minorVersion as properties.
 300       */
 301      public function alterLibraryFiles(&$files, $libraries): void {
 302          global $PAGE;
 303  
 304          // Refactor dependency list.
 305          $librarylist = [];
 306          foreach ($libraries as $dependency) {
 307              $librarylist[$dependency['machineName']] = [
 308                  'majorVersion' => $dependency['majorVersion'],
 309                  'minorVersion' => $dependency['minorVersion']
 310              ];
 311          }
 312  
 313          $renderer = $PAGE->get_renderer('core_h5p');
 314  
 315          $embedtype = 'editor';
 316          $renderer->h5p_alter_scripts($files['scripts'], $librarylist, $embedtype);
 317          $renderer->h5p_alter_styles($files['styles'], $librarylist, $embedtype);
 318      }
 319  
 320      /**
 321       * Saves a file or moves it temporarily.
 322       *
 323       * This is often necessary in order to validate and store uploaded or fetched H5Ps.
 324       *
 325       * @param string $data Uri of data that should be saved as a temporary file.
 326       * @param bool $movefile Can be set to TRUE to move the data instead of saving it.
 327       *
 328       * @return bool|object Returns false if saving failed or an object with path
 329       * of the directory and file that is temporarily saved.
 330       */
 331      public static function saveFileTemporarily($data, $movefile = false) {
 332          // This is to be implemented when the Hub client is used to upload libraries.
 333          return false;
 334      }
 335  
 336      /**
 337       * Marks a file for later cleanup.
 338       *
 339       * Useful when files are not instantly cleaned up. E.g. for files that are uploaded through the editor.
 340       *
 341       * @param int $file Id of file that should be cleaned up
 342       * @param int|null $contentid Content id of file
 343       */
 344      public static function markFileForCleanup($file, $contentid = null): ?int {
 345          // Temporal files will be removed on a task when they are in the "editor" file area and and are at least one day older.
 346          return null;
 347      }
 348  
 349      /**
 350       * Clean up temporary files
 351       *
 352       * @param string $filepath Path to file or directory
 353       */
 354      public static function removeTemporarilySavedFiles($filepath): void {
 355          // This is to be implemented when the Hub client is used to upload libraries.
 356      }
 357  }