Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace core_h5p; 18 19 use core_xapi\handler; 20 use core_xapi\xapi_exception; 21 use Moodle\H5PFrameworkInterface; 22 use Moodle\H5PCore; 23 24 // phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod 25 26 /** 27 * Moodle's implementation of the H5P framework interface. 28 * 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 32 */ 33 class framework implements H5PFrameworkInterface { 34 35 /** @var string The path to the last uploaded h5p */ 36 private $lastuploadedfolder; 37 38 /** @var string The path to the last uploaded h5p file */ 39 private $lastuploadedfile; 40 41 /** @var \stored_file The .h5p file */ 42 private $file; 43 44 /** 45 * Returns info for the current platform. 46 * Implements getPlatformInfo. 47 * 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 52 */ 53 public function getPlatformInfo() { 54 global $CFG; 55 56 return array( 57 'name' => 'Moodle', 58 'version' => $CFG->version, 59 'h5pVersion' => $CFG->version, 60 ); 61 } 62 63 /** 64 * Fetches a file from a remote server using HTTP GET 65 * Implements fetchExternalData. 66 * 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 75 * 76 * @return string|array The content (response body), or an array with data. NULL if something went wrong 77 */ 78 public function fetchExternalData($url, $data = null, $blocking = true, $stream = null, $fulldata = false, $headers = [], 79 $files = [], $method = 'POST') { 80 81 if ($stream === null) { 82 // Download file. 83 set_time_limit(0); 84 85 // Get the extension of the remote file. 86 $parsedurl = parse_url($url); 87 $ext = pathinfo($parsedurl['path'], PATHINFO_EXTENSION); 88 89 // Generate local tmp file path. 90 $fs = new \core_h5p\file_storage(); 91 $localfolder = $fs->getTmpPath(); 92 $stream = $localfolder; 93 94 // Add the remote file's extension to the temp file. 95 if ($ext) { 96 $stream .= '.' . $ext; 97 } 98 99 $this->getUploadedH5pFolderPath($localfolder); 100 $this->getUploadedH5pPath($stream); 101 } 102 103 $response = download_file_content($url, null, $data, true, 300, 20, 104 false, $stream); 105 106 if (empty($response->error) && ($response->status != '404')) { 107 return $response->results; 108 } else { 109 $this->setErrorMessage($response->error, 'failed-fetching-external-data'); 110 } 111 } 112 113 /** 114 * Set the tutorial URL for a library. All versions of the library is set. 115 * Implements setLibraryTutorialUrl. 116 * 117 * @param string $libraryname 118 * @param string $url 119 */ 120 public function setLibraryTutorialUrl($libraryname, $url) { 121 global $DB; 122 123 $sql = 'UPDATE {h5p_libraries} 124 SET tutorial = :tutorial 125 WHERE machinename = :machinename'; 126 $params = [ 127 'tutorial' => $url, 128 'machinename' => $libraryname, 129 ]; 130 $DB->execute($sql, $params); 131 } 132 133 /** 134 * Set an error message. 135 * Implements setErrorMessage. 136 * 137 * @param string $message The error message 138 * @param string $code An optional code 139 */ 140 public function setErrorMessage($message, $code = null) { 141 if ($message !== null) { 142 $this->set_message('error', $message, $code); 143 } 144 } 145 146 /** 147 * Set an info message. 148 * Implements setInfoMessage. 149 * 150 * @param string $message The info message 151 */ 152 public function setInfoMessage($message) { 153 if ($message !== null) { 154 $this->set_message('info', $message); 155 } 156 } 157 158 /** 159 * Return messages. 160 * Implements getMessages. 161 * 162 * @param string $type The message type, e.g. 'info' or 'error' 163 * @return string[] Array of messages 164 */ 165 public function getMessages($type) { 166 global $SESSION; 167 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); 175 } 176 } 177 178 return $messages; 179 } 180 181 /** 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. 187 * 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 191 */ 192 public function t($message, $replacements = array()) { 193 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', 455 ]; 456 457 if (isset($translationsmap[$message])) { 458 return get_string($translationsmap[$message], 'core_h5p', $replacements); 459 } 460 461 debugging("String translation cannot be found. Please add a string definition for '" . 462 $message . "' in the core_h5p component.", DEBUG_DEVELOPER); 463 464 return $message; 465 } 466 467 /** 468 * Get URL to file in the specifimake_pluginfile_urlc library. 469 * Implements getLibraryFileUrl. 470 * 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 474 */ 475 public function getLibraryFileUrl($libraryfoldername, $filename) { 476 global $DB; 477 478 // Remove unnecessary slashes (first and last, if present) from the path to the folder 479 // of the library file. 480 $libraryfilepath = trim($libraryfoldername, '/'); 481 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]; 485 486 $factory = new \core_h5p\factory(); 487 $core = $factory->get_core(); 488 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); 496 497 return; 498 } 499 500 $params = array( 501 'machinename' => $libdata['machineName'], 502 'majorversion' => $libdata['majorVersion'], 503 'minorversion' => $libdata['minorVersion'] 504 ); 505 506 $libraries = $DB->get_records('h5p_libraries', $params, 'patchversion DESC', 'id', 507 0, 1); 508 509 if (!$library = reset($libraries)) { 510 debugging('The library "' . $libfoldername . '" does not exist.', DEBUG_DEVELOPER); 511 512 return; 513 } 514 515 $context = \context_system::instance(); 516 517 return \moodle_url::make_pluginfile_url($context->id, 'core_h5p', 'libraries', 518 $library->id, '/' . $libraryfilepath . '/', $filename)->out(); 519 } 520 521 /** 522 * Get the Path to the last uploaded h5p. 523 * Implements getUploadedH5PFolderPath. 524 * 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 527 */ 528 public function getUploadedH5pFolderPath($setpath = null) { 529 if ($setpath !== null) { 530 $this->lastuploadedfolder = $setpath; 531 } 532 533 if (!isset($this->lastuploadedfolder)) { 534 throw new \coding_exception('Using getUploadedH5pFolderPath() before path is set'); 535 } 536 537 return $this->lastuploadedfolder; 538 } 539 540 /** 541 * Get the path to the last uploaded h5p file. 542 * Implements getUploadedH5PPath. 543 * 544 * @param string $setpath The path to the last uploaded h5p 545 * @return string Path to the last uploaded h5p 546 */ 547 public function getUploadedH5pPath($setpath = null) { 548 if ($setpath !== null) { 549 $this->lastuploadedfile = $setpath; 550 } 551 552 if (!isset($this->lastuploadedfile)) { 553 throw new \coding_exception('Using getUploadedH5pPath() before path is set'); 554 } 555 556 return $this->lastuploadedfile; 557 } 558 559 /** 560 * Load addon libraries. 561 * Implements loadAddons. 562 * 563 * @return array The array containing the addon libraries 564 */ 565 public function loadAddons() { 566 global $DB; 567 568 $addons = array(); 569 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"); 587 588 // NOTE: These are treated as library objects but are missing the following properties: 589 // title, droplibrarycss, fullscreen, runnable, semantics. 590 591 // Extract num from records. 592 foreach ($records as $addon) { 593 $addons[] = H5PCore::snakeToCamel($addon); 594 } 595 596 return $addons; 597 } 598 599 /** 600 * Load config for libraries. 601 * Implements getLibraryConfig. 602 * 603 * @param array|null $libraries List of libraries 604 * @return array|null The library config if it exists, null otherwise 605 */ 606 public function getLibraryConfig($libraries = null) { 607 global $CFG; 608 return isset($CFG->core_h5p_library_config) ? $CFG->core_h5p_library_config : null; 609 } 610 611 /** 612 * Get a list of the current installed libraries. 613 * Implements loadLibraries. 614 * 615 * @return array Associative array containing one entry per machine name. 616 * For each machineName there is a list of libraries(with different versions). 617 */ 618 public function loadLibraries() { 619 global $DB; 620 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'); 624 625 $libraries = array(); 626 foreach ($results as $library) { 627 $libraries[$library->machine_name][] = $library; 628 } 629 630 return $libraries; 631 } 632 633 /** 634 * Returns the URL to the library admin page. 635 * Implements getAdminUrl. 636 * 637 * @return string URL to admin page 638 */ 639 public function getAdminUrl() { 640 // Not supported. 641 } 642 643 /** 644 * Return the library's ID. 645 * Implements getLibraryId. 646 * 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 651 */ 652 public function getLibraryId($machinename, $majorversion = null, $minorversion = null) { 653 global $DB; 654 655 $params = array( 656 'machinename' => $machinename 657 ); 658 659 if ($majorversion !== null) { 660 $params['majorversion'] = $majorversion; 661 } 662 663 if ($minorversion !== null) { 664 $params['minorversion'] = $minorversion; 665 } 666 667 $libraries = $DB->get_records('h5p_libraries', $params, 668 'majorversion DESC, minorversion DESC, patchversion DESC', 'id', 0, 1); 669 670 // Get the latest version which matches the input parameters. 671 if ($libraries) { 672 $library = reset($libraries); 673 return $library->id ?? false; 674 } 675 676 return false; 677 } 678 679 /** 680 * Get allowed file extension list. 681 * Implements getWhitelist. 682 * 683 * The default extension list is part of h5p, but admins should be allowed to modify it. 684 * 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. 690 */ 691 public function getWhitelist($islibrary, $defaultcontentwhitelist, $defaultlibrarywhitelist) { 692 return $defaultcontentwhitelist . ($islibrary ? ' ' . $defaultlibrarywhitelist : ''); 693 } 694 695 /** 696 * Is the library a patched version of an existing library? 697 * Implements isPatchedLibrary. 698 * 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 705 */ 706 public function isPatchedLibrary($library) { 707 global $DB; 708 709 $sql = "SELECT id 710 FROM {h5p_libraries} 711 WHERE machinename = :machinename 712 AND majorversion = :majorversion 713 AND minorversion = :minorversion 714 AND patchversion < :patchversion"; 715 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'] 723 ), 724 0, 725 1 726 ); 727 728 return !empty($library); 729 } 730 731 /** 732 * Is H5P in development mode? 733 * Implements isInDevMode. 734 * 735 * @return boolean TRUE if H5P development mode is active FALSE otherwise 736 */ 737 public function isInDevMode() { 738 return false; // Not supported (Files in moodle not editable). 739 } 740 741 /** 742 * Is the current user allowed to update libraries? 743 * Implements mayUpdateLibraries. 744 * 745 * @return boolean TRUE if the user is allowed to update libraries, 746 * FALSE if the user is not allowed to update libraries. 747 */ 748 public function mayUpdateLibraries() { 749 return helper::can_update_library($this->get_file()); 750 } 751 752 /** 753 * Get the .h5p file. 754 * 755 * @return \stored_file The .h5p file. 756 */ 757 public function get_file(): \stored_file { 758 if (!isset($this->file)) { 759 throw new \coding_exception('Using get_file() before file is set'); 760 } 761 762 return $this->file; 763 } 764 765 /** 766 * Set the .h5p file. 767 * 768 * @param stored_file $file The .h5p file. 769 */ 770 public function set_file(\stored_file $file): void { 771 $this->file = $file; 772 } 773 774 /** 775 * Store data about a library. 776 * Implements saveLibraryData. 777 * 778 * Also fills in the libraryId in the libraryData object if the object is new. 779 * 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. 801 */ 802 public function saveLibraryData(&$librarydata, $new = true) { 803 global $DB; 804 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'); 809 810 if (!isset($librarydata['semantics'])) { 811 $librarydata['semantics'] = ''; 812 } 813 if (!isset($librarydata['fullscreen'])) { 814 $librarydata['fullscreen'] = 0; 815 } 816 $embedtypes = ''; 817 if (isset($librarydata['embedTypes'])) { 818 $embedtypes = implode(', ', $librarydata['embedTypes']); 819 } 820 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, 838 ); 839 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']); 851 } 852 } 853 854 /** 855 * Insert new content. 856 * Implements insertContent. 857 * 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 868 */ 869 public function insertContent($content, $contentmainid = null) { 870 return $this->updateContent($content); 871 } 872 873 /** 874 * Update old content or insert new content. 875 * Implements updateContent. 876 * 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 887 */ 888 public function updateContent($content, $contentmainid = null) { 889 global $DB; 890 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; 899 } 900 $content['library']['libraryId'] = $mainlibrary->id; 901 } 902 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); 909 } 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); 915 } 916 917 $data = [ 918 'jsoncontent' => $content['params'], 919 'displayoptions' => $content['disable'], 920 'mainlibraryid' => $content['library']['libraryId'], 921 'timemodified' => time(), 922 'filtered' => null, 923 ]; 924 925 if (isset($content['pathnamehash'])) { 926 $data['pathnamehash'] = $content['pathnamehash']; 927 } 928 929 if (isset($content['contenthash'])) { 930 $data['contenthash'] = $content['contenthash']; 931 } 932 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); 941 } 942 943 return $id; 944 } 945 946 /** 947 * Resets marked user data for the given content. 948 * Implements resetContentUserData. 949 * 950 * @param int $contentid The h5p content id 951 */ 952 public function resetContentUserData($contentid) { 953 global $DB; 954 955 // Get the component associated to the H5P content to reset. 956 $h5p = $DB->get_record('h5p', ['id' => $contentid]); 957 if (!$h5p) { 958 return; 959 } 960 961 $fs = get_file_storage(); 962 $file = $fs->get_file_by_hash($h5p->pathnamehash); 963 if (!$file) { 964 return; 965 } 966 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; 976 } 977 } 978 979 /** 980 * Save what libraries a library is depending on. 981 * Implements saveLibraryDependencies. 982 * 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 989 */ 990 public function saveLibraryDependencies($libraryid, $dependencies, $dependencytype) { 991 global $DB; 992 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'] 1000 ) 1001 ); 1002 1003 // Create relation. 1004 $DB->insert_record('h5p_library_dependencies', array( 1005 'libraryid' => $libraryid, 1006 'requiredlibraryid' => $dependencylibrary->id, 1007 'dependencytype' => $dependencytype 1008 )); 1009 } 1010 } 1011 1012 /** 1013 * Give an H5P the same library dependencies as a given H5P. 1014 * Implements copyLibraryUsage. 1015 * 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 1019 */ 1020 public function copyLibraryUsage($contentid, $copyfromid, $contentmainid = null) { 1021 // Currently not being called. 1022 } 1023 1024 /** 1025 * Deletes content data. 1026 * Implements deleteContentData. 1027 * 1028 * @param int $contentid Id identifying the content 1029 */ 1030 public function deleteContentData($contentid) { 1031 global $DB; 1032 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); 1036 1037 // Remove content. 1038 $DB->delete_records('h5p', ['id' => $contentid]); 1039 1040 // Remove content library dependencies. 1041 $this->deleteLibraryUsage($contentid); 1042 } 1043 1044 /** 1045 * Delete what libraries a content item is using. 1046 * Implements deleteLibraryUsage. 1047 * 1048 * @param int $contentid Content Id of the content we'll be deleting library usage for 1049 */ 1050 public function deleteLibraryUsage($contentid) { 1051 global $DB; 1052 1053 $DB->delete_records('h5p_contents_libraries', array('h5pid' => $contentid)); 1054 } 1055 1056 /** 1057 * Saves what libraries the content uses. 1058 * Implements saveLibraryUsage. 1059 * 1060 * @param int $contentid Id identifying the content 1061 * @param array $librariesinuse List of libraries the content uses 1062 */ 1063 public function saveLibraryUsage($contentid, $librariesinuse) { 1064 global $DB; 1065 1066 $droplibrarycsslist = array(); 1067 foreach ($librariesinuse as $dependency) { 1068 if (!empty($dependency['library']['dropLibraryCss'])) { 1069 $droplibrarycsslist = array_merge($droplibrarycsslist, 1070 explode(', ', $dependency['library']['dropLibraryCss'])); 1071 } 1072 } 1073 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'] 1082 )); 1083 } 1084 } 1085 1086 /** 1087 * Get number of content/nodes using a library, and the number of dependencies to other libraries. 1088 * Implements getLibraryUsage. 1089 * 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 1094 */ 1095 public function getLibraryUsage($id, $skipcontent = false) { 1096 global $DB; 1097 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"; 1106 1107 $sqlargs = array( 1108 'libraryid' => $id 1109 ); 1110 1111 $content = $DB->count_records_sql($sql, $sqlargs); 1112 } 1113 1114 $libraries = $DB->count_records('h5p_library_dependencies', ['requiredlibraryid' => $id]); 1115 1116 return array( 1117 'content' => $content, 1118 'libraries' => $libraries, 1119 ); 1120 } 1121 1122 /** 1123 * Loads a library. 1124 * Implements loadLibrary. 1125 * 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 1153 */ 1154 public function loadLibrary($machinename, $majorversion, $minorversion) { 1155 global $DB; 1156 1157 $library = $DB->get_record('h5p_libraries', array( 1158 'machinename' => $machinename, 1159 'majorversion' => $majorversion, 1160 'minorversion' => $minorversion 1161 )); 1162 1163 if (!$library) { 1164 return false; 1165 } 1166 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 1181 ); 1182 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'; 1188 1189 $sqlargs = array( 1190 'libraryid' => $library->id 1191 ); 1192 1193 $dependencies = $DB->get_records_sql($sql, $sqlargs); 1194 1195 foreach ($dependencies as $dependency) { 1196 $librarydata[$dependency->dependencytype . 'Dependencies'][] = array( 1197 'machineName' => $dependency->machinename, 1198 'majorVersion' => $dependency->majorversion, 1199 'minorVersion' => $dependency->minorversion 1200 ); 1201 } 1202 1203 return $librarydata; 1204 } 1205 1206 /** 1207 * Loads library semantics. 1208 * Implements loadLibrarySemantics. 1209 * 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 1214 */ 1215 public function loadLibrarySemantics($name, $majorversion, $minorversion) { 1216 global $DB; 1217 1218 $semantics = $DB->get_field('h5p_libraries', 'semantics', 1219 array( 1220 'machinename' => $name, 1221 'majorversion' => $majorversion, 1222 'minorversion' => $minorversion 1223 ) 1224 ); 1225 1226 return ($semantics === false ? null : $semantics); 1227 } 1228 1229 /** 1230 * Makes it possible to alter the semantics, adding custom fields, etc. 1231 * Implements alterLibrarySemantics. 1232 * 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 1237 */ 1238 public function alterLibrarySemantics(&$semantics, $name, $majorversion, $minorversion) { 1239 global $PAGE; 1240 1241 $renderer = $PAGE->get_renderer('core_h5p'); 1242 $renderer->h5p_alter_semantics($semantics, $name, $majorversion, $minorversion); 1243 } 1244 1245 /** 1246 * Delete all dependencies belonging to given library. 1247 * Implements deleteLibraryDependencies. 1248 * 1249 * @param int $libraryid Library identifier 1250 */ 1251 public function deleteLibraryDependencies($libraryid) { 1252 global $DB; 1253 1254 $DB->delete_records('h5p_library_dependencies', array('libraryid' => $libraryid)); 1255 } 1256 1257 /** 1258 * Start an atomic operation against the dependency storage. 1259 * Implements lockDependencyStorage. 1260 */ 1261 public function lockDependencyStorage() { 1262 // Library development mode not supported. 1263 } 1264 1265 /** 1266 * Start an atomic operation against the dependency storage. 1267 * Implements unlockDependencyStorage. 1268 */ 1269 public function unlockDependencyStorage() { 1270 // Library development mode not supported. 1271 } 1272 1273 /** 1274 * Delete a library from database and file system. 1275 * Implements deleteLibrary. 1276 * 1277 * @param \stdClass $library Library object with id, name, major version and minor version 1278 */ 1279 public function deleteLibrary($library) { 1280 $factory = new \core_h5p\factory(); 1281 \core_h5p\api::delete_library($factory, $library); 1282 } 1283 1284 /** 1285 * Load content. 1286 * Implements loadContent. 1287 * 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 1303 */ 1304 public function loadContent($id) { 1305 global $DB; 1306 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"; 1313 1314 $sqlargs = array( 1315 'h5pid' => $id 1316 ); 1317 1318 $data = $DB->get_record_sql($sql, $sqlargs); 1319 1320 // Return null if not found. 1321 if ($data === false) { 1322 return null; 1323 } 1324 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 1345 ); 1346 1347 $params = json_decode($data->jsoncontent); 1348 if (empty($params->metadata)) { 1349 $params->metadata = new \stdClass(); 1350 } 1351 // Add title to metadata. 1352 if (!empty($params->title) && empty($params->metadata->title)) { 1353 $params->metadata->title = $params->title; 1354 } 1355 $content['metadata'] = $params->metadata; 1356 $content['params'] = json_encode($params->params ?? $params); 1357 1358 return $content; 1359 } 1360 1361 /** 1362 * Load dependencies for the given content of the given type. 1363 * Implements loadContentDependencies. 1364 * 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 1377 */ 1378 public function loadContentDependencies($id, $type = null) { 1379 global $DB; 1380 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 1391 ); 1392 1393 if ($type !== null) { 1394 $query .= " AND hcl.dependencytype = :dependencytype"; 1395 $queryargs['dependencytype'] = $type; 1396 } 1397 1398 $query .= " ORDER BY hcl.weight"; 1399 $data = $DB->get_records_sql($query, $queryargs); 1400 1401 $dependencies = array(); 1402 foreach ($data as $dependency) { 1403 unset($dependency->unidepid); 1404 $dependencies[$dependency->machine_name] = H5PCore::snakeToCamel($dependency); 1405 } 1406 1407 return $dependencies; 1408 } 1409 1410 /** 1411 * Get stored setting. 1412 * Implements getOption. 1413 * 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. 1417 * 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 1421 */ 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; 1428 } 1429 1430 // To avoid update the libraries cache using the Hub selector. 1431 if ($name == 'content_type_cache_updated_at') { 1432 return time(); 1433 } 1434 1435 $value = get_config('core_h5p', $name); 1436 if ($value === false) { 1437 return $default; 1438 } 1439 return $value; 1440 } 1441 1442 /** 1443 * Stores the given setting. 1444 * For example when did we last check h5p.org for updates to our libraries. 1445 * Implements setOption. 1446 * 1447 * @param string $name Identifier for the setting 1448 * @param mixed $value Data Whatever we want to store as the setting 1449 */ 1450 public function setOption($name, $value) { 1451 set_config($name, $value, 'core_h5p'); 1452 } 1453 1454 /** 1455 * This will update selected fields on the given content. 1456 * Implements updateContentFields(). 1457 * 1458 * @param int $id Content identifier 1459 * @param array $fields Content fields, e.g. filtered 1460 */ 1461 public function updateContentFields($id, $fields) { 1462 global $DB; 1463 1464 $content = new \stdClass(); 1465 $content->id = $id; 1466 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; 1471 } 1472 1473 $content->$name = $value; 1474 } 1475 1476 $DB->update_record('h5p', $content); 1477 } 1478 1479 /** 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(). 1483 * 1484 * @param array $libraryids Array of library ids 1485 */ 1486 public function clearFilteredParameters($libraryids) { 1487 global $DB; 1488 1489 if (empty($libraryids)) { 1490 return; 1491 } 1492 1493 list($insql, $inparams) = $DB->get_in_or_equal($libraryids); 1494 1495 $DB->set_field_select('h5p', 'filtered', null, 1496 "mainlibraryid $insql", $inparams); 1497 } 1498 1499 /** 1500 * Get number of contents that has to get their content dependencies rebuilt. 1501 * and parameters re-filtered. 1502 * Implements getNumNotFiltered(). 1503 * 1504 * @return int The number of contents that has to get their content dependencies rebuilt 1505 * and parameters re-filtered 1506 */ 1507 public function getNumNotFiltered() { 1508 global $DB; 1509 1510 $sql = "SELECT COUNT(id) 1511 FROM {h5p} 1512 WHERE " . $DB->sql_compare_text('filtered') . " IS NULL"; 1513 1514 return $DB->count_records_sql($sql); 1515 } 1516 1517 /** 1518 * Get number of contents using library as main library. 1519 * Implements getNumContent(). 1520 * 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 1524 */ 1525 public function getNumContent($libraryid, $skip = null) { 1526 global $DB; 1527 1528 $notinsql = ''; 1529 $params = array(); 1530 1531 if (!empty($skip)) { 1532 list($sql, $params) = $DB->get_in_or_equal($skip, SQL_PARAMS_NAMED, 'param', false); 1533 $notinsql = " AND id {$sql}"; 1534 } 1535 1536 $sql = "SELECT COUNT(id) 1537 FROM {h5p} 1538 WHERE mainlibraryid = :libraryid {$notinsql}"; 1539 1540 $params['libraryid'] = $libraryid; 1541 1542 return $DB->count_records_sql($sql, $params); 1543 } 1544 1545 /** 1546 * Determines if content slug is used. 1547 * Implements isContentSlugAvailable. 1548 * 1549 * @param string $slug The content slug 1550 * @return boolean Whether the content slug is used 1551 */ 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; 1556 } 1557 1558 /** 1559 * Generates statistics from the event log per library. 1560 * Implements getLibraryStats. 1561 * 1562 * @param string $type Type of event to generate stats for 1563 * @return array Number values indexed by library name and version 1564 */ 1565 public function getLibraryStats($type) { 1566 // Event logs are not being stored. 1567 } 1568 1569 /** 1570 * Aggregate the current number of H5P authors. 1571 * Implements getNumAuthors. 1572 * 1573 * @return int The current number of H5P authors 1574 */ 1575 public function getNumAuthors() { 1576 // Currently, H5P authors are not being stored. 1577 } 1578 1579 /** 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. 1584 * 1585 * @param string $key Hash key for the given libraries 1586 * @param array $libraries List of dependencies(libraries) used to create the key 1587 */ 1588 public function saveCachedAssets($key, $libraries) { 1589 global $DB; 1590 1591 foreach ($libraries as $library) { 1592 $cachedasset = new \stdClass(); 1593 $cachedasset->libraryid = $library['libraryId']; 1594 $cachedasset->hash = $key; 1595 1596 $DB->insert_record('h5p_libraries_cachedassets', $cachedasset); 1597 } 1598 } 1599 1600 /** 1601 * Locate hash keys for given library and delete them. 1602 * Used when cache file are deleted. 1603 * Implements deleteCachedAssets. 1604 * 1605 * @param int $libraryid Library identifier 1606 * @return array List of hash keys removed 1607 */ 1608 public function deleteCachedAssets($libraryid) { 1609 global $DB; 1610 1611 // Get all the keys so we can remove the files. 1612 $results = $DB->get_records('h5p_libraries_cachedassets', ['libraryid' => $libraryid]); 1613 1614 $hashes = array_map(function($result) { 1615 return $result->hash; 1616 }, $results); 1617 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); 1622 1623 // Remove also the cachedassets files. 1624 $fs = new file_storage(); 1625 $fs->deleteCachedAssets($hashes); 1626 } 1627 1628 return $hashes; 1629 } 1630 1631 /** 1632 * Get the amount of content items associated to a library. 1633 * Implements getLibraryContentCount. 1634 * 1635 * return array The number of content items associated to a library 1636 */ 1637 public function getLibraryContentCount() { 1638 global $DB; 1639 1640 $contentcount = array(); 1641 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"; 1651 1652 // Count content using the same content type. 1653 $res = $DB->get_records_sql($sql); 1654 1655 // Extract results. 1656 foreach ($res as $lib) { 1657 $contentcount["{$lib->machinename} {$lib->majorversion}.{$lib->minorversion}"] = $lib->count; 1658 } 1659 1660 return $contentcount; 1661 } 1662 1663 /** 1664 * Will trigger after the export file is created. 1665 * Implements afterExportCreated. 1666 * 1667 * @param array $content The content 1668 * @param string $filename The file name 1669 */ 1670 public function afterExportCreated($content, $filename) { 1671 // Not being used. 1672 } 1673 1674 /** 1675 * Check whether a user has permissions to execute an action, such as embed H5P content. 1676 * Implements hasPermission. 1677 * 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 1681 */ 1682 public function hasPermission($permission, $id = null) { 1683 // H5P capabilities have not been introduced. 1684 } 1685 1686 /** 1687 * Replaces existing content type cache with the one passed in. 1688 * Implements replaceContentTypeCache. 1689 * 1690 * @param object $contenttypecache Json with an array called 'libraries' containing the new content type cache 1691 * that should replace the old one 1692 */ 1693 public function replaceContentTypeCache($contenttypecache) { 1694 // Currently, content type caches are not being stored. 1695 } 1696 1697 /** 1698 * Checks if the given library has a higher version. 1699 * Implements libraryHasUpgrade. 1700 * 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 1706 */ 1707 public function libraryHasUpgrade($library) { 1708 global $DB; 1709 1710 $sql = "SELECT id 1711 FROM {h5p_libraries} 1712 WHERE machinename = :machinename 1713 AND (majorversion > :majorversion1 1714 OR (majorversion = :majorversion2 AND minorversion > :minorversion))"; 1715 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'] 1723 ), 1724 0, 1725 1 1726 ); 1727 1728 return !empty($results); 1729 } 1730 1731 /** 1732 * Get current H5P language code. 1733 * 1734 * @return string Language Code 1735 */ 1736 public static function get_language() { 1737 static $map; 1738 1739 if (empty($map)) { 1740 // Create mapping for "converting" language codes. 1741 $map = array( 1742 'no' => 'nb' 1743 ); 1744 } 1745 1746 // Get current language in Moodle. 1747 $language = get_html_lang_attribute_value(strtolower(\current_language())); 1748 1749 // Try to map. 1750 return $map[$language] ?? $language; 1751 } 1752 1753 /** 1754 * Store messages until they can be printed to the current user. 1755 * 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 1759 */ 1760 private function set_message(string $type, string $newmessage = null, string $code = null) { 1761 global $SESSION; 1762 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 1770 ); 1771 } else { 1772 $SESSION->core_h5p_messages[$type][] = $newmessage; 1773 } 1774 } 1775 1776 /** 1777 * Convert list of library parameter values to csv. 1778 * 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 ', ' 1783 */ 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; 1791 } 1792 } 1793 } 1794 return implode(', ', $parametervalues); 1795 } 1796 return ''; 1797 } 1798 1799 /** 1800 * Get the latest library version. 1801 * 1802 * @param string $machinename The library's machine name 1803 * @return stdClass|null An object with the latest library version 1804 */ 1805 public function get_latest_library_version(string $machinename): ?\stdClass { 1806 global $DB; 1807 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); 1812 } 1813 1814 return null; 1815 } 1816 1817 /** 1818 * Replace content hub metadata cache 1819 * 1820 * @param JsonSerializable $metadata Metadata as received from content hub 1821 * @param string $lang Language in ISO 639-1 1822 * 1823 * @return mixed 1824 */ 1825 public function replaceContentHubMetadataCache($metadata, $lang) { 1826 debugging('The replaceContentHubMetadataCache() method is not implemented.', DEBUG_DEVELOPER); 1827 return null; 1828 } 1829 1830 /** 1831 * Get content hub metadata cache from db 1832 * 1833 * @param string $lang Language code in ISO 639-1 1834 * 1835 * @return JsonSerializable Json string 1836 */ 1837 public function getContentHubMetadataCache($lang = 'en') { 1838 debugging('The getContentHubMetadataCache() method is not implemented.', DEBUG_DEVELOPER); 1839 return null; 1840 } 1841 1842 /** 1843 * Get time of last content hub metadata check 1844 * 1845 * @param string $lang Language code iin ISO 639-1 format 1846 * 1847 * @return string|null Time in RFC7231 format 1848 */ 1849 public function getContentHubMetadataChecked($lang = 'en') { 1850 debugging('The getContentHubMetadataChecked() method is not implemented.', DEBUG_DEVELOPER); 1851 return null; 1852 } 1853 1854 /** 1855 * Set time of last content hub metadata check 1856 * 1857 * @param int|null $time Time in RFC7231 format 1858 * @param string $lang Language code iin ISO 639-1 format 1859 * 1860 * @return bool True if successful 1861 */ 1862 public function setContentHubMetadataChecked($time, $lang = 'en') { 1863 debugging('The setContentHubMetadataChecked() method is not implemented.', DEBUG_DEVELOPER); 1864 return false; 1865 } 1866 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body