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