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