diff --git a/h5p/classes/factory.php b/h5p/classes/factory.php new file mode 100644 index 00000000000..f4edb4dd519 --- /dev/null +++ b/h5p/classes/factory.php @@ -0,0 +1,132 @@ +. + +/** + * H5P factory class. + * This class is used to decouple the construction of H5P related objects. + * + * @package core_h5p + * @copyright 2019 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_h5p; + +defined('MOODLE_INTERNAL') || die(); + +use \core_h5p\framework as framework; +use \core_h5p\core as core; +use \H5PStorage as storage; +use \H5PValidator as validator; +use \H5PContentValidator as content_validator; + +/** + * H5P factory class. + * This class is used to decouple the construction of H5P related objects. + * + * @package core_h5p + * @copyright 2019 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class factory { + + /** @var \core_h5p\core The Moodle H5PCore implementation */ + protected $core; + + /** @var \core_h5p\framework The Moodle H5PFramework implementation */ + protected $framework; + + /** @var \core_h5p\file_storage The Moodle H5PStorage implementation */ + protected $storage; + + /** @var validator The Moodle H5PValidator implementation */ + protected $validator; + + /** @var content_validator The Moodle H5PContentValidator implementation */ + protected $content_validator; + + /** + * Returns an instance of the \core_h5p\framework class. + * + * @return \core_h5p\framework + */ + public function get_framework(): framework { + if (null === $this->framework) { + $this->framework = new framework(); + } + + return $this->framework; + } + + /** + * Returns an instance of the \core_h5p\core class. + * + * @return \core_h5p\core + */ + public function get_core(): core { + if (null === $this->core) { + $fs = new \core_h5p\file_storage(); + $language = \core_h5p\framework::get_language(); + $context = \context_system::instance(); + + $url = \moodle_url::make_pluginfile_url($context->id, 'core_h5p', '', null, + '', '')->out(); + + $this->core = new core($this->get_framework(), $fs, $url, $language, true); + } + + return $this->core; + } + + /** + * Returns an instance of the \H5PStorage class. + * + * @return \H5PStorage + */ + public function get_storage(): storage { + if (null === $this->storage) { + $this->storage = new storage($this->get_framework(), $this->get_core()); + } + + return $this->storage; + } + + /** + * Returns an instance of the \H5PValidator class. + * + * @return \H5PValidator + */ + public function get_validator(): validator { + if (null === $this->validator) { + $this->validator = new validator($this->get_framework(), $this->get_core()); + } + + return $this->validator; + } + + /** + * Returns an instance of the \H5PContentValidator class. + * + * @return \H5PContentValidator + */ + public function get_content_validator(): content_validator { + if (null === $this->content_validator) { + $this->content_validator = new content_validator($this->get_framework(), $this->get_core()); + } + + return $this->content_validator; + } +} diff --git a/h5p/classes/framework.php b/h5p/classes/framework.php new file mode 100644 index 00000000000..54d15ff1e27 --- /dev/null +++ b/h5p/classes/framework.php @@ -0,0 +1,1625 @@ +. + +/** + * \core_h5p\framework class + * + * @package core_h5p + * @copyright 2019 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_h5p; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Moodle's implementation of the H5P framework interface. + * + * @package core_h5p + * @copyright 2019 Mihail Geshoski + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class framework implements \H5PFrameworkInterface { + + /** @var string The path to the last uploaded h5p */ + private $lastuploadedfolder; + + /** @var string The path to the last uploaded h5p file */ + private $lastuploadedfile; + + /** + * Returns info for the current platform. + * Implements getPlatformInfo. + * + * @return array An associative array containing: + * - name: The name of the platform, for instance "Moodle" + * - version: The version of the platform, for instance "3.8" + * - h5pVersion: The version of the H5P component + */ + public function getPlatformInfo() { + global $CFG; + + return array( + 'name' => 'Moodle', + 'version' => $CFG->version, + 'h5pVersion' => $CFG->version, + ); + } + + /** + * Fetches a file from a remote server using HTTP GET. + * Implements fetchExternalData. + * + * @param string $url Where you want to get or send data + * @param array $data Data to post to the URL + * @param bool $blocking Set to 'FALSE' to instantly time out (fire and forget) + * @param string $stream Path to where the file should be saved + * @return string The content (response body). NULL if something went wrong + */ + public function fetchExternalData($url, $data = null, $blocking = true, $stream = null) { + + if ($stream === null) { + // Download file. + set_time_limit(0); + + // Get the extension of the remote file. + $parsedurl = parse_url($url); + $ext = pathinfo($parsedurl['path'], PATHINFO_EXTENSION); + + // Generate local tmp file path. + $fs = new \core_h5p\file_storage(); + $localfolder = $fs->getTmpPath(); + $stream = $localfolder; + + // Add the remote file's extension to the temp file. + if ($ext) { + $stream .= '.' . $ext; + } + + $this->getUploadedH5pFolderPath($localfolder); + $this->getUploadedH5pPath($stream); + } + + $response = download_file_content($url, null, $data, true, 300, 20, + false, $stream); + + if (empty($response->error)) { + return $response->results; + } else { + $this->setErrorMessage($response->error, 'failed-fetching-external-data'); + } + } + + /** + * Set the tutorial URL for a library. All versions of the library is set. + * Implements setLibraryTutorialUrl. + * + * @param string $libraryname + * @param string $url + */ + public function setLibraryTutorialUrl($libraryname, $url) { + // Tutorial url is currently not being used or stored in libraries. + } + + /** + * Set an error message. + * Implements setErrorMessage. + * + * @param string $message The error message + * @param string $code An optional code + */ + public function setErrorMessage($message, $code = null) { + if ($message !== null) { + $this->set_message('error', $message, $code); + } + } + + /** + * Set an info message. + * Implements setInfoMessage. + * + * @param string $message The info message + */ + public function setInfoMessage($message) { + if ($message !== null) { + $this->set_message('info', $message); + } + } + + /** + * Return messages. + * Implements getMessages. + * + * @param string $type The message type, e.g. 'info' or 'error' + * @return string[] Array of messages + */ + public function getMessages($type) { + global $SESSION; + + // Return and reset messages. + $messages = array(); + if (isset($SESSION->core_h5p_messages[$type])) { + $messages = $SESSION->core_h5p_messages[$type]; + unset($SESSION->core_h5p_messages[$type]); + if (empty($SESSION->core_h5p_messages)) { + unset($SESSION->core_h5p_messages); + } + } + + return $messages; + } + + /** + * Translation function. + * The purpose of this function is to map the strings used in the core h5p methods + * and replace them with the translated ones. If a translation for a particular string + * is not available, the default message (key) will be returned. + * Implements t. + * + * @param string $message The english string to be translated + * @param array $replacements An associative array of replacements to make after translation + * @return string Translated string or the english string if a translation is not available + */ + public function t($message, $replacements = array()) { + + // Create mapping. + $translationsmap = [ + 'The file you uploaded is not a valid HTML5 Package (It does not have the .h5p file extension)' => 'noextension', + 'The file you uploaded is not a valid HTML5 Package (We are unable to unzip it)' => 'nounzip', + 'The main h5p.json file is not valid' => 'nojson', + 'Library directory name must match machineName or machineName-majorVersion.minorVersion (from library.json).' . + ' (Directory: %directoryName , machineName: %machineName, majorVersion: %majorVersion, minorVersion:' . + ' %minorVersion)' + => 'librarydirectoryerror', + 'A valid content folder is missing' => 'missingcontentfolder', + 'A valid main h5p.json file is missing' => 'invalidmainjson', + 'Missing required library @library' => 'missinglibrary', + "Note that the libraries may exist in the file you uploaded, but you're not allowed to upload new libraries." . + ' Contact the site administrator about this.' => 'missinguploadpermissions', + 'Invalid library name: %name' => 'invalidlibraryname', + 'Could not find library.json file with valid json format for library %name' => 'missinglibraryjson', + 'Invalid semantics.json file has been included in the library %name' => 'invalidsemanticsjson', + 'Invalid language file %file in library %library' => 'invalidlanguagefile', + 'Invalid language file %languageFile has been included in the library %name' => 'invalidlanguagefile2', + 'The file "%file" is missing from library: "%name"' => 'missinglibraryfile', + 'The system was unable to install the %component component from the package, it requires a newer' . + ' version of the H5P plugin. This site is currently running version %current, whereas the required version' . + ' is %required or higher. You should consider upgrading and then try again.' => 'missingcoreversion', + "Invalid data provided for %property in %library. Boolean expected." => 'invalidlibrarydataboolean', + "Invalid data provided for %property in %library" => 'invalidlibrarydata', + "Can't read the property %property in %library" => 'invalidlibraryproperty', + 'The required property %property is missing from %library' => 'missinglibraryproperty', + 'Illegal option %option in %library' => 'invalidlibraryoption', + 'Added %new new H5P library and updated %old old one.' => 'addedandupdatedss', + 'Added %new new H5P library and updated %old old ones.' => 'addedandupdatedsp', + 'Added %new new H5P libraries and updated %old old one.' => 'addedandupdatedps', + 'Added %new new H5P libraries and updated %old old ones.' => 'addedandupdatedpp', + 'Added %new new H5P library.' => 'addednewlibrary', + 'Added %new new H5P libraries.' => 'addednewlibraries', + 'Updated %old H5P library.' => 'updatedlibrary', + 'Updated %old H5P libraries.' => 'updatedlibraries', + 'Missing dependency @dep required by @lib.' => 'missingdependency', + 'Provided string is not valid according to regexp in semantics. (value: "%value", regexp: "%regexp")' + => 'invalidstring', + 'File "%filename" not allowed. Only files with the following extensions are allowed: %files-allowed.' + => 'invalidfile', + 'Invalid selected option in multi-select.' => 'invalidmultiselectoption', + 'Invalid selected option in select.' => 'invalidselectoption', + 'H5P internal error: unknown content type "@type" in semantics. Removing content!' => 'invalidsemanticstype', + 'Copyright information' => 'copyrightinfo', + 'Title' => 'title', + 'Author' => 'author', + 'Year(s)' => 'years', + 'Year' => 'year', + 'Source' => 'source', + 'License' => 'license', + 'Undisclosed' => 'undisclosed', + 'General Public License v3' => 'gpl', + 'Public Domain' => 'pd', + 'Public Domain Dedication and Licence' => 'pddl', + 'Public Domain Mark' => 'pdm', + 'Public Domain Mark (PDM)' => 'pdm', + 'Copyright' => 'copyrightstring', + 'The mbstring PHP extension is not loaded. H5P need this to function properly' => 'missingmbstring', + 'The version of the H5P library %machineName used in this content is not valid. Content contains %contentLibrary, ' . + 'but it should be %semanticsLibrary.' => 'wrongversion', + 'The H5P library %library used in the content is not valid' => 'invalidlibrarynamed', + 'Fullscreen' => 'fullscreen', + 'Disable fullscreen' => 'disablefullscreen', + 'Download' => 'download', + 'Rights of use' => 'copyright', + 'Embed' => 'embed', + 'Size' => 'size', + 'Show advanced' => 'showadvanced', + 'Hide advanced' => 'hideadvanced', + 'Include this script on your website if you want dynamic sizing of the embedded content:' => 'resizescript', + 'Close' => 'close', + 'Thumbnail' => 'thumbnail', + 'No copyright information available for this content.' => 'nocopyright', + 'Download this content as a H5P file.' => 'downloadtitle', + 'View copyright information for this content.' => 'copyrighttitle', + 'View the embed code for this content.' => 'embedtitle', + 'Visit H5P.org to check out more cool content.' => 'h5ptitle', + 'This content has changed since you last used it.' => 'contentchanged', + "You'll be starting over." => 'startingover', + 'by' => 'by', + 'Show more' => 'showmore', + 'Show less' => 'showless', + 'Sublevel' => 'sublevel', + 'Confirm action' => 'confirmdialogheader', + 'Please confirm that you wish to proceed. This action is not reversible.' => 'confirmdialogbody', + 'Cancel' => 'cancellabel', + 'Confirm' => 'confirmlabel', + '4.0 International' => 'licenseCC40', + '3.0 Unported' => 'licenseCC30', + '2.5 Generic' => 'licenseCC25', + '2.0 Generic' => 'licenseCC20', + '1.0 Generic' => 'licenseCC10', + 'General Public License' => 'licenseGPL', + 'Version 3' => 'licenseV3', + 'Version 2' => 'licenseV2', + 'Version 1' => 'licenseV1', + 'CC0 1.0 Universal (CC0 1.0) Public Domain Dedication' => 'licenseCC010', + 'CC0 1.0 Universal' => 'licenseCC010U', + 'License Version' => 'licenseversion', + 'Creative Commons' => 'creativecommons', + 'Attribution' => 'ccattribution', + 'Attribution (CC BY)' => 'ccattribution', + 'Attribution-ShareAlike' => 'ccattributionsa', + 'Attribution-ShareAlike (CC BY-SA)' => 'ccattributionsa', + 'Attribution-NoDerivs' => 'ccattributionnd', + 'Attribution-NoDerivs (CC BY-ND)' => 'ccattributionnd', + 'Attribution-NonCommercial' => 'ccattributionnc', + 'Attribution-NonCommercial (CC BY-NC)' => 'ccattributionnc', + 'Attribution-NonCommercial-ShareAlike' => 'ccattributionncsa', + 'Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)' => 'ccattributionncsa', + 'Attribution-NonCommercial-NoDerivs' => 'ccattributionncnd', + 'Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)' => 'ccattributionncnd', + 'Public Domain Dedication (CC0)' => 'ccpdd', + 'Years (from)' => 'yearsfrom', + 'Years (to)' => 'yearsto', + "Author's name" => 'authorname', + "Author's role" => 'authorrole', + 'Editor' => 'editor', + 'Licensee' => 'licensee', + 'Originator' => 'originator', + 'Any additional information about the license' => 'additionallicenseinfo', + 'License Extras' => 'licenseextras', + 'Changelog' => 'changelog', + 'Content Type' => 'contenttype', + 'Date' => 'date', + 'Changed by' => 'changedby', + 'Description of change' => 'changedescription', + 'Photo cropped, text changed, etc.' => 'changeplaceholder', + 'Author comments' => 'authorcomments', + 'Comments for the editor of the content (This text will not be published as a part of copyright info)' + => 'authorcommentsdescription', + 'Reuse' => 'reuse', + 'Reuse Content' => 'reuseContent', + 'Reuse this content.' => 'reuseDescription', + 'Content is copied to the clipboard' => 'contentCopied', + 'Connection lost. Results will be stored and sent when you regain connection.' => 'connectionLost', + 'Connection reestablished.' => 'connectionReestablished', + 'Attempting to submit stored results.' => 'resubmitScores', + 'Your connection to the server was lost' => 'offlineDialogHeader', + 'We were unable to send information about your completion of this task. Please check your internet connection.' + => 'offlineDialogBody', + 'Retrying in :num....' => 'offlineDialogRetryMessage', + 'Retry now' => 'offlineDialogRetryButtonLabel', + 'Successfully submitted results.' => 'offlineSuccessfulSubmit', + 'One of the files inside the package exceeds the maximum file size allowed. (%file %used > %max)' + => 'fileExceedsMaxSize', + 'The total size of the unpacked files exceeds the maximum size allowed. (%used > %max)' + => 'unpackedFilesExceedsMaxSize', + 'Unable to read file from the package: %fileName' => 'couldNotReadFileFromZip', + 'Unable to parse JSON from the package: %fileName' => 'couldNotParseJSONFromZip', + 'A problem with the server write access was detected. Please make sure that your server can write to your data folder.' => 'nowriteaccess', + 'H5P hub communication has been disabled because one or more H5P requirements failed.' => 'hubcommunicationdisabled', + 'Site could not be registered with the hub. Please contact your site administrator.' => 'sitecouldnotberegistered', + 'The H5P Hub has been disabled until this problem can be resolved. You may still upload libraries through the "H5P Libraries" page.' => 'hubisdisableduploadlibraries', + 'When you have revised your server setup you may re-enable H5P hub communication in H5P Settings.' => 'reviseserversetupandretry', + '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', + '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', + 'Your PHP max upload size is bigger than your max post size. This is known to cause issues in some installations.' => 'uploadsizelargerthanpostsize', + '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', + 'Your PHP version does not support ZipArchive.' => 'noziparchive', + 'Your PHP version is outdated. H5P requires version 5.2 to function properly. Version 5.6 or later is recommended.' => 'oldphpversion', + 'Your server does not have SSL enabled. SSL should be enabled to ensure a secure connection with the H5P hub.' => 'sslnotenabled', + 'Your site was successfully registered with the H5P Hub.' => 'successfullyregisteredwithhub' + ]; + + if (isset($translationsmap[$message])) { + return get_string($translationsmap[$message], 'core_h5p', $replacements); + } + + debugging("String translation cannot be found. Please add a string definition for '" . + $message . "' in the core_h5p component.", DEBUG_DEVELOPER); + + return $message; + } + + /** + * Get URL to file in the specifimake_pluginfile_urlc library. + * Implements getLibraryFileUrl. + * + * @param string $libraryfoldername The name or path of the library's folder + * @param string $filename The file name + * @return string URL to file + */ + public function getLibraryFileUrl($libraryfoldername, $filename) { + global $DB; + + // Remove unnecessary slashes (first and last, if present) from the path to the folder + // of the library file. + $libraryfilepath = trim($libraryfoldername, '/'); + + // Get the folder name of the library from the path. + // The first element should represent the folder name of the library. + $libfoldername = explode('/', $libraryfilepath)[0]; + + $factory = new \core_h5p\factory(); + $core = $factory->get_core(); + + // The provided folder name of the library must have a valid format (can be parsed). + // The folder name is parsed with a purpose of getting the library related information + // such as 'machineName', 'majorVersion' and 'minorVersion'. + // This information is later used to retrieve the library ID. + if (!$libdata = $core->libraryFromString($libfoldername, true)) { + debugging('The provided string value "' . $libfoldername . + '" is not a valid name for a library folder.', DEBUG_DEVELOPER); + + return; + } + + $params = array( + 'machinename' => $libdata['machineName'], + 'majorversion' => $libdata['majorVersion'], + 'minorversion' => $libdata['minorVersion'] + ); + + $libraries = $DB->get_records('h5p_libraries', $params, 'patchversion DESC', 'id', + 0, 1); + + if (!$library = reset($libraries)) { + debugging('The library "' . $libfoldername . '" does not exist.', DEBUG_DEVELOPER); + + return; + } + + $context = \context_system::instance(); + + return \moodle_url::make_pluginfile_url($context->id, 'core_h5p', 'libraries', + $library->id, '/' . $libraryfilepath . '/', $filename)->out(); + } + + /** + * Get the Path to the last uploaded h5p. + * Implements getUploadedH5PFolderPath. + * + * @param string $setpath The path to the folder of the last uploaded h5p + * @return string Path to the folder where the last uploaded h5p for this session is located + */ + public function getUploadedH5pFolderPath($setpath = null) { + if ($setpath !== null) { + $this->lastuploadedfolder = $setpath; + } + + if (!isset($this->lastuploadedfolder)) { + throw new \coding_exception('Using getUploadedH5pFolderPath() before path is set'); + } + + return $this->lastuploadedfolder; + } + + /** + * Get the path to the last uploaded h5p file. + * Implements getUploadedH5PPath. + * + * @param string $setpath The path to the last uploaded h5p + * @return string Path to the last uploaded h5p + */ + public function getUploadedH5pPath($setpath = null) { + if ($setpath !== null) { + $this->lastuploadedfile = $setpath; + } + + if (!isset($this->lastuploadedfile)) { + throw new \coding_exception('Using getUploadedH5pPath() before path is set'); + } + + return $this->lastuploadedfile; + } + + /** + * Load addon libraries. + * Implements loadAddons. + * + * @return array The array containing the addon libraries + */ + public function loadAddons() { + global $DB; + + $addons = array(); + + $records = $DB->get_records_sql( + "SELECT l1.id AS library_id, + l1.machinename AS machine_name, + l1.majorversion AS major_version, + l1.minorversion AS minor_version, + l1.patchversion AS patch_version, + l1.addto AS add_to, + l1.preloadedjs AS preloaded_js, + l1.preloadedcss AS preloaded_css + FROM {h5p_libraries} l1 + LEFT JOIN {h5p_libraries} l2 + ON l1.machinename = l2.machinename + AND (l1.majorversion < l2.majorversion + OR (l1.majorversion = l2.majorversion + AND l1.minorversion < l2.minorversion)) + WHERE l1.addto IS NOT NULL + AND l2.machinename IS NULL"); + + // NOTE: These are treated as library objects but are missing the following properties: + // title, droplibrarycss, fullscreen, runnable, semantics. + + // Extract num from records. + foreach ($records as $addon) { + $addons[] = \H5PCore::snakeToCamel($addon); + } + + return $addons; + } + + /** + * Load config for libraries. + * Implements getLibraryConfig. + * + * @param array|null $libraries List of libraries + * @return array|null The library config if it exists, null otherwise + */ + public function getLibraryConfig($libraries = null) { + global $CFG; + return isset($CFG->core_h5p_library_config) ? $CFG->core_h5p_library_config : null; + } + + /** + * Get a list of the current installed libraries. + * Implements loadLibraries. + * + * @return array Associative array containing one entry per machine name. + * For each machineName there is a list of libraries(with different versions). + */ + public function loadLibraries() { + global $DB; + + $results = $DB->get_records('h5p_libraries', [], 'title ASC, majorversion ASC, minorversion ASC', + 'machinename AS machine_name, majorversion AS major_version, minorversion AS minor_version, + patchversion AS patch_version'); + + $libraries = array(); + foreach ($results as $library) { + $libraries[$library->machine_name][] = $library; + } + + return $libraries; + } + + /** + * Returns the URL to the library admin page. + * Implements getAdminUrl. + * + * @return string URL to admin page + */ + public function getAdminUrl() { + // Not supported. + } + + /** + * Return the library's ID. + * Implements getLibraryId. + * + * @param string $machinename The librarys machine name + * @param string $majorversion Major version number for library (optional) + * @param string $minorversion Minor version number for library (optional) + * @return int|bool Identifier, or false if non-existent + */ + public function getLibraryId($machinename, $majorversion = null, $minorversion = null) { + global $DB; + + $params = array( + 'machinename' => $machinename + ); + + if ($majorversion !== null) { + $params['majorversion'] = $majorversion; + } + + if ($minorversion !== null) { + $params['minorversion'] = $minorversion; + } + + $libraries = $DB->get_records('h5p_libraries', $params, + 'majorversion DESC, minorversion DESC, patchversion DESC', 'id', 0, 1); + + // Get the latest version which matches the input parameters. + if ($libraries) { + $library = reset($libraries); + return $library->id ?? false; + } + + return false; + } + + /** + * Get file extension whitelist. + * Implements getWhitelist. + * + * The default extension list is part of h5p, but admins should be allowed to modify it. + * + * @param boolean $islibrary TRUE if this is the whitelist for a library. FALSE if it is the whitelist + * for the content folder we are getting. + * @param string $defaultcontentwhitelist A string of file extensions separated by whitespace. + * @param string $defaultlibrarywhitelist A string of file extensions separated by whitespace. + * @return string A string containing the allowed file extensions separated by whitespace. + */ + public function getWhitelist($islibrary, $defaultcontentwhitelist, $defaultlibrarywhitelist) { + return $defaultcontentwhitelist . ($islibrary ? ' ' . $defaultlibrarywhitelist : ''); + } + + /** + * Is the library a patched version of an existing library? + * Implements isPatchedLibrary. + * + * @param array $library An associative array containing: + * - machineName: The library machine name + * - majorVersion: The librarys major version + * - minorVersion: The librarys minor version + * - patchVersion: The librarys patch version + * @return boolean TRUE if the library is a patched version of an existing library FALSE otherwise + */ + public function isPatchedLibrary($library) { + global $DB; + + $sql = "SELECT id + FROM {h5p_libraries} + WHERE machinename = :machinename + AND majorversion = :majorversion + AND minorversion = :minorversion + AND patchversion < :patchversion"; + + $library = $DB->get_records_sql( + $sql, + array( + 'machinename' => $library['machineName'], + 'majorversion' => $library['majorVersion'], + 'minorversion' => $library['minorVersion'], + 'patchversion' => $library['patchVersion'] + ), + 0, + 1 + ); + + return !empty($library); + } + + /** + * Is H5P in development mode? + * Implements isInDevMode. + * + * @return boolean TRUE if H5P development mode is active FALSE otherwise + */ + public function isInDevMode() { + return false; // Not supported (Files in moodle not editable). + } + + /** + * Is the current user allowed to update libraries? + * Implements mayUpdateLibraries. + * + * @return boolean TRUE if the user is allowed to update libraries, + * FALSE if the user is not allowed to update libraries. + */ + public function mayUpdateLibraries() { + // Currently, capabilities are not being set/used, so everyone can update libraries. + return true; + } + + /** + * Store data about a library. + * Implements saveLibraryData. + * + * Also fills in the libraryId in the libraryData object if the object is new. + * + * @param array $librarydata Associative array containing: + * - libraryId: The id of the library if it is an existing library + * - title: The library's name + * - machineName: The library machineName + * - majorVersion: The library's majorVersion + * - minorVersion: The library's minorVersion + * - patchVersion: The library's patchVersion + * - runnable: 1 if the library is a content type, 0 otherwise + * - fullscreen(optional): 1 if the library supports fullscreen, 0 otherwise + * - embedtypes: list of supported embed types + * - preloadedJs(optional): list of associative arrays containing: + * - path: path to a js file relative to the library root folder + * - preloadedCss(optional): list of associative arrays containing: + * - path: path to css file relative to the library root folder + * - dropLibraryCss(optional): list of associative arrays containing: + * - machineName: machine name for the librarys that are to drop their css + * - semantics(optional): Json describing the content structure for the library + * @param bool $new Whether it is a new or existing library. + */ + public function saveLibraryData(&$librarydata, $new = true) { + global $DB; + + // Some special properties needs some checking and converting before they can be saved. + $preloadedjs = $this->library_parameter_values_to_csv($librarydata, 'preloadedJs', 'path'); + $preloadedcss = $this->library_parameter_values_to_csv($librarydata, 'preloadedCss', 'path'); + $droplibrarycss = $this->library_parameter_values_to_csv($librarydata, 'dropLibraryCss', 'machineName'); + + if (!isset($librarydata['semantics'])) { + $librarydata['semantics'] = ''; + } + if (!isset($librarydata['fullscreen'])) { + $librarydata['fullscreen'] = 0; + } + $embedtypes = ''; + if (isset($librarydata['embedTypes'])) { + $embedtypes = implode(', ', $librarydata['embedTypes']); + } + + $library = (object) array( + 'title' => $librarydata['title'], + 'machinename' => $librarydata['machineName'], + 'majorversion' => $librarydata['majorVersion'], + 'minorversion' => $librarydata['minorVersion'], + 'patchversion' => $librarydata['patchVersion'], + 'runnable' => $librarydata['runnable'], + 'fullscreen' => $librarydata['fullscreen'], + 'embedtypes' => $embedtypes, + 'preloadedjs' => $preloadedjs, + 'preloadedcss' => $preloadedcss, + 'droplibrarycss' => $droplibrarycss, + 'semantics' => $librarydata['semantics'], + 'addto' => isset($librarydata['addTo']) ? json_encode($librarydata['addTo']) : null, + ); + + if ($new) { + // Create new library and keep track of id. + $library->id = $DB->insert_record('h5p_libraries', $library); + $librarydata['libraryId'] = $library->id; + } else { + // Update library data. + $library->id = $librarydata['libraryId']; + // Save library data. + $DB->update_record('h5p_libraries', $library); + // Remove old dependencies. + $this->deleteLibraryDependencies($librarydata['libraryId']); + } + } + + /** + * Insert new content. + * Implements insertContent. + * + * @param array $content An associative array containing: + * - id: The content id + * - params: The content in json format + * - library: An associative array containing: + * - libraryId: The id of the main library for this content + * - disable: H5P Button display options + * - pathnamehash: The pathnamehash linking the record with the entry in the mdl_files table + * - contenthash: The contenthash linking the record with the entry in the mdl_files table + * @param int $contentmainid Main id for the content if this is a system that supports versions + * @return int The ID of the newly inserted content + */ + public function insertContent($content, $contentmainid = null) { + return $this->updateContent($content); + } + + /** + * Update old content or insert new content. + * Implements updateContent. + * + * @param array $content An associative array containing: + * - id: The content id + * - params: The content in json format + * - library: An associative array containing: + * - libraryId: The id of the main library for this content + * - disable: H5P Button display options + * - pathnamehash: The pathnamehash linking the record with the entry in the mdl_files table + * - contenthash: The contenthash linking the record with the entry in the mdl_files table + * @param int $contentmainid Main id for the content if this is a system that supports versions + * @return int The ID of the newly inserted or updated content + */ + public function updateContent($content, $contentmainid = null) { + global $DB; + + if (!isset($content['pathnamehash'])) { + $content['pathnamehash'] = ''; + } + + if (!isset($content['contenthash'])) { + $content['contenthash'] = ''; + } + + $data = array( + 'jsoncontent' => $content['params'], + 'displayoptions' => $content['disable'], + 'mainlibraryid' => $content['library']['libraryId'], + 'timemodified' => time(), + 'filtered' => null, + 'pathnamehash' => $content['pathnamehash'], + 'contenthash' => $content['contenthash'] + ); + + if (!isset($content['id'])) { + $data['timecreated'] = $data['timemodified']; + $id = $DB->insert_record('h5p', $data); + } else { + $id = $data['id'] = $content['id']; + $DB->update_record('h5p', $data); + } + + return $id; + } + + /** + * Resets marked user data for the given content. + * Implements resetContentUserData. + * + * @param int $contentid The h5p content id + */ + public function resetContentUserData($contentid) { + // Currently, we do not store user data for a content. + } + + /** + * Save what libraries a library is depending on. + * Implements saveLibraryDependencies. + * + * @param int $libraryid Library Id for the library we're saving dependencies for + * @param array $dependencies List of dependencies as associative arrays containing: + * - machineName: The library machineName + * - majorVersion: The library's majorVersion + * - minorVersion: The library's minorVersion + * @param string $dependencytype The type of dependency + */ + public function saveLibraryDependencies($libraryid, $dependencies, $dependencytype) { + global $DB; + + foreach ($dependencies as $dependency) { + // Find dependency library. + $dependencylibrary = $DB->get_record('h5p_libraries', + array( + 'machinename' => $dependency['machineName'], + 'majorversion' => $dependency['majorVersion'], + 'minorversion' => $dependency['minorVersion'] + ) + ); + + // Create relation. + $DB->insert_record('h5p_library_dependencies', array( + 'libraryid' => $libraryid, + 'requiredlibraryid' => $dependencylibrary->id, + 'dependencytype' => $dependencytype + )); + } + } + + /** + * Give an H5P the same library dependencies as a given H5P. + * Implements copyLibraryUsage. + * + * @param int $contentid Id identifying the content + * @param int $copyfromid Id identifying the content to be copied + * @param int $contentmainid Main id for the content, typically used in frameworks + */ + public function copyLibraryUsage($contentid, $copyfromid, $contentmainid = null) { + // Currently not being called. + } + + /** + * Deletes content data. + * Implements deleteContentData. + * + * @param int $contentid Id identifying the content + */ + public function deleteContentData($contentid) { + global $DB; + + // Remove content. + $DB->delete_records('h5p', array('id' => $contentid)); + + // Remove content library dependencies. + $this->deleteLibraryUsage($contentid); + } + + /** + * Delete what libraries a content item is using. + * Implements deleteLibraryUsage. + * + * @param int $contentid Content Id of the content we'll be deleting library usage for + */ + public function deleteLibraryUsage($contentid) { + global $DB; + + $DB->delete_records('h5p_contents_libraries', array('h5pid' => $contentid)); + } + + /** + * Saves what libraries the content uses. + * Implements saveLibraryUsage. + * + * @param int $contentid Id identifying the content + * @param array $librariesinuse List of libraries the content uses + */ + public function saveLibraryUsage($contentid, $librariesinuse) { + global $DB; + + $droplibrarycsslist = array(); + foreach ($librariesinuse as $dependency) { + if (!empty($dependency['library']['dropLibraryCss'])) { + $droplibrarycsslist = array_merge($droplibrarycsslist, + explode(', ', $dependency['library']['dropLibraryCss'])); + } + } + + foreach ($librariesinuse as $dependency) { + $dropcss = in_array($dependency['library']['machineName'], $droplibrarycsslist) ? 1 : 0; + $DB->insert_record('h5p_contents_libraries', array( + 'h5pid' => $contentid, + 'libraryid' => $dependency['library']['libraryId'], + 'dependencytype' => $dependency['type'], + 'dropcss' => $dropcss, + 'weight' => $dependency['weight'] + )); + } + } + + /** + * Get number of content/nodes using a library, and the number of dependencies to other libraries. + * Implements getLibraryUsage. + * + * @param int $id Library identifier + * @param boolean $skipcontent Optional. Set as true to get number of content instances for library + * @return array The array contains two elements, keyed by 'content' and 'libraries'. + * Each element contains a number + */ + public function getLibraryUsage($id, $skipcontent = false) { + global $DB; + + if ($skipcontent) { + $content = -1; + } else { + $sql = "SELECT COUNT(distinct c.id) + FROM {h5p_libraries} l + JOIN {h5p_contents_libraries} cl ON l.id = cl.libraryid + JOIN {h5p} c ON cl.h5pid = c.id + WHERE l.id = :libraryid"; + + $sqlargs = array( + 'libraryid' => $id + ); + + $content = $DB->count_records_sql($sql, $sqlargs); + } + + $libraries = $DB->count_records('h5p_library_dependencies', ['requiredlibraryid' => $id]); + + return array( + 'content' => $content, + 'libraries' => $libraries, + ); + } + + /** + * Loads a library. + * Implements loadLibrary. + * + * @param string $machinename The library's machine name + * @param int $majorversion The library's major version + * @param int $minorversion The library's minor version + * @return array|bool Returns FALSE if the library does not exist + * Otherwise an associative array containing: + * - libraryId: The id of the library if it is an existing library, + * - title: The library's name, + * - machineName: The library machineName + * - majorVersion: The library's majorVersion + * - minorVersion: The library's minorVersion + * - patchVersion: The library's patchVersion + * - runnable: 1 if the library is a content type, 0 otherwise + * - fullscreen: 1 if the library supports fullscreen, 0 otherwise + * - embedTypes: list of supported embed types + * - preloadedJs: comma separated string with js file paths + * - preloadedCss: comma separated sting with css file paths + * - dropLibraryCss: list of associative arrays containing: + * - machineName: machine name for the librarys that are to drop their css + * - semantics: Json describing the content structure for the library + * - preloadedDependencies(optional): list of associative arrays containing: + * - machineName: Machine name for a library this library is depending on + * - majorVersion: Major version for a library this library is depending on + * - minorVersion: Minor for a library this library is depending on + * - dynamicDependencies(optional): list of associative arrays containing: + * - machineName: Machine name for a library this library is depending on + * - majorVersion: Major version for a library this library is depending on + * - minorVersion: Minor for a library this library is depending on + */ + public function loadLibrary($machinename, $majorversion, $minorversion) { + global $DB; + + $library = $DB->get_record('h5p_libraries', array( + 'machinename' => $machinename, + 'majorversion' => $majorversion, + 'minorversion' => $minorversion + )); + + if (!$library) { + return false; + } + + $librarydata = array( + 'libraryId' => $library->id, + 'title' => $library->title, + 'machineName' => $library->machinename, + 'majorVersion' => $library->majorversion, + 'minorVersion' => $library->minorversion, + 'patchVersion' => $library->patchversion, + 'runnable' => $library->runnable, + 'fullscreen' => $library->fullscreen, + 'embedTypes' => $library->embedtypes, + 'preloadedJs' => $library->preloadedjs, + 'preloadedCss' => $library->preloadedcss, + 'dropLibraryCss' => $library->droplibrarycss, + 'semantics' => $library->semantics + ); + + $sql = 'SELECT hl.id, hl.machinename, hl.majorversion, hl.minorversion, hll.dependencytype + FROM {h5p_library_dependencies} hll + JOIN {h5p_libraries} hl ON hll.requiredlibraryid = hl.id + WHERE hll.libraryid = :libraryid + ORDER BY hl.id ASC'; + + $sqlargs = array( + 'libraryid' => $library->id + ); + + $dependencies = $DB->get_records_sql($sql, $sqlargs); + + foreach ($dependencies as $dependency) { + $librarydata[$dependency->dependencytype . 'Dependencies'][] = array( + 'machineName' => $dependency->machinename, + 'majorVersion' => $dependency->majorversion, + 'minorVersion' => $dependency->minorversion + ); + } + + return $librarydata; + } + + /** + * Loads library semantics. + * Implements loadLibrarySemantics. + * + * @param string $name Machine name for the library + * @param int $majorversion The library's major version + * @param int $minorversion The library's minor version + * @return string The library's semantics as json + */ + public function loadLibrarySemantics($name, $majorversion, $minorversion) { + global $DB; + + $semantics = $DB->get_field('h5p_libraries', 'semantics', + array( + 'machinename' => $name, + 'majorversion' => $majorversion, + 'minorversion' => $minorversion + ) + ); + + return ($semantics === false ? null : $semantics); + } + + /** + * Makes it possible to alter the semantics, adding custom fields, etc. + * Implements alterLibrarySemantics. + * + * @param array $semantics Associative array representing the semantics + * @param string $name The library's machine name + * @param int $majorversion The library's major version + * @param int $minorversion The library's minor version + */ + public function alterLibrarySemantics(&$semantics, $name, $majorversion, $minorversion) { + global $DB; + + $library = $DB->get_record('h5p_libraries', + array( + 'machinename' => $name, + 'majorversion' => $majorversion, + 'minorversion' => $minorversion, + ) + ); + + if ($library) { + $library->semantics = json_encode($semantics); + $DB->update_record('h5p_libraries', $library); + } + } + + /** + * Delete all dependencies belonging to given library. + * Implements deleteLibraryDependencies. + * + * @param int $libraryid Library identifier + */ + public function deleteLibraryDependencies($libraryid) { + global $DB; + + $DB->delete_records('h5p_library_dependencies', array('libraryid' => $libraryid)); + } + + /** + * Start an atomic operation against the dependency storage. + * Implements lockDependencyStorage. + */ + public function lockDependencyStorage() { + // Library development mode not supported. + } + + /** + * Start an atomic operation against the dependency storage. + * Implements unlockDependencyStorage. + */ + public function unlockDependencyStorage() { + // Library development mode not supported. + } + + /** + * Delete a library from database and file system. + * Implements deleteLibrary. + * + * @param stdClass $library Library object with id, name, major version and minor version + */ + public function deleteLibrary($library) { + global $DB; + + $fs = new \core_h5p\file_storage(); + // Delete the library from the file system. + $fs->delete_library(array('libraryId' => $library->id)); + + // Remove library data from database. + $DB->delete_records('h5p_library_dependencies', array('libraryid' => $library->id)); + $DB->delete_records('h5p_libraries', array('id' => $library->id)); + } + + /** + * Load content. + * Implements loadContent. + * + * @param int $id Content identifier + * @return array Associative array containing: + * - id: Identifier for the content + * - params: json content as string + * - embedType: list of supported embed types + * - disable: H5P Button display options + * - title: H5P content title + * - slug: Human readable content identifier that is unique + * - libraryId: Id for the main library + * - libraryName: The library machine name + * - libraryMajorVersion: The library's majorVersion + * - libraryMinorVersion: The library's minorVersion + * - libraryEmbedTypes: CSV of the main library's embed types + * - libraryFullscreen: 1 if fullscreen is supported. 0 otherwise + * - metadata: The content's metadata + */ + public function loadContent($id) { + global $DB; + + $sql = "SELECT hc.id, hc.jsoncontent, hc.displayoptions, hl.id AS libraryid, + hl.machinename, hl.title, hl.majorversion, hl.minorversion, hl.fullscreen, + hl.embedtypes, hl.semantics, hc.filtered + FROM {h5p} hc + JOIN {h5p_libraries} hl ON hl.id = hc.mainlibraryid + WHERE hc.id = :h5pid"; + + $sqlargs = array( + 'h5pid' => $id + ); + + $data = $DB->get_record_sql($sql, $sqlargs); + + // Return null if not found. + if ($data === false) { + return null; + } + + // Some databases do not support camelCase, so we need to manually + // map the values to the camelCase names used by the H5P core. + $content = array( + 'id' => $data->id, + 'params' => $data->jsoncontent, + // It has been decided that the embedtype will be always set to 'iframe' (at least for now) because the 'div' + // may cause conflicts with CSS and JS in some cases. + 'embedType' => 'iframe', + 'disable' => $data->displayoptions, + 'title' => $data->title, + 'slug' => \H5PCore::slugify($data->title) . '-' . $data->id, + 'filtered' => $data->filtered, + 'libraryId' => $data->libraryid, + 'libraryName' => $data->machinename, + 'libraryMajorVersion' => $data->majorversion, + 'libraryMinorVersion' => $data->minorversion, + 'libraryEmbedTypes' => $data->embedtypes, + 'libraryFullscreen' => $data->fullscreen, + 'metadata' => '' + ); + + return $content; + } + + /** + * Load dependencies for the given content of the given type. + * Implements loadContentDependencies. + * + * @param int $id Content identifier + * @param int $type The dependency type + * @return array List of associative arrays containing: + * - libraryId: The id of the library if it is an existing library + * - machineName: The library machineName + * - majorVersion: The library's majorVersion + * - minorVersion: The library's minorVersion + * - patchVersion: The library's patchVersion + * - preloadedJs(optional): comma separated string with js file paths + * - preloadedCss(optional): comma separated sting with css file paths + * - dropCss(optional): csv of machine names + * - dependencyType: The dependency type + */ + public function loadContentDependencies($id, $type = null) { + global $DB; + + $query = "SELECT hcl.id AS unidepid, hl.id AS library_id, hl.machinename AS machine_name, + hl.majorversion AS major_version, hl.minorversion AS minor_version, + hl.patchversion AS patch_version, hl.preloadedcss AS preloaded_css, + hl.preloadedjs AS preloaded_js, hcl.dropcss AS drop_css, + hcl.dependencytype as dependency_type + FROM {h5p_contents_libraries} hcl + JOIN {h5p_libraries} hl ON hcl.libraryid = hl.id + WHERE hcl.h5pid = :h5pid"; + $queryargs = array( + 'h5pid' => $id + ); + + if ($type !== null) { + $query .= " AND hcl.dependencytype = :dependencytype"; + $queryargs['dependencytype'] = $type; + } + + $query .= " ORDER BY hcl.weight"; + $data = $DB->get_records_sql($query, $queryargs); + + $dependencies = array(); + foreach ($data as $dependency) { + unset($dependency->unidepid); + $dependencies[$dependency->machine_name] = \H5PCore::snakeToCamel($dependency); + } + + return $dependencies; + } + + /** + * Get the default behaviour for the display option defined. + * Implements getOption. + * + * @param string $name Identifier for the setting + * @param string $default Optional default value if settings is not set + * @return mixed Return The default \H5PDisplayOptionBehaviour for this display option + */ + public function getOption($name, $default = false) { + // TODO: Define the default behaviour for each display option. + // For now, all them are disabled by default, so only will be rendered when defined in the displayoptions DB field. + return \H5PDisplayOptionBehaviour::CONTROLLED_BY_AUTHOR_DEFAULT_OFF; + } + + /** + * Stores the given setting. + * Implements setOption. + * + * @param string $name Identifier for the setting + * @param mixed $value Data Whatever we want to store as the setting + */ + public function setOption($name, $value) { + // Currently not storing settings. + } + + /** + * This will update selected fields on the given content. + * Implements updateContentFields(). + * + * @param int $id Content identifier + * @param array $fields Content fields, e.g. filtered + */ + public function updateContentFields($id, $fields) { + global $DB; + + $content = new \stdClass(); + $content->id = $id; + + foreach ($fields as $name => $value) { + // Skip 'slug' as it currently does not exist in the h5p content table. + if ($name == 'slug') { + continue; + } + + $content->$name = $value; + } + + $DB->update_record('h5p', $content); + } + + /** + * Will clear filtered params for all the content that uses the specified. + * libraries. This means that the content dependencies will have to be rebuilt and the parameters re-filtered. + * Implements clearFilteredParameters(). + * + * @param array $libraryids Array of library ids + */ + public function clearFilteredParameters($libraryids) { + global $DB; + + if (empty($libraryids)) { + return; + } + + list($insql, $inparams) = $DB->get_in_or_equal($libraryids); + + $DB->set_field_select('h5p', 'filtered', null, + "mainlibraryid $insql", $inparams); + } + + /** + * Get number of contents that has to get their content dependencies rebuilt. + * and parameters re-filtered. + * Implements getNumNotFiltered(). + * + * @return int The number of contents that has to get their content dependencies rebuilt + * and parameters re-filtered + */ + public function getNumNotFiltered() { + global $DB; + + $sql = "SELECT COUNT(id) + FROM {h5p} + WHERE " . $DB->sql_compare_text('filtered') . " IS NULL"; + + return $DB->count_records_sql($sql); + } + + /** + * Get number of contents using library as main library. + * Implements getNumContent(). + * + * @param int $libraryid The library ID + * @param array $skip The array of h5p content ID's that should be ignored + * @return int The number of contents using library as main library + */ + public function getNumContent($libraryid, $skip = null) { + global $DB; + + $notinsql = ''; + $params = array(); + + if (!empty($skip)) { + list($sql, $params) = $DB->get_in_or_equal($skip, SQL_PARAMS_NAMED, 'param', false); + $notinsql = " AND id {$sql}"; + } + + $sql = "SELECT COUNT(id) + FROM {h5p} + WHERE mainlibraryid = :libraryid {$notinsql}"; + + $params['libraryid'] = $libraryid; + + return $DB->count_records_sql($sql, $params); + } + + /** + * Determines if content slug is used. + * Implements isContentSlugAvailable. + * + * @param string $slug The content slug + * @return boolean Whether the content slug is used + */ + public function isContentSlugAvailable($slug) { + // By default the slug should be available as it's currently generated as a unique + // value for each h5p content (not stored in the h5p table). + return true; + } + + /** + * Generates statistics from the event log per library. + * Implements getLibraryStats. + * + * @param string $type Type of event to generate stats for + * @return array Number values indexed by library name and version + */ + public function getLibraryStats($type) { + // Event logs are not being stored. + } + + /** + * Aggregate the current number of H5P authors. + * Implements getNumAuthors. + * + * @return int The current number of H5P authors + */ + public function getNumAuthors() { + // Currently, H5P authors are not being stored. + } + + /** + * Stores hash keys for cached assets, aggregated JavaScripts and + * stylesheets, and connects it to libraries so that we know which cache file + * to delete when a library is updated. + * Implements saveCachedAssets. + * + * @param string $key Hash key for the given libraries + * @param array $libraries List of dependencies(libraries) used to create the key + */ + public function saveCachedAssets($key, $libraries) { + global $DB; + + foreach ($libraries as $library) { + $cachedasset = new \stdClass(); + $cachedasset->libraryid = $library['libraryId']; + $cachedasset->hash = $key; + + $DB->insert_record('h5p_libraries_cachedassets', $cachedasset); + } + } + + /** + * Locate hash keys for given library and delete them. + * Used when cache file are deleted. + * Implements deleteCachedAssets. + * + * @param int $libraryid Library identifier + * @return array List of hash keys removed + */ + public function deleteCachedAssets($libraryid) { + global $DB; + + // Get all the keys so we can remove the files. + $results = $DB->get_records('h5p_libraries_cachedassets', ['libraryid' => $libraryid]); + + $hashes = array_map(function($result) { + return $result->hash; + }, $results); + + if (!empty($hashes)) { + list($sql, $params) = $DB->get_in_or_equal($hashes, SQL_PARAMS_NAMED); + // Remove all invalid keys. + $DB->delete_records_select('h5p_libraries_cachedassets', 'hash ' . $sql, $params); + } + + return $hashes; + } + + /** + * Get the amount of content items associated to a library. + * Implements getLibraryContentCount. + * + * return array The number of content items associated to a library + */ + public function getLibraryContentCount() { + global $DB; + + $contentcount = array(); + + $sql = "SELECT h.mainlibraryid, + l.machinename, + l.majorversion, + l.minorversion, + COUNT(h.id) AS count + FROM {h5p} h + LEFT JOIN {h5p_libraries} l + ON h.mainlibraryid = l.id + GROUP BY h.mainlibraryid, l.machinename, l.majorversion, l.minorversion"; + + // Count content using the same content type. + $res = $DB->get_records_sql($sql); + + // Extract results. + foreach ($res as $lib) { + $contentcount["{$lib->machinename} {$lib->majorversion}.{$lib->minorversion}"] = $lib->count; + } + + return $contentcount; + } + + /** + * Will trigger after the export file is created. + * Implements afterExportCreated. + * + * @param array $content The content + * @param string $filename The file name + */ + public function afterExportCreated($content, $filename) { + // Not being used. + } + + /** + * Check whether a user has permissions to execute an action, such as embed H5P content. + * Implements hasPermission. + * + * @param \H5PPermission $permission Permission type + * @param int $id Id need by platform to determine permission + * @return boolean true if the user can execute the action defined in $permission; false otherwise + */ + public function hasPermission($permission, $id = null) { + // H5P capabilities have not been introduced. + } + + /** + * Replaces existing content type cache with the one passed in. + * Implements replaceContentTypeCache. + * + * @param object $contenttypecache Json with an array called 'libraries' containing the new content type cache + * that should replace the old one + */ + public function replaceContentTypeCache($contenttypecache) { + // Currently, content type caches are not being stored. + } + + /** + * Checks if the given library has a higher version. + * Implements libraryHasUpgrade. + * + * @param array $library An associative array containing: + * - machineName: The library machineName + * - majorVersion: The library's majorVersion + * - minorVersion: The library's minorVersion + * @return boolean Whether the library has a higher version + */ + public function libraryHasUpgrade($library) { + global $DB; + + $sql = "SELECT id + FROM {h5p_libraries} + WHERE machinename = :machinename + AND (majorversion > :majorversion1 + OR (majorversion = :majorversion2 AND minorversion > :minorversion))"; + + $results = $DB->get_records_sql( + $sql, + array( + 'machinename' => $library['machineName'], + 'majorversion1' => $library['majorVersion'], + 'majorversion2' => $library['majorVersion'], + 'minorversion' => $library['minorVersion'] + ), + 0, + 1 + ); + + return !empty($results); + } + + /** + * Get type of h5p instance + * + * @param string|null $type Type of h5p instance to get + * @return \H5PContentValidator|\H5PCore|\H5PStorage|\H5PValidator|\core_h5p\framework|\H5peditor + */ + public static function instance($type = null) { + global $CFG; + static $interface, $core; + + if (!isset($interface)) { + $interface = new \core_h5p\framework(); + $fs = new \core_h5p\file_storage(); + $language = self::get_language(); + + $context = \context_system::instance(); + $url = "{$CFG->wwwroot}/pluginfile.php/{$context->id}/core_h5p"; + + $core = new \H5PCore($interface, $fs, $url, $language, true); + $core->aggregateAssets = !(isset($CFG->core_h5p_aggregate_assets) && $CFG->core_h5p_aggregate_assets === '0'); + } + + switch ($type) { + case 'validator': + return new \H5PValidator($interface, $core); + case 'storage': + return new \H5PStorage($interface, $core); + case 'contentvalidator': + return new \H5PContentValidator($interface, $core); + case 'interface': + return $interface; + case 'core': + default: + return $core; + } + } + + /** + * Get current H5P language code. + * + * @return string Language Code + */ + public static function get_language() { + static $map; + + if (empty($map)) { + // Create mapping for "converting" language codes. + $map = array( + 'no' => 'nb' + ); + } + + // Get current language in Moodle. + $language = str_replace('_', '-', strtolower(\current_language())); + + // Try to map. + return $map[$language] ?? $language; + } + + /** + * Store messages until they can be printed to the current user. + * + * @param string $type Type of messages, e.g. 'info', 'error', etc + * @param string $newmessage The message + * @param string $code The message code + */ + private function set_message(string $type, string $newmessage = null, string $code = null) { + global $SESSION; + + // We expect to get out an array of strings when getting info + // and an array of objects when getting errors for consistency across platforms. + // This implementation should be improved for consistency across the data type returned here. + if ($type === 'error') { + $SESSION->core_h5p_messages[$type][] = (object) array( + 'code' => $code, + 'message' => $newmessage + ); + } else { + $SESSION->core_h5p_messages[$type][] = $newmessage; + } + } + + /** + * Convert list of library parameter values to csv. + * + * @param array $librarydata Library data as found in library.json files + * @param string $key Key that should be found in $librarydata + * @param string $searchparam The library parameter (Default: 'path') + * @return string Library parameter values separated by ', ' + */ + private function library_parameter_values_to_csv(array $librarydata, string $key, string $searchparam = 'path'): string { + if (isset($librarydata[$key])) { + $parametervalues = array(); + foreach ($librarydata[$key] as $file) { + foreach ($file as $index => $value) { + if ($index === $searchparam) { + $parametervalues[] = $value; + } + } + } + return implode(', ', $parametervalues); + } + return ''; + } +}