MDL-68921 core_h5p: Add H5P libraries metadata settings
[moodle.git] / h5p / classes / framework.php
CommitLineData
09d8143d
MG
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * \core_h5p\framework class
19 *
20 * @package core_h5p
21 * @copyright 2019 Mihail Geshoski <mihail@moodle.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25namespace core_h5p;
26
27defined('MOODLE_INTERNAL') || die();
28
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 */
36class framework implements \H5PFrameworkInterface {
37
38 /** @var string The path to the last uploaded h5p */
39 private $lastuploadedfolder;
40
41 /** @var string The path to the last uploaded h5p file */
42 private $lastuploadedfile;
43
c07f31ae
SA
44 /** @var stored_file The .h5p file */
45 private $file;
46
09d8143d
MG
47 /**
48 * Returns info for the current platform.
49 * Implements getPlatformInfo.
50 *
51 * @return array An associative array containing:
52 * - name: The name of the platform, for instance "Moodle"
53 * - version: The version of the platform, for instance "3.8"
54 * - h5pVersion: The version of the H5P component
55 */
56 public function getPlatformInfo() {
57 global $CFG;
58
59 return array(
60 'name' => 'Moodle',
61 'version' => $CFG->version,
62 'h5pVersion' => $CFG->version,
63 );
64 }
65
66 /**
67 * Fetches a file from a remote server using HTTP GET.
68 * Implements fetchExternalData.
69 *
70 * @param string $url Where you want to get or send data
71 * @param array $data Data to post to the URL
72 * @param bool $blocking Set to 'FALSE' to instantly time out (fire and forget)
73 * @param string $stream Path to where the file should be saved
74 * @return string The content (response body). NULL if something went wrong
75 */
76 public function fetchExternalData($url, $data = null, $blocking = true, $stream = null) {
77
78 if ($stream === null) {
79 // Download file.
80 set_time_limit(0);
81
82 // Get the extension of the remote file.
83 $parsedurl = parse_url($url);
84 $ext = pathinfo($parsedurl['path'], PATHINFO_EXTENSION);
85
86 // Generate local tmp file path.
87 $fs = new \core_h5p\file_storage();
88 $localfolder = $fs->getTmpPath();
89 $stream = $localfolder;
90
91 // Add the remote file's extension to the temp file.
92 if ($ext) {
93 $stream .= '.' . $ext;
94 }
95
96 $this->getUploadedH5pFolderPath($localfolder);
97 $this->getUploadedH5pPath($stream);
98 }
99
100 $response = download_file_content($url, null, $data, true, 300, 20,
101 false, $stream);
102
d8fe1edd 103 if (empty($response->error) && ($response->status != '404')) {
09d8143d
MG
104 return $response->results;
105 } else {
106 $this->setErrorMessage($response->error, 'failed-fetching-external-data');
107 }
108 }
109
110 /**
111 * Set the tutorial URL for a library. All versions of the library is set.
112 * Implements setLibraryTutorialUrl.
113 *
114 * @param string $libraryname
115 * @param string $url
116 */
117 public function setLibraryTutorialUrl($libraryname, $url) {
118 // Tutorial url is currently not being used or stored in libraries.
119 }
120
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 }
133
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 }
145
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;
155
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 }
165
166 return $messages;
167 }
168
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()) {
181
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 ];
347
348 if (isset($translationsmap[$message])) {
349 return get_string($translationsmap[$message], 'core_h5p', $replacements);
350 }
351
352 debugging("String translation cannot be found. Please add a string definition for '" .
353 $message . "' in the core_h5p component.", DEBUG_DEVELOPER);
354
355 return $message;
356 }
357
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;
368
369 // Remove unnecessary slashes (first and last, if present) from the path to the folder
370 // of the library file.
371 $libraryfilepath = trim($libraryfoldername, '/');
372
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];
376
377 $factory = new \core_h5p\factory();
378 $core = $factory->get_core();
379
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);
387
388 return;
389 }
390
391 $params = array(
392 'machinename' => $libdata['machineName'],
393 'majorversion' => $libdata['majorVersion'],
394 'minorversion' => $libdata['minorVersion']
395 );
396
397 $libraries = $DB->get_records('h5p_libraries', $params, 'patchversion DESC', 'id',
398 0, 1);
399
400 if (!$library = reset($libraries)) {
401 debugging('The library "' . $libfoldername . '" does not exist.', DEBUG_DEVELOPER);
402
403 return;
404 }
405
406 $context = \context_system::instance();
407
408 return \moodle_url::make_pluginfile_url($context->id, 'core_h5p', 'libraries',
409 $library->id, '/' . $libraryfilepath . '/', $filename)->out();
410 }
411
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 }
423
424 if (!isset($this->lastuploadedfolder)) {
425 throw new \coding_exception('Using getUploadedH5pFolderPath() before path is set');
426 }
427
428 return $this->lastuploadedfolder;
429 }
430
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 }
442
443 if (!isset($this->lastuploadedfile)) {
444 throw new \coding_exception('Using getUploadedH5pPath() before path is set');
445 }
446
447 return $this->lastuploadedfile;
448 }
449
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;
458
459 $addons = array();
460
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");
478
479 // NOTE: These are treated as library objects but are missing the following properties:
480 // title, droplibrarycss, fullscreen, runnable, semantics.
481
482 // Extract num from records.
483 foreach ($records as $addon) {
484 $addons[] = \H5PCore::snakeToCamel($addon);
485 }
486
487 return $addons;
488 }
489
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 }
501
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;
511
512 $results = $DB->get_records('h5p_libraries', [], 'title ASC, majorversion ASC, minorversion ASC',
c43f99ca
BB
513 'id, machinename AS machine_name, majorversion AS major_version, minorversion AS minor_version,
514 patchversion AS patch_version, runnable, title');
09d8143d
MG
515
516 $libraries = array();
517 foreach ($results as $library) {
518 $libraries[$library->machine_name][] = $library;
519 }
520
521 return $libraries;
522 }
523
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 }
533
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;
545
546 $params = array(
547 'machinename' => $machinename
548 );
549
550 if ($majorversion !== null) {
551 $params['majorversion'] = $majorversion;
552 }
553
554 if ($minorversion !== null) {
555 $params['minorversion'] = $minorversion;
556 }
557
558 $libraries = $DB->get_records('h5p_libraries', $params,
559 'majorversion DESC, minorversion DESC, patchversion DESC', 'id', 0, 1);
560
561 // Get the latest version which matches the input parameters.
562 if ($libraries) {
563 $library = reset($libraries);
564 return $library->id ?? false;
565 }
566
567 return false;
568 }
569
570 /**
571 * Get file extension whitelist.
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 }
585
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;
599
600 $sql = "SELECT id
601 FROM {h5p_libraries}
602 WHERE machinename = :machinename
603 AND majorversion = :majorversion
604 AND minorversion = :minorversion
605 AND patchversion < :patchversion";
606
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 );
618
619 return !empty($library);
620 }
621
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 }
631
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() {
c07f31ae
SA
640 return helper::can_update_library($this->get_file());
641 }
642
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 }
652
653 return $this->file;
654 }
655
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;
09d8143d
MG
663 }
664
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
fde9d442
VDF
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
09d8143d
MG
691 * @param bool $new Whether it is a new or existing library.
692 */
693 public function saveLibraryData(&$librarydata, $new = true) {
694 global $DB;
695
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');
700
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 }
711
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,
b7c307f3
VDF
726 'coremajor' => isset($librarydata['coreApi']['majorVersion']) ? $librarydata['coreApi']['majorVersion'] : null,
727 'coreminor' => isset($librarydata['coreApi']['majorVersion']) ? $librarydata['coreApi']['minorVersion'] : null,
fde9d442 728 'metadatasettings' => isset($librarydata['metadataSettings']) ? $librarydata['metadataSettings'] : null,
09d8143d
MG
729 );
730
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 }
744
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 }
763
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;
781
782 if (!isset($content['pathnamehash'])) {
783 $content['pathnamehash'] = '';
784 }
785
786 if (!isset($content['contenthash'])) {
787 $content['contenthash'] = '';
788 }
789
7a0d9bd5
SA
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 }
801
6da050d7 802 $content['disable'] = $content['disable'] ?? null;
3149defe
AA
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 }
7a0d9bd5 809 $data = [
09d8143d
MG
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']
7a0d9bd5 817 ];
09d8143d
MG
818
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 }
826
827 return $id;
828 }
829
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 }
839
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;
853
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 );
863
864 // Create relation.
865 $DB->insert_record('h5p_library_dependencies', array(
866 'libraryid' => $libraryid,
867 'requiredlibraryid' => $dependencylibrary->id,
868 'dependencytype' => $dependencytype
869 ));
870 }
871 }
872
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 }
884
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;
893
894 // Remove content.
895 $DB->delete_records('h5p', array('id' => $contentid));
896
897 // Remove content library dependencies.
898 $this->deleteLibraryUsage($contentid);
899 }
900
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;
909
910 $DB->delete_records('h5p_contents_libraries', array('h5pid' => $contentid));
911 }
912
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;
922
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 }
930
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 }
942
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;
954
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";
963
964 $sqlargs = array(
965 'libraryid' => $id
966 );
967
968 $content = $DB->count_records_sql($sql, $sqlargs);
969 }
970
971 $libraries = $DB->count_records('h5p_library_dependencies', ['requiredlibraryid' => $id]);
972
973 return array(
974 'content' => $content,
975 'libraries' => $libraries,
976 );
977 }
978
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;
1013
1014 $library = $DB->get_record('h5p_libraries', array(
1015 'machinename' => $machinename,
1016 'majorversion' => $majorversion,
1017 'minorversion' => $minorversion
1018 ));
1019
1020 if (!$library) {
1021 return false;
1022 }
1023
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 );
1039
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';
1045
1046 $sqlargs = array(
1047 'libraryid' => $library->id
1048 );
1049
1050 $dependencies = $DB->get_records_sql($sql, $sqlargs);
1051
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 }
1059
1060 return $librarydata;
1061 }
1062
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;
1074
1075 $semantics = $DB->get_field('h5p_libraries', 'semantics',
1076 array(
1077 'machinename' => $name,
1078 'majorversion' => $majorversion,
1079 'minorversion' => $minorversion
1080 )
1081 );
1082
1083 return ($semantics === false ? null : $semantics);
1084 }
1085
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 $DB;
1097
1098 $library = $DB->get_record('h5p_libraries',
1099 array(
1100 'machinename' => $name,
1101 'majorversion' => $majorversion,
1102 'minorversion' => $minorversion,
1103 )
1104 );
1105
1106 if ($library) {
1107 $library->semantics = json_encode($semantics);
1108 $DB->update_record('h5p_libraries', $library);
1109 }
1110 }
1111
1112 /**
1113 * Delete all dependencies belonging to given library.
1114 * Implements deleteLibraryDependencies.
1115 *
1116 * @param int $libraryid Library identifier
1117 */
1118 public function deleteLibraryDependencies($libraryid) {
1119 global $DB;
1120
1121 $DB->delete_records('h5p_library_dependencies', array('libraryid' => $libraryid));
1122 }
1123
1124 /**
1125 * Start an atomic operation against the dependency storage.
1126 * Implements lockDependencyStorage.
1127 */
1128 public function lockDependencyStorage() {
1129 // Library development mode not supported.
1130 }
1131
1132 /**
1133 * Start an atomic operation against the dependency storage.
1134 * Implements unlockDependencyStorage.
1135 */
1136 public function unlockDependencyStorage() {
1137 // Library development mode not supported.
1138 }
1139
1140 /**
1141 * Delete a library from database and file system.
1142 * Implements deleteLibrary.
1143 *
1144 * @param stdClass $library Library object with id, name, major version and minor version
1145 */
1146 public function deleteLibrary($library) {
64a2bd19
SA
1147 $factory = new \core_h5p\factory();
1148 \core_h5p\api::delete_library($factory, $library);
09d8143d
MG
1149 }
1150
1151 /**
1152 * Load content.
1153 * Implements loadContent.
1154 *
1155 * @param int $id Content identifier
1156 * @return array Associative array containing:
1157 * - id: Identifier for the content
1158 * - params: json content as string
1159 * - embedType: list of supported embed types
1160 * - disable: H5P Button display options
1161 * - title: H5P content title
1162 * - slug: Human readable content identifier that is unique
1163 * - libraryId: Id for the main library
1164 * - libraryName: The library machine name
1165 * - libraryMajorVersion: The library's majorVersion
1166 * - libraryMinorVersion: The library's minorVersion
1167 * - libraryEmbedTypes: CSV of the main library's embed types
1168 * - libraryFullscreen: 1 if fullscreen is supported. 0 otherwise
1169 * - metadata: The content's metadata
1170 */
1171 public function loadContent($id) {
1172 global $DB;
1173
1174 $sql = "SELECT hc.id, hc.jsoncontent, hc.displayoptions, hl.id AS libraryid,
1175 hl.machinename, hl.title, hl.majorversion, hl.minorversion, hl.fullscreen,
6da050d7 1176 hl.embedtypes, hl.semantics, hc.filtered, hc.pathnamehash
09d8143d
MG
1177 FROM {h5p} hc
1178 JOIN {h5p_libraries} hl ON hl.id = hc.mainlibraryid
1179 WHERE hc.id = :h5pid";
1180
1181 $sqlargs = array(
1182 'h5pid' => $id
1183 );
1184
1185 $data = $DB->get_record_sql($sql, $sqlargs);
1186
1187 // Return null if not found.
1188 if ($data === false) {
1189 return null;
1190 }
1191
1192 // Some databases do not support camelCase, so we need to manually
1193 // map the values to the camelCase names used by the H5P core.
1194 $content = array(
1195 'id' => $data->id,
1196 'params' => $data->jsoncontent,
1197 // It has been decided that the embedtype will be always set to 'iframe' (at least for now) because the 'div'
1198 // may cause conflicts with CSS and JS in some cases.
1199 'embedType' => 'iframe',
1200 'disable' => $data->displayoptions,
1201 'title' => $data->title,
1202 'slug' => \H5PCore::slugify($data->title) . '-' . $data->id,
1203 'filtered' => $data->filtered,
1204 'libraryId' => $data->libraryid,
1205 'libraryName' => $data->machinename,
1206 'libraryMajorVersion' => $data->majorversion,
1207 'libraryMinorVersion' => $data->minorversion,
1208 'libraryEmbedTypes' => $data->embedtypes,
1209 'libraryFullscreen' => $data->fullscreen,
6da050d7
VDF
1210 'metadata' => '',
1211 'pathnamehash' => $data->pathnamehash
09d8143d
MG
1212 );
1213
6da050d7
VDF
1214 $params = json_decode($data->jsoncontent);
1215 if (empty($params->metadata)) {
1216 $params->metadata = new \stdClass();
1217 }
3149defe
AA
1218 // Add title to metadata.
1219 if (!empty($params->title) && empty($params->metadata->title)) {
1220 $params->metadata->title = $params->title;
1221 }
6da050d7
VDF
1222 $content['metadata'] = $params->metadata;
1223 $content['params'] = json_encode($params->params ?? $params);
1224
09d8143d
MG
1225 return $content;
1226 }
1227
1228 /**
1229 * Load dependencies for the given content of the given type.
1230 * Implements loadContentDependencies.
1231 *
1232 * @param int $id Content identifier
1233 * @param int $type The dependency type
1234 * @return array List of associative arrays containing:
1235 * - libraryId: The id of the library if it is an existing library
1236 * - machineName: The library machineName
1237 * - majorVersion: The library's majorVersion
1238 * - minorVersion: The library's minorVersion
1239 * - patchVersion: The library's patchVersion
1240 * - preloadedJs(optional): comma separated string with js file paths
1241 * - preloadedCss(optional): comma separated sting with css file paths
1242 * - dropCss(optional): csv of machine names
1243 * - dependencyType: The dependency type
1244 */
1245 public function loadContentDependencies($id, $type = null) {
1246 global $DB;
1247
1248 $query = "SELECT hcl.id AS unidepid, hl.id AS library_id, hl.machinename AS machine_name,
1249 hl.majorversion AS major_version, hl.minorversion AS minor_version,
1250 hl.patchversion AS patch_version, hl.preloadedcss AS preloaded_css,
1251 hl.preloadedjs AS preloaded_js, hcl.dropcss AS drop_css,
1252 hcl.dependencytype as dependency_type
1253 FROM {h5p_contents_libraries} hcl
1254 JOIN {h5p_libraries} hl ON hcl.libraryid = hl.id
1255 WHERE hcl.h5pid = :h5pid";
1256 $queryargs = array(
1257 'h5pid' => $id
1258 );
1259
1260 if ($type !== null) {
1261 $query .= " AND hcl.dependencytype = :dependencytype";
1262 $queryargs['dependencytype'] = $type;
1263 }
1264
1265 $query .= " ORDER BY hcl.weight";
1266 $data = $DB->get_records_sql($query, $queryargs);
1267
1268 $dependencies = array();
1269 foreach ($data as $dependency) {
1270 unset($dependency->unidepid);
1271 $dependencies[$dependency->machine_name] = \H5PCore::snakeToCamel($dependency);
1272 }
1273
1274 return $dependencies;
1275 }
1276
1277 /**
92ad6bd9 1278 * Get stored setting.
09d8143d
MG
1279 * Implements getOption.
1280 *
8c325d3f
VDF
1281 * To avoid updating the cache libraries when using the Hub selector,
1282 * {@link \H5PEditorAjax::isContentTypeCacheUpdated}, the setting content_type_cache_updated_at
1283 * always return the current time.
1284 *
09d8143d
MG
1285 * @param string $name Identifier for the setting
1286 * @param string $default Optional default value if settings is not set
92ad6bd9 1287 * @return mixed Return Whatever has been stored as the setting
09d8143d
MG
1288 */
1289 public function getOption($name, $default = false) {
92ad6bd9
SA
1290 if ($name == core::DISPLAY_OPTION_DOWNLOAD || $name == core::DISPLAY_OPTION_EMBED) {
1291 // For now, the download and the embed displayoptions are disabled by default, so only will be rendered when
1292 // defined in the displayoptions DB field.
1293 // This check should be removed if they are added as new H5P settings, to let admins to define the default value.
1294 return \H5PDisplayOptionBehaviour::CONTROLLED_BY_AUTHOR_DEFAULT_OFF;
1295 }
1296
8c325d3f
VDF
1297 // To avoid update the libraries cache using the Hub selector.
1298 if ($name == 'content_type_cache_updated_at') {
1299 return time();
1300 }
1301
92ad6bd9
SA
1302 $value = get_config('core_h5p', $name);
1303 if ($value === false) {
1304 return $default;
1305 }
1306 return $value;
09d8143d
MG
1307 }
1308
1309 /**
1310 * Stores the given setting.
92ad6bd9 1311 * For example when did we last check h5p.org for updates to our libraries.
09d8143d
MG
1312 * Implements setOption.
1313 *
1314 * @param string $name Identifier for the setting
1315 * @param mixed $value Data Whatever we want to store as the setting
1316 */
1317 public function setOption($name, $value) {
92ad6bd9 1318 set_config($name, $value, 'core_h5p');
09d8143d
MG
1319 }
1320
1321 /**
1322 * This will update selected fields on the given content.
1323 * Implements updateContentFields().
1324 *
1325 * @param int $id Content identifier
1326 * @param array $fields Content fields, e.g. filtered
1327 */
1328 public function updateContentFields($id, $fields) {
1329 global $DB;
1330
1331 $content = new \stdClass();
1332 $content->id = $id;
1333
1334 foreach ($fields as $name => $value) {
1335 // Skip 'slug' as it currently does not exist in the h5p content table.
1336 if ($name == 'slug') {
1337 continue;
1338 }
1339
1340 $content->$name = $value;
1341 }
1342
1343 $DB->update_record('h5p', $content);
1344 }
1345
1346 /**
1347 * Will clear filtered params for all the content that uses the specified.
1348 * libraries. This means that the content dependencies will have to be rebuilt and the parameters re-filtered.
1349 * Implements clearFilteredParameters().
1350 *
1351 * @param array $libraryids Array of library ids
1352 */
1353 public function clearFilteredParameters($libraryids) {
1354 global $DB;
1355
1356 if (empty($libraryids)) {
1357 return;
1358 }
1359
1360 list($insql, $inparams) = $DB->get_in_or_equal($libraryids);
1361
1362 $DB->set_field_select('h5p', 'filtered', null,
1363 "mainlibraryid $insql", $inparams);
1364 }
1365
1366 /**
1367 * Get number of contents that has to get their content dependencies rebuilt.
1368 * and parameters re-filtered.
1369 * Implements getNumNotFiltered().
1370 *
1371 * @return int The number of contents that has to get their content dependencies rebuilt
1372 * and parameters re-filtered
1373 */
1374 public function getNumNotFiltered() {
1375 global $DB;
1376
1377 $sql = "SELECT COUNT(id)
1378 FROM {h5p}
1379 WHERE " . $DB->sql_compare_text('filtered') . " IS NULL";
1380
1381 return $DB->count_records_sql($sql);
1382 }
1383
1384 /**
1385 * Get number of contents using library as main library.
1386 * Implements getNumContent().
1387 *
1388 * @param int $libraryid The library ID
1389 * @param array $skip The array of h5p content ID's that should be ignored
1390 * @return int The number of contents using library as main library
1391 */
1392 public function getNumContent($libraryid, $skip = null) {
1393 global $DB;
1394
1395 $notinsql = '';
1396 $params = array();
1397
1398 if (!empty($skip)) {
1399 list($sql, $params) = $DB->get_in_or_equal($skip, SQL_PARAMS_NAMED, 'param', false);
1400 $notinsql = " AND id {$sql}";
1401 }
1402
1403 $sql = "SELECT COUNT(id)
1404 FROM {h5p}
1405 WHERE mainlibraryid = :libraryid {$notinsql}";
1406
1407 $params['libraryid'] = $libraryid;
1408
1409 return $DB->count_records_sql($sql, $params);
1410 }
1411
1412 /**
1413 * Determines if content slug is used.
1414 * Implements isContentSlugAvailable.
1415 *
1416 * @param string $slug The content slug
1417 * @return boolean Whether the content slug is used
1418 */
1419 public function isContentSlugAvailable($slug) {
1420 // By default the slug should be available as it's currently generated as a unique
1421 // value for each h5p content (not stored in the h5p table).
1422 return true;
1423 }
1424
1425 /**
1426 * Generates statistics from the event log per library.
1427 * Implements getLibraryStats.
1428 *
1429 * @param string $type Type of event to generate stats for
1430 * @return array Number values indexed by library name and version
1431 */
1432 public function getLibraryStats($type) {
1433 // Event logs are not being stored.
1434 }
1435
1436 /**
1437 * Aggregate the current number of H5P authors.
1438 * Implements getNumAuthors.
1439 *
1440 * @return int The current number of H5P authors
1441 */
1442 public function getNumAuthors() {
1443 // Currently, H5P authors are not being stored.
1444 }
1445
1446 /**
1447 * Stores hash keys for cached assets, aggregated JavaScripts and
1448 * stylesheets, and connects it to libraries so that we know which cache file
1449 * to delete when a library is updated.
1450 * Implements saveCachedAssets.
1451 *
1452 * @param string $key Hash key for the given libraries
1453 * @param array $libraries List of dependencies(libraries) used to create the key
1454 */
1455 public function saveCachedAssets($key, $libraries) {
1456 global $DB;
1457
1458 foreach ($libraries as $library) {
1459 $cachedasset = new \stdClass();
1460 $cachedasset->libraryid = $library['libraryId'];
1461 $cachedasset->hash = $key;
1462
1463 $DB->insert_record('h5p_libraries_cachedassets', $cachedasset);
1464 }
1465 }
1466
1467 /**
1468 * Locate hash keys for given library and delete them.
1469 * Used when cache file are deleted.
1470 * Implements deleteCachedAssets.
1471 *
1472 * @param int $libraryid Library identifier
1473 * @return array List of hash keys removed
1474 */
1475 public function deleteCachedAssets($libraryid) {
1476 global $DB;
1477
1478 // Get all the keys so we can remove the files.
1479 $results = $DB->get_records('h5p_libraries_cachedassets', ['libraryid' => $libraryid]);
1480
1481 $hashes = array_map(function($result) {
1482 return $result->hash;
1483 }, $results);
1484
1485 if (!empty($hashes)) {
1486 list($sql, $params) = $DB->get_in_or_equal($hashes, SQL_PARAMS_NAMED);
1487 // Remove all invalid keys.
1488 $DB->delete_records_select('h5p_libraries_cachedassets', 'hash ' . $sql, $params);
2b8d9732
SA
1489
1490 // Remove also the cachedassets files.
1491 $fs = new file_storage();
1492 $fs->deleteCachedAssets($hashes);
09d8143d
MG
1493 }
1494
1495 return $hashes;
1496 }
1497
1498 /**
1499 * Get the amount of content items associated to a library.
1500 * Implements getLibraryContentCount.
1501 *
1502 * return array The number of content items associated to a library
1503 */
1504 public function getLibraryContentCount() {
1505 global $DB;
1506
1507 $contentcount = array();
1508
1509 $sql = "SELECT h.mainlibraryid,
1510 l.machinename,
1511 l.majorversion,
1512 l.minorversion,
1513 COUNT(h.id) AS count
1514 FROM {h5p} h
1515 LEFT JOIN {h5p_libraries} l
1516 ON h.mainlibraryid = l.id
1517 GROUP BY h.mainlibraryid, l.machinename, l.majorversion, l.minorversion";
1518
1519 // Count content using the same content type.
1520 $res = $DB->get_records_sql($sql);
1521
1522 // Extract results.
1523 foreach ($res as $lib) {
1524 $contentcount["{$lib->machinename} {$lib->majorversion}.{$lib->minorversion}"] = $lib->count;
1525 }
1526
1527 return $contentcount;
1528 }
1529
1530 /**
1531 * Will trigger after the export file is created.
1532 * Implements afterExportCreated.
1533 *
1534 * @param array $content The content
1535 * @param string $filename The file name
1536 */
1537 public function afterExportCreated($content, $filename) {
1538 // Not being used.
1539 }
1540
1541 /**
1542 * Check whether a user has permissions to execute an action, such as embed H5P content.
1543 * Implements hasPermission.
1544 *
1545 * @param \H5PPermission $permission Permission type
1546 * @param int $id Id need by platform to determine permission
1547 * @return boolean true if the user can execute the action defined in $permission; false otherwise
1548 */
1549 public function hasPermission($permission, $id = null) {
1550 // H5P capabilities have not been introduced.
1551 }
1552
1553 /**
1554 * Replaces existing content type cache with the one passed in.
1555 * Implements replaceContentTypeCache.
1556 *
1557 * @param object $contenttypecache Json with an array called 'libraries' containing the new content type cache
1558 * that should replace the old one
1559 */
1560 public function replaceContentTypeCache($contenttypecache) {
1561 // Currently, content type caches are not being stored.
1562 }
1563
1564 /**
1565 * Checks if the given library has a higher version.
1566 * Implements libraryHasUpgrade.
1567 *
1568 * @param array $library An associative array containing:
1569 * - machineName: The library machineName
1570 * - majorVersion: The library's majorVersion
1571 * - minorVersion: The library's minorVersion
1572 * @return boolean Whether the library has a higher version
1573 */
1574 public function libraryHasUpgrade($library) {
1575 global $DB;
1576
1577 $sql = "SELECT id
1578 FROM {h5p_libraries}
1579 WHERE machinename = :machinename
1580 AND (majorversion > :majorversion1
1581 OR (majorversion = :majorversion2 AND minorversion > :minorversion))";
1582
1583 $results = $DB->get_records_sql(
1584 $sql,
1585 array(
1586 'machinename' => $library['machineName'],
1587 'majorversion1' => $library['majorVersion'],
1588 'majorversion2' => $library['majorVersion'],
1589 'minorversion' => $library['minorVersion']
1590 ),
1591 0,
1592 1
1593 );
1594
1595 return !empty($results);
1596 }
1597
09d8143d
MG
1598 /**
1599 * Get current H5P language code.
1600 *
1601 * @return string Language Code
1602 */
1603 public static function get_language() {
1604 static $map;
1605
1606 if (empty($map)) {
1607 // Create mapping for "converting" language codes.
1608 $map = array(
1609 'no' => 'nb'
1610 );
1611 }
1612
1613 // Get current language in Moodle.
1614 $language = str_replace('_', '-', strtolower(\current_language()));
1615
1616 // Try to map.
1617 return $map[$language] ?? $language;
1618 }
1619
1620 /**
1621 * Store messages until they can be printed to the current user.
1622 *
1623 * @param string $type Type of messages, e.g. 'info', 'error', etc
1624 * @param string $newmessage The message
1625 * @param string $code The message code
1626 */
1627 private function set_message(string $type, string $newmessage = null, string $code = null) {
1628 global $SESSION;
1629
1630 // We expect to get out an array of strings when getting info
1631 // and an array of objects when getting errors for consistency across platforms.
1632 // This implementation should be improved for consistency across the data type returned here.
1633 if ($type === 'error') {
1634 $SESSION->core_h5p_messages[$type][] = (object) array(
1635 'code' => $code,
1636 'message' => $newmessage
1637 );
1638 } else {
1639 $SESSION->core_h5p_messages[$type][] = $newmessage;
1640 }
1641 }
1642
1643 /**
1644 * Convert list of library parameter values to csv.
1645 *
1646 * @param array $librarydata Library data as found in library.json files
1647 * @param string $key Key that should be found in $librarydata
1648 * @param string $searchparam The library parameter (Default: 'path')
1649 * @return string Library parameter values separated by ', '
1650 */
1651 private function library_parameter_values_to_csv(array $librarydata, string $key, string $searchparam = 'path'): string {
1652 if (isset($librarydata[$key])) {
1653 $parametervalues = array();
1654 foreach ($librarydata[$key] as $file) {
1655 foreach ($file as $index => $value) {
1656 if ($index === $searchparam) {
1657 $parametervalues[] = $value;
1658 }
1659 }
1660 }
1661 return implode(', ', $parametervalues);
1662 }
1663 return '';
1664 }
7a0d9bd5
SA
1665
1666 /**
1667 * Get the latest library version.
1668 *
1669 * @param string $machinename The library's machine name
1670 * @return stdClass|null An object with the latest library version
1671 */
1672 public function get_latest_library_version(string $machinename): ?\stdClass {
1673 global $DB;
1674
1675 $libraries = $DB->get_records('h5p_libraries', ['machinename' => $machinename],
1676 'majorversion DESC, minorversion DESC, patchversion DESC', '*', 0, 1);
1677 if ($libraries) {
1678 return reset($libraries);
1679 }
1680
1681 return null;
1682 }
09d8143d 1683}