h5p-php-library/h5p.classes.php

5499 lines
188 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
/**
* Interface defining functions the h5p library needs the framework to implement
*/
interface H5PFrameworkInterface {
/**
* Returns info for the current platform
*
* @return array
* An associative array containing:
* - name: The name of the platform, for instance "Wordpress"
* - version: The version of the platform, for instance "4.0"
* - h5pVersion: The version of the H5P plugin/module
*/
public function getPlatformInfo();
/**
* Fetches a file from a remote server using HTTP GET
*
* @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.
* @param bool $fullData Return additional response data such as headers and potentially other data
* @param array $headers Headers to send
* @param array $files Files to send
* @param string $method
*
* @return string|array The content (response body), or an array with data. NULL if something went wrong
*/
public function fetchExternalData($url, $data = NULL, $blocking = TRUE, $stream = NULL, $fullData = FALSE, $headers = array(), $files = array(), $method = 'POST');
/**
* Set the tutorial URL for a library. All versions of the library is set
*
* @param string $machineName
* @param string $tutorialUrl
*/
public function setLibraryTutorialUrl($machineName, $tutorialUrl);
/**
* Show the user an error message
*
* @param string $message The error message
* @param string $code An optional code
*/
public function setErrorMessage($message, $code = NULL);
/**
* Show the user an information message
*
* @param string $message
* The error message
*/
public function setInfoMessage($message);
/**
* Return messages
*
* @param string $type 'info' or 'error'
* @return string[]
*/
public function getMessages($type);
/**
* Translation function
*
* @param string $message
* The english string to be translated.
* @param array $replacements
* An associative array of replacements to make after translation. Incidences
* of any key in this array are replaced with the corresponding value. Based
* on the first character of the key, the value is escaped and/or themed:
* - !variable: inserted as is
* - @variable: escape plain text to HTML
* - %variable: escape text and theme as a placeholder for user-submitted
* content
* @return string Translated string
* Translated string
*/
public function t($message, $replacements = array());
/**
* Get URL to file in the specific library
* @param string $libraryFolderName
* @param string $fileName
* @return string URL to file
*/
public function getLibraryFileUrl($libraryFolderName, $fileName);
/**
* Get the Path to the last uploaded h5p
*
* @return string
* Path to the folder where the last uploaded h5p for this session is located.
*/
public function getUploadedH5pFolderPath();
/**
* Get the path to the last uploaded h5p file
*
* @return string
* Path to the last uploaded h5p
*/
public function getUploadedH5pPath();
/**
* Load addon libraries
*
* @return array
*/
public function loadAddons();
/**
* Load config for libraries
*
* @param array $libraries
* @return array
*/
public function getLibraryConfig($libraries = NULL);
/**
* Get a list of the current installed libraries
*
* @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();
/**
* Returns the URL to the library admin page
*
* @return string
* URL to admin page
*/
public function getAdminUrl();
/**
* Get id to an existing library.
* If version number is not specified, the newest version will be returned.
*
* @param string $machineName
* The librarys machine name
* @param int $majorVersion
* Optional major version number for library
* @param int $minorVersion
* Optional minor version number for library
* @return int
* The id of the specified library or FALSE
*/
public function getLibraryId($machineName, $majorVersion = NULL, $minorVersion = NULL);
/**
* Get file extension whitelist
*
* 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
*/
public function getWhitelist($isLibrary, $defaultContentWhitelist, $defaultLibraryWhitelist);
/**
* Is the library a patched version of an existing library?
*
* @param object $library
* An associative array containing:
* - machineName: The library machineName
* - majorVersion: The librarys majorVersion
* - minorVersion: The librarys minorVersion
* - patchVersion: The librarys patchVersion
* @return boolean
* TRUE if the library is a patched version of an existing library
* FALSE otherwise
*/
public function isPatchedLibrary($library);
/**
* Is H5P in development mode?
*
* @return boolean
* TRUE if H5P development mode is active
* FALSE otherwise
*/
public function isInDevMode();
/**
* Is the current user allowed to update libraries?
*
* @return boolean
* TRUE if the user is allowed to update libraries
* FALSE if the user is not allowed to update libraries
*/
public function mayUpdateLibraries();
/**
* Store data about a library
*
* Also fills in the libraryId in the libraryData object if the object is new
*
* @param object $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
* - metadataSettings: Associative array containing:
* - disable: 1 if the library should not support setting metadata (copyright etc)
* - disableExtraTitleField: 1 if the library don't need the extra title field
* - fullscreen(optional): 1 if the library supports fullscreen, 0 otherwise
* - embedTypes(optional): 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
* - language(optional): associative array containing:
* - languageCode: Translation in json format
* @param bool $new
* @return
*/
public function saveLibraryData(&$libraryData, $new = TRUE);
/**
* Insert new content.
*
* @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
* @param int $contentMainId
* Main id for the content if this is a system that supports versions
*/
public function insertContent($content, $contentMainId = NULL);
/**
* Update old content.
*
* @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
* @param int $contentMainId
* Main id for the content if this is a system that supports versions
*/
public function updateContent($content, $contentMainId = NULL);
/**
* Resets marked user data for the given content.
*
* @param int $contentId
*/
public function resetContentUserData($contentId);
/**
* Save what libraries a library is depending on
*
* @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 $dependency_type
* What type of dependency this is, the following values are allowed:
* - editor
* - preloaded
* - dynamic
*/
public function saveLibraryDependencies($libraryId, $dependencies, $dependency_type);
/**
* Give an H5P the same library dependencies as a given H5P
*
* @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
* That supports versions. (In this case the content id will typically be
* the version id, and the contentMainId will be the frameworks content id
*/
public function copyLibraryUsage($contentId, $copyFromId, $contentMainId = NULL);
/**
* Deletes content data
*
* @param int $contentId
* Id identifying the content
*/
public function deleteContentData($contentId);
/**
* Delete what libraries a content item is using
*
* @param int $contentId
* Content Id of the content we'll be deleting library usage for
*/
public function deleteLibraryUsage($contentId);
/**
* Saves what libraries the content uses
*
* @param int $contentId
* Id identifying the content
* @param array $librariesInUse
* List of libraries the content uses. Libraries consist of associative arrays with:
* - library: Associative array containing:
* - dropLibraryCss(optional): comma separated list of machineNames
* - machineName: Machine name for the library
* - libraryId: Id of the library
* - type: The dependency type. Allowed values:
* - editor
* - dynamic
* - preloaded
*/
public function saveLibraryUsage($contentId, $librariesInUse);
/**
* Get number of content/nodes using a library, and the number of
* dependencies to other libraries
*
* @param int $libraryId
* Library identifier
* @param boolean $skipContent
* Flag to indicate if content usage should be skipped
* @return array
* Associative array containing:
* - content: Number of content using the library
* - libraries: Number of libraries depending on the library
*/
public function getLibraryUsage($libraryId, $skipContent = FALSE);
/**
* Loads a library
*
* @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|FALSE
* 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(optional): 1 if the library supports fullscreen, 0 otherwise
* - embedTypes(optional): list of supported embed types
* - preloadedJs(optional): comma separated string with js file paths
* - preloadedCss(optional): comma separated sting with css file paths
* - 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
* - 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
* - editorDependencies(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);
/**
* Loads library semantics.
*
* @param string $machineName
* 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($machineName, $majorVersion, $minorVersion);
/**
* Makes it possible to alter the semantics, adding custom fields, etc.
*
* @param array $semantics
* Associative array representing the semantics
* @param string $machineName
* 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, $machineName, $majorVersion, $minorVersion);
/**
* Delete all dependencies belonging to given library
*
* @param int $libraryId
* Library identifier
*/
public function deleteLibraryDependencies($libraryId);
/**
* Start an atomic operation against the dependency storage
*/
public function lockDependencyStorage();
/**
* Stops an atomic operation against the dependency storage
*/
public function unlockDependencyStorage();
/**
* Delete a library from database and file system
*
* @param stdClass $library
* Library object with id, name, major version and minor version.
*/
public function deleteLibrary($library);
/**
* Load content.
*
* @param int $id
* Content identifier
* @return array
* Associative array containing:
* - contentId: Identifier for the content
* - params: json content as string
* - embedType: csv of embed types
* - title: The contents title
* - language: Language code for the content
* - 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.
*/
public function loadContent($id);
/**
* Load dependencies for the given content of the given type.
*
* @param int $id
* Content identifier
* @param int $type
* Dependency types. Allowed values:
* - editor
* - preloaded
* - dynamic
* @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
*/
public function loadContentDependencies($id, $type = NULL);
/**
* Get stored setting.
*
* @param string $name
* Identifier for the setting
* @param string $default
* Optional default value if settings is not set
* @return mixed
* Whatever has been stored as the setting
*/
public function getOption($name, $default = NULL);
/**
* Stores the given setting.
* For example when did we last check h5p.org for updates to our libraries.
*
* @param string $name
* Identifier for the setting
* @param mixed $value Data
* Whatever we want to store as the setting
*/
public function setOption($name, $value);
/**
* This will update selected fields on the given content.
*
* @param int $id Content identifier
* @param array $fields Content fields, e.g. filtered or slug.
*/
public function updateContentFields($id, $fields);
/**
* 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.
*
* @param array $library_ids
*/
public function clearFilteredParameters($library_ids);
/**
* Get number of contents that has to get their content dependencies rebuilt
* and parameters re-filtered.
*
* @return int
*/
public function getNumNotFiltered();
/**
* Get number of contents using library as main library.
*
* @param int $libraryId
* @param array $skip
* @return int
*/
public function getNumContent($libraryId, $skip = NULL);
/**
* Determines if content slug is used.
*
* @param string $slug
* @return boolean
*/
public function isContentSlugAvailable($slug);
/**
* Generates statistics from the event log per library
*
* @param string $type Type of event to generate stats for
* @return array Number values indexed by library name and version
*/
public function getLibraryStats($type);
/**
* Aggregate the current number of H5P authors
* @return int
*/
public function getNumAuthors();
/**
* 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.
*
* @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);
/**
* Locate hash keys for given library and delete them.
* Used when cache file are deleted.
*
* @param int $library_id
* Library identifier
* @return array
* List of hash keys removed
*/
public function deleteCachedAssets($library_id);
/**
* Get the amount of content items associated to a library
* return int
*/
public function getLibraryContentCount();
/**
* Will trigger after the export file is created.
*/
public function afterExportCreated($content, $filename);
/**
* Check if user has permissions to an action
*
* @method hasPermission
* @param [H5PPermission] $permission Permission type, ref H5PPermission
* @param [int] $id Id need by platform to determine permission
* @return boolean
*/
public function hasPermission($permission, $id = NULL);
/**
* Replaces existing content type cache with the one passed in
*
* @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);
/**
* Checks if the given library has a higher version.
*
* @param array $library
* @return boolean
*/
public function libraryHasUpgrade($library);
/**
* Replace content hub metadata cache
*
* @param JsonSerializable $metadata Metadata as received from content hub
* @param string $lang Language in ISO 639-1
*
* @return mixed
*/
public function replaceContentHubMetadataCache($metadata, $lang);
/**
* Get content hub metadata cache from db
*
* @param string $lang Language code in ISO 639-1
*
* @return JsonSerializable Json string
*/
public function getContentHubMetadataCache($lang = 'en');
/**
* Get time of last content hub metadata check
*
* @param string $lang Language code iin ISO 639-1 format
*
* @return string|null Time in RFC7231 format
*/
public function getContentHubMetadataChecked($lang = 'en');
/**
* Set time of last content hub metadata check
*
* @param int|null $time Time in RFC7231 format
* @param string $lang Language code iin ISO 639-1 format
*
* @return bool True if successful
*/
public function setContentHubMetadataChecked($time, $lang = 'en');
}
/**
* This class is used for validating H5P files
*/
class H5PValidator {
public $h5pF;
public $h5pC;
// Schemas used to validate the h5p files
private $h5pRequired = array(
'title' => '/^.{1,255}$/',
'language' => '/^[-a-zA-Z]{1,10}$/',
'preloadedDependencies' => array(
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
),
'mainLibrary' => '/^[$a-z_][0-9a-z_\.$]{1,254}$/i',
'embedTypes' => array('iframe', 'div'),
);
private $h5pOptional = array(
'contentType' => '/^.{1,255}$/',
'dynamicDependencies' => array(
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
),
// deprecated
'author' => '/^.{1,255}$/',
'authors' => array(
'name' => '/^.{1,255}$/',
'role' => '/^\w+$/',
),
'source' => '/^(http[s]?:\/\/.+)$/',
'license' => '/^(CC BY|CC BY-SA|CC BY-ND|CC BY-NC|CC BY-NC-SA|CC BY-NC-ND|CC0 1\.0|GNU GPL|PD|ODC PDDL|CC PDM|U|C)$/',
'licenseVersion' => '/^(1\.0|2\.0|2\.5|3\.0|4\.0)$/',
'licenseExtras' => '/^.{1,5000}$/s',
'yearsFrom' => '/^([0-9]{1,4})$/',
'yearsTo' => '/^([0-9]{1,4})$/',
'changes' => array(
'date' => '/^[0-9]{2}-[0-9]{2}-[0-9]{2} [0-9]{1,2}:[0-9]{2}:[0-9]{2}$/',
'author' => '/^.{1,255}$/',
'log' => '/^.{1,5000}$/s'
),
'authorComments' => '/^.{1,5000}$/s',
'w' => '/^[0-9]{1,4}$/',
'h' => '/^[0-9]{1,4}$/',
// deprecated
'metaKeywords' => '/^.{1,}$/',
// deprecated
'metaDescription' => '/^.{1,}$/',
);
// Schemas used to validate the library files
private $libraryRequired = array(
'title' => '/^.{1,255}$/',
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
'patchVersion' => '/^[0-9]{1,5}$/',
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
'runnable' => '/^(0|1)$/',
);
private $libraryOptional = array(
'author' => '/^.{1,255}$/',
'license' => '/^(cc-by|cc-by-sa|cc-by-nd|cc-by-nc|cc-by-nc-sa|cc-by-nc-nd|pd|cr|MIT|GPL1|GPL2|GPL3|MPL|MPL2)$/',
'description' => '/^.{1,}$/',
'metadataSettings' => array(
'disable' => '/^(0|1)$/',
'disableExtraTitleField' => '/^(0|1)$/'
),
'dynamicDependencies' => array(
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
),
'preloadedDependencies' => array(
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
),
'editorDependencies' => array(
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
),
'preloadedJs' => array(
'path' => '/^((\\\|\/)?[a-z_\-\s0-9\.]+)+\.js$/i',
),
'preloadedCss' => array(
'path' => '/^((\\\|\/)?[a-z_\-\s0-9\.]+)+\.css$/i',
),
'dropLibraryCss' => array(
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
),
'w' => '/^[0-9]{1,4}$/',
'h' => '/^[0-9]{1,4}$/',
'embedTypes' => array('iframe', 'div'),
'fullscreen' => '/^(0|1)$/',
'coreApi' => array(
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
),
);
/**
* Constructor for the H5PValidator
*
* @param H5PFrameworkInterface $H5PFramework
* The frameworks implementation of the H5PFrameworkInterface
* @param H5PCore $H5PCore
*/
public function __construct($H5PFramework, $H5PCore) {
$this->h5pF = $H5PFramework;
$this->h5pC = $H5PCore;
$this->h5pCV = new H5PContentValidator($this->h5pF, $this->h5pC);
}
/**
* Validates a .h5p file
*
* @param bool $skipContent
* @param bool $upgradeOnly
* @return bool TRUE if the .h5p file is valid
* TRUE if the .h5p file is valid
*/
public function isValidPackage($skipContent = FALSE, $upgradeOnly = FALSE) {
// Check dependencies, make sure Zip is present
if (!class_exists('ZipArchive')) {
$this->h5pF->setErrorMessage($this->h5pF->t('Your PHP version does not support ZipArchive.'), 'zip-archive-unsupported');
unlink($tmpPath);
return FALSE;
}
if (!extension_loaded('mbstring')) {
$this->h5pF->setErrorMessage($this->h5pF->t('The mbstring PHP extension is not loaded. H5P need this to function properly'), 'mbstring-unsupported');
unlink($tmpPath);
return FALSE;
}
// Create a temporary dir to extract package in.
$tmpDir = $this->h5pF->getUploadedH5pFolderPath();
$tmpPath = $this->h5pF->getUploadedH5pPath();
// Only allow files with the .h5p extension:
if (strtolower(substr($tmpPath, -3)) !== 'h5p') {
$this->h5pF->setErrorMessage($this->h5pF->t('The file you uploaded is not a valid HTML5 Package (It does not have the .h5p file extension)'), 'missing-h5p-extension');
unlink($tmpPath);
return FALSE;
}
// Extract and then remove the package file.
$zip = new ZipArchive;
// Open the package
if ($zip->open($tmpPath) !== TRUE) {
$this->h5pF->setErrorMessage($this->h5pF->t('The file you uploaded is not a valid HTML5 Package (We are unable to unzip it)'), 'unable-to-unzip');
unlink($tmpPath);
return FALSE;
}
if ($this->h5pC->disableFileCheck !== TRUE) {
list($contentWhitelist, $contentRegExp) = $this->getWhitelistRegExp(FALSE);
list($libraryWhitelist, $libraryRegExp) = $this->getWhitelistRegExp(TRUE);
}
$canInstall = $this->h5pC->mayUpdateLibraries();
$valid = TRUE;
$libraries = array();
$totalSize = 0;
$mainH5pExists = FALSE;
$contentExists = FALSE;
// Check for valid file types, JSON files + file sizes before continuing to unpack.
for ($i = 0; $i < $zip->numFiles; $i++) {
$fileStat = $zip->statIndex($i);
if (!empty($this->h5pC->maxFileSize) && $fileStat['size'] > $this->h5pC->maxFileSize) {
// Error file is too large
$this->h5pF->setErrorMessage($this->h5pF->t('One of the files inside the package exceeds the maximum file size allowed. (%file %used > %max)', array('%file' => $fileStat['name'], '%used' => ($fileStat['size'] / 1048576) . ' MB', '%max' => ($this->h5pC->maxFileSize / 1048576) . ' MB')), 'file-size-too-large');
$valid = FALSE;
}
$totalSize += $fileStat['size'];
$fileName = mb_strtolower($fileStat['name']);
if (preg_match('/(^[\._]|\/[\._])/', $fileName) !== 0) {
continue; // Skip any file or folder starting with a . or _
}
elseif ($fileName === 'h5p.json') {
$mainH5pExists = TRUE;
}
elseif ($fileName === 'content/content.json') {
$contentExists = TRUE;
}
elseif (substr($fileName, 0, 8) === 'content/') {
// This is a content file, check that the file type is allowed
if ($skipContent === FALSE && $this->h5pC->disableFileCheck !== TRUE && !preg_match($contentRegExp, $fileName)) {
$this->h5pF->setErrorMessage($this->h5pF->t('File "%filename" not allowed. Only files with the following extensions are allowed: %files-allowed.', array('%filename' => $fileStat['name'], '%files-allowed' => $contentWhitelist)), 'not-in-whitelist');
$valid = FALSE;
}
}
elseif ($canInstall && strpos($fileName, '/') !== FALSE) {
// This is a library file, check that the file type is allowed
if ($this->h5pC->disableFileCheck !== TRUE && !preg_match($libraryRegExp, $fileName)) {
$this->h5pF->setErrorMessage($this->h5pF->t('File "%filename" not allowed. Only files with the following extensions are allowed: %files-allowed.', array('%filename' => $fileStat['name'], '%files-allowed' => $libraryWhitelist)), 'not-in-whitelist');
$valid = FALSE;
}
// Further library validation happens after the files are extracted
}
}
if (!empty($this->h5pC->maxTotalSize) && $totalSize > $this->h5pC->maxTotalSize) {
// Error total size of the zip is too large
$this->h5pF->setErrorMessage($this->h5pF->t('The total size of the unpacked files exceeds the maximum size allowed. (%used > %max)', array('%used' => ($totalSize / 1048576) . ' MB', '%max' => ($this->h5pC->maxTotalSize / 1048576) . ' MB')), 'total-size-too-large');
$valid = FALSE;
}
if ($skipContent === FALSE) {
// Not skipping content, require two valid JSON files from the package
if (!$contentExists) {
$this->h5pF->setErrorMessage($this->h5pF->t('A valid content folder is missing'), 'invalid-content-folder');
$valid = FALSE;
}
else {
$contentJsonData = $this->getJson($tmpPath, $zip, 'content/content.json'); // TODO: Is this case-senstivie?
if ($contentJsonData === NULL) {
return FALSE; // Breaking error when reading from the archive.
}
elseif ($contentJsonData === FALSE) {
$valid = FALSE; // Validation error when parsing JSON
}
}
if (!$mainH5pExists) {
$this->h5pF->setErrorMessage($this->h5pF->t('A valid main h5p.json file is missing'), 'invalid-h5p-json-file');
$valid = FALSE;
}
else {
$mainH5pData = $this->getJson($tmpPath, $zip, 'h5p.json', TRUE);
if ($mainH5pData === NULL) {
return FALSE; // Breaking error when reading from the archive.
}
elseif ($mainH5pData === FALSE) {
$valid = FALSE; // Validation error when parsing JSON
}
elseif (!$this->isValidH5pData($mainH5pData, 'h5p.json', $this->h5pRequired, $this->h5pOptional)) {
$this->h5pF->setErrorMessage($this->h5pF->t('The main h5p.json file is not valid'), 'invalid-h5p-json-file'); // Is this message a bit redundant?
$valid = FALSE;
}
}
}
if (!$valid) {
// If something has failed during the initial checks of the package
// we will not unpack it or continue validation.
$zip->close();
unlink($tmpPath);
return FALSE;
}
// Extract the files from the package
for ($i = 0; $i < $zip->numFiles; $i++) {
$fileName = $zip->statIndex($i)['name'];
if (preg_match('/(^[\._]|\/[\._])/', $fileName) !== 0) {
continue; // Skip any file or folder starting with a . or _
}
$isContentFile = (substr($fileName, 0, 8) === 'content/');
$isFolder = (strpos($fileName, '/') !== FALSE);
if ($skipContent !== FALSE && $isContentFile) {
continue; // Skipping any content files
}
if (!($isContentFile || ($canInstall && $isFolder))) {
continue; // Not something we want to unpack
}
// Get file stream
$fileStream = $zip->getStream($fileName);
if (!$fileStream) {
// This is a breaking error, there's no need to continue. (the rest of the files will fail as well)
$this->h5pF->setErrorMessage($this->h5pF->t('Unable to read file from the package: %fileName', array('%fileName' => $fileName)), 'unable-to-read-package-file');
$zip->close();
unlink($path);
H5PCore::deleteFileTree($tmpDir);
return FALSE;
}
// Use file interface to allow overrides
$this->h5pC->fs->saveFileFromZip($tmpDir, $fileName, $fileStream);
// Clean up
if (is_resource($fileStream)) {
fclose($fileStream);
}
}
// We're done with the zip file, clean up the stuff
$zip->close();
unlink($tmpPath);
if ($canInstall) {
// Process and validate libraries using the unpacked library folders
$files = scandir($tmpDir);
foreach ($files as $file) {
$filePath = $tmpDir . '/' . $file;
if ($file === '.' || $file === '..' || $file === 'content' || !is_dir($filePath)) {
continue; // Skip
}
$libraryH5PData = $this->getLibraryData($file, $filePath, $tmpDir);
if ($libraryH5PData === FALSE) {
$valid = FALSE;
continue; // Failed, but continue validating the rest of the libraries
}
// Library's directory name must be:
// - <machineName>
// - or -
// - <machineName>-<majorVersion>.<minorVersion>
// where machineName, majorVersion and minorVersion is read from library.json
if ($libraryH5PData['machineName'] !== $file && H5PCore::libraryToString($libraryH5PData, TRUE) !== $file) {
$this->h5pF->setErrorMessage($this->h5pF->t('Library directory name must match machineName or machineName-majorVersion.minorVersion (from library.json). (Directory: %directoryName , machineName: %machineName, majorVersion: %majorVersion, minorVersion: %minorVersion)', array(
'%directoryName' => $file,
'%machineName' => $libraryH5PData['machineName'],
'%majorVersion' => $libraryH5PData['majorVersion'],
'%minorVersion' => $libraryH5PData['minorVersion'])), 'library-directory-name-mismatch');
$valid = FALSE;
continue; // Failed, but continue validating the rest of the libraries
}
$libraryH5PData['uploadDirectory'] = $filePath;
$libraries[H5PCore::libraryToString($libraryH5PData)] = $libraryH5PData;
}
}
if ($valid) {
if ($upgradeOnly) {
// When upgrading, we only add the already installed libraries, and
// the new dependent libraries
$upgrades = array();
foreach ($libraries as $libString => &$library) {
// Is this library already installed?
if ($this->h5pF->getLibraryId($library['machineName']) !== FALSE) {
$upgrades[$libString] = $library;
}
}
while ($missingLibraries = $this->getMissingLibraries($upgrades)) {
foreach ($missingLibraries as $libString => $missing) {
$library = $libraries[$libString];
if ($library) {
$upgrades[$libString] = $library;
}
}
}
$libraries = $upgrades;
}
$this->h5pC->librariesJsonData = $libraries;
if ($skipContent === FALSE) {
$this->h5pC->mainJsonData = $mainH5pData;
$this->h5pC->contentJsonData = $contentJsonData;
$libraries['mainH5pData'] = $mainH5pData; // Check for the dependencies in h5p.json as well as in the libraries
}
$missingLibraries = $this->getMissingLibraries($libraries);
foreach ($missingLibraries as $libString => $missing) {
if ($this->h5pC->getLibraryId($missing, $libString)) {
unset($missingLibraries[$libString]);
}
}
if (!empty($missingLibraries)) {
// We still have missing libraries, check if our main library has an upgrade (BUT only if we has content)
$mainDependency = NULL;
if (!$skipContent && !empty($mainH5pData)) {
foreach ($mainH5pData['preloadedDependencies'] as $dep) {
if ($dep['machineName'] === $mainH5pData['mainLibrary']) {
$mainDependency = $dep;
}
}
}
if ($skipContent || !$mainDependency || !$this->h5pF->libraryHasUpgrade(array(
'machineName' => $mainDependency['machineName'],
'majorVersion' => $mainDependency['majorVersion'],
'minorVersion' => $mainDependency['minorVersion']
))) {
foreach ($missingLibraries as $libString => $library) {
$this->h5pF->setErrorMessage($this->h5pF->t('Missing required library @library', array('@library' => $libString)), 'missing-required-library');
$valid = FALSE;
}
if (!$this->h5pC->mayUpdateLibraries()) {
$this->h5pF->setInfoMessage($this->h5pF->t("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."));
$valid = FALSE;
}
}
}
}
if (!$valid) {
H5PCore::deleteFileTree($tmpDir);
}
return $valid;
}
/**
* Help read JSON from the archive
*
* @param string $path
* @param ZipArchive $zip
* @param string $file
* @return mixed JSON content if valid, FALSE for invalid, NULL for breaking error.
*/
private function getJson($path, $zip, $file, $assoc = FALSE) {
// Get stream
$stream = $zip->getStream($file);
if (!$stream) {
// Breaking error, no need to continue validating.
$this->h5pF->setErrorMessage($this->h5pF->t('Unable to read file from the package: %fileName', array('%fileName' => $file)), 'unable-to-read-package-file');
$zip->close();
unlink($path);
return NULL;
}
// Read data
$contents = '';
while (!feof($stream)) {
$contents .= fread($stream, 2);
}
// Decode the data
$json = json_decode($contents, $assoc);
if ($json === NULL) {
// JSON cannot be decoded or the recursion limit has been reached.
$this->h5pF->setErrorMessage($this->h5pF->t('Unable to parse JSON from the package: %fileName', array('%fileName' => $file)), 'unable-to-parse-package');
return FALSE;
}
// All OK
return $json;
}
/**
* Help retrieve file type regexp whitelist from plugin.
*
* @param bool $isLibrary Separate list with more allowed file types
* @return string RegExp
*/
private function getWhitelistRegExp($isLibrary) {
$whitelist = $this->h5pF->getWhitelist($isLibrary, H5PCore::$defaultContentWhitelist, H5PCore::$defaultLibraryWhitelistExtras);
return array($whitelist, '/\.(' . preg_replace('/ +/i', '|', preg_quote($whitelist)) . ')$/i');
}
/**
* Validates a H5P library
*
* @param string $file
* Name of the library folder
* @param string $filePath
* Path to the library folder
* @param string $tmpDir
* Path to the temporary upload directory
* @return boolean|array
* H5P data from library.json and semantics if the library is valid
* FALSE if the library isn't valid
*/
public function getLibraryData($file, $filePath, $tmpDir) {
if (preg_match('/^[\w0-9\-\.]{1,255}$/i', $file) === 0) {
$this->h5pF->setErrorMessage($this->h5pF->t('Invalid library name: %name', array('%name' => $file)), 'invalid-library-name');
return FALSE;
}
$h5pData = $this->getJsonData($filePath . '/' . 'library.json');
if ($h5pData === FALSE) {
$this->h5pF->setErrorMessage($this->h5pF->t('Could not find library.json file with valid json format for library %name', array('%name' => $file)), 'invalid-library-json-file');
return FALSE;
}
// validate json if a semantics file is provided
$semanticsPath = $filePath . '/' . 'semantics.json';
if (file_exists($semanticsPath)) {
$semantics = $this->getJsonData($semanticsPath, TRUE);
if ($semantics === FALSE) {
$this->h5pF->setErrorMessage($this->h5pF->t('Invalid semantics.json file has been included in the library %name', array('%name' => $file)), 'invalid-semantics-json-file');
return FALSE;
}
else {
$h5pData['semantics'] = $semantics;
}
}
// validate language folder if it exists
$languagePath = $filePath . '/' . 'language';
if (is_dir($languagePath)) {
$languageFiles = scandir($languagePath);
foreach ($languageFiles as $languageFile) {
if (in_array($languageFile, array('.', '..'))) {
continue;
}
if (preg_match('/^(-?[a-z]+){1,7}\.json$/i', $languageFile) === 0) {
$this->h5pF->setErrorMessage($this->h5pF->t('Invalid language file %file in library %library', array('%file' => $languageFile, '%library' => $file)), 'invalid-language-file');
return FALSE;
}
$languageJson = $this->getJsonData($languagePath . '/' . $languageFile, TRUE);
if ($languageJson === FALSE) {
$this->h5pF->setErrorMessage($this->h5pF->t('Invalid language file %languageFile has been included in the library %name', array('%languageFile' => $languageFile, '%name' => $file)), 'invalid-language-file');
return FALSE;
}
$parts = explode('.', $languageFile); // $parts[0] is the language code
$h5pData['language'][$parts[0]] = $languageJson;
}
}
// Check for icon:
$h5pData['hasIcon'] = file_exists($filePath . '/' . 'icon.svg');
$validLibrary = $this->isValidH5pData($h5pData, $file, $this->libraryRequired, $this->libraryOptional);
//$validLibrary = $this->h5pCV->validateContentFiles($filePath, TRUE) && $validLibrary;
if (isset($h5pData['preloadedJs'])) {
$validLibrary = $this->isExistingFiles($h5pData['preloadedJs'], $tmpDir, $file) && $validLibrary;
}
if (isset($h5pData['preloadedCss'])) {
$validLibrary = $this->isExistingFiles($h5pData['preloadedCss'], $tmpDir, $file) && $validLibrary;
}
if ($validLibrary) {
return $h5pData;
}
else {
return FALSE;
}
}
/**
* Use the dependency declarations to find any missing libraries
*
* @param array $libraries
* A multidimensional array of libraries keyed with machineName first and majorVersion second
* @return array
* A list of libraries that are missing keyed with machineName and holds objects with
* machineName, majorVersion and minorVersion properties
*/
private function getMissingLibraries($libraries) {
$missing = array();
foreach ($libraries as $library) {
if (isset($library['preloadedDependencies'])) {
$missing = array_merge($missing, $this->getMissingDependencies($library['preloadedDependencies'], $libraries));
}
if (isset($library['dynamicDependencies'])) {
$missing = array_merge($missing, $this->getMissingDependencies($library['dynamicDependencies'], $libraries));
}
if (isset($library['editorDependencies'])) {
$missing = array_merge($missing, $this->getMissingDependencies($library['editorDependencies'], $libraries));
}
}
return $missing;
}
/**
* Helper function for getMissingLibraries, searches for dependency required libraries in
* the provided list of libraries
*
* @param array $dependencies
* A list of objects with machineName, majorVersion and minorVersion properties
* @param array $libraries
* An array of libraries keyed with machineName
* @return
* A list of libraries that are missing keyed with machineName and holds objects with
* machineName, majorVersion and minorVersion properties
*/
private function getMissingDependencies($dependencies, $libraries) {
$missing = array();
foreach ($dependencies as $dependency) {
$libString = H5PCore::libraryToString($dependency);
if (!isset($libraries[$libString])) {
$missing[$libString] = $dependency;
}
}
return $missing;
}
/**
* Figure out if the provided file paths exists
*
* Triggers error messages if files doesn't exist
*
* @param array $files
* List of file paths relative to $tmpDir
* @param string $tmpDir
* Path to the directory where the $files are stored.
* @param string $library
* Name of the library we are processing
* @return boolean
* TRUE if all the files excists
*/
private function isExistingFiles($files, $tmpDir, $library) {
foreach ($files as $file) {
$path = str_replace(array('/', '\\'), '/', $file['path']);
if (!file_exists($tmpDir . '/' . $library . '/' . $path)) {
$this->h5pF->setErrorMessage($this->h5pF->t('The file "%file" is missing from library: "%name"', array('%file' => $path, '%name' => $library)), 'library-missing-file');
return FALSE;
}
}
return TRUE;
}
/**
* Validates h5p.json and library.json data
*
* Error messages are triggered if the data isn't valid
*
* @param array $h5pData
* h5p data
* @param string $library_name
* Name of the library we are processing
* @param array $required
* Validation pattern for required properties
* @param array $optional
* Validation pattern for optional properties
* @return boolean
* TRUE if the $h5pData is valid
*/
private function isValidH5pData($h5pData, $library_name, $required, $optional) {
$valid = $this->isValidRequiredH5pData($h5pData, $required, $library_name);
$valid = $this->isValidOptionalH5pData($h5pData, $optional, $library_name) && $valid;
// Check the library's required API version of Core.
// If no requirement is set this implicitly means 1.0.
if (isset($h5pData['coreApi']) && !empty($h5pData['coreApi'])) {
if (($h5pData['coreApi']['majorVersion'] > H5PCore::$coreApi['majorVersion']) ||
( ($h5pData['coreApi']['majorVersion'] == H5PCore::$coreApi['majorVersion']) &&
($h5pData['coreApi']['minorVersion'] > H5PCore::$coreApi['minorVersion']) )) {
$this->h5pF->setErrorMessage(
$this->h5pF->t('The system was unable to install the <em>%component</em> 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.',
array(
'%component' => (isset($h5pData['title']) ? $h5pData['title'] : $library_name),
'%current' => H5PCore::$coreApi['majorVersion'] . '.' . H5PCore::$coreApi['minorVersion'],
'%required' => $h5pData['coreApi']['majorVersion'] . '.' . $h5pData['coreApi']['minorVersion']
)
),
'api-version-unsupported'
);
$valid = false;
}
}
return $valid;
}
/**
* Helper function for isValidH5pData
*
* Validates the optional part of the h5pData
*
* Triggers error messages
*
* @param array $h5pData
* h5p data
* @param array $requirements
* Validation pattern
* @param string $library_name
* Name of the library we are processing
* @return boolean
* TRUE if the optional part of the $h5pData is valid
*/
private function isValidOptionalH5pData($h5pData, $requirements, $library_name) {
$valid = TRUE;
foreach ($h5pData as $key => $value) {
if (isset($requirements[$key])) {
$valid = $this->isValidRequirement($value, $requirements[$key], $library_name, $key) && $valid;
}
// Else: ignore, a package can have parameters that this library doesn't care about, but that library
// specific implementations does care about...
}
return $valid;
}
/**
* Validate a requirement given as regexp or an array of requirements
*
* @param mixed $h5pData
* The data to be validated
* @param mixed $requirement
* The requirement the data is to be validated against, regexp or array of requirements
* @param string $library_name
* Name of the library we are validating(used in error messages)
* @param string $property_name
* Name of the property we are validating(used in error messages)
* @return boolean
* TRUE if valid, FALSE if invalid
*/
private function isValidRequirement($h5pData, $requirement, $library_name, $property_name) {
$valid = TRUE;
if (is_string($requirement)) {
if ($requirement == 'boolean') {
if (!is_bool($h5pData)) {
$this->h5pF->setErrorMessage($this->h5pF->t("Invalid data provided for %property in %library. Boolean expected.", array('%property' => $property_name, '%library' => $library_name)));
$valid = FALSE;
}
}
else {
// The requirement is a regexp, match it against the data
if (is_string($h5pData) || is_int($h5pData)) {
if (preg_match($requirement, $h5pData) === 0) {
$this->h5pF->setErrorMessage($this->h5pF->t("Invalid data provided for %property in %library", array('%property' => $property_name, '%library' => $library_name)));
$valid = FALSE;
}
}
else {
$this->h5pF->setErrorMessage($this->h5pF->t("Invalid data provided for %property in %library", array('%property' => $property_name, '%library' => $library_name)));
$valid = FALSE;
}
}
}
elseif (is_array($requirement)) {
// We have sub requirements
if (is_array($h5pData)) {
if (is_array(current($h5pData))) {
foreach ($h5pData as $sub_h5pData) {
$valid = $this->isValidRequiredH5pData($sub_h5pData, $requirement, $library_name) && $valid;
}
}
else {
$valid = $this->isValidRequiredH5pData($h5pData, $requirement, $library_name) && $valid;
}
}
else {
$this->h5pF->setErrorMessage($this->h5pF->t("Invalid data provided for %property in %library", array('%property' => $property_name, '%library' => $library_name)));
$valid = FALSE;
}
}
else {
$this->h5pF->setErrorMessage($this->h5pF->t("Can't read the property %property in %library", array('%property' => $property_name, '%library' => $library_name)));
$valid = FALSE;
}
return $valid;
}
/**
* Validates the required h5p data in libraray.json and h5p.json
*
* @param mixed $h5pData
* Data to be validated
* @param array $requirements
* Array with regexp to validate the data against
* @param string $library_name
* Name of the library we are validating (used in error messages)
* @return boolean
* TRUE if all the required data exists and is valid, FALSE otherwise
*/
private function isValidRequiredH5pData($h5pData, $requirements, $library_name) {
$valid = TRUE;
foreach ($requirements as $required => $requirement) {
if (is_int($required)) {
// We have an array of allowed options
return $this->isValidH5pDataOptions($h5pData, $requirements, $library_name);
}
if (isset($h5pData[$required])) {
$valid = $this->isValidRequirement($h5pData[$required], $requirement, $library_name, $required) && $valid;
}
else {
$this->h5pF->setErrorMessage($this->h5pF->t('The required property %property is missing from %library', array('%property' => $required, '%library' => $library_name)), 'missing-required-property');
$valid = FALSE;
}
}
return $valid;
}
/**
* Validates h5p data against a set of allowed values(options)
*
* @param array $selected
* The option(s) that has been specified
* @param array $allowed
* The allowed options
* @param string $library_name
* Name of the library we are validating (used in error messages)
* @return boolean
* TRUE if the specified data is valid, FALSE otherwise
*/
private function isValidH5pDataOptions($selected, $allowed, $library_name) {
$valid = TRUE;
foreach ($selected as $value) {
if (!in_array($value, $allowed)) {
$this->h5pF->setErrorMessage($this->h5pF->t('Illegal option %option in %library', array('%option' => $value, '%library' => $library_name)), 'illegal-option-in-library');
$valid = FALSE;
}
}
return $valid;
}
/**
* Fetch json data from file
*
* @param string $filePath
* Path to the file holding the json string
* @param boolean $return_as_string
* If true the json data will be decoded in order to validate it, but will be
* returned as string
* @return mixed
* FALSE if the file can't be read or the contents can't be decoded
* string if the $return as string parameter is set
* array otherwise
*/
private function getJsonData($filePath, $return_as_string = FALSE) {
$json = file_get_contents($filePath);
if ($json === FALSE) {
return FALSE; // Cannot read from file.
}
$jsonData = json_decode($json, TRUE);
if ($jsonData === NULL) {
return FALSE; // JSON cannot be decoded or the recursion limit has been reached.
}
return $return_as_string ? $json : $jsonData;
}
/**
* Helper function that copies an array
*
* @param array $array
* The array to be copied
* @return array
* Copy of $array. All objects are cloned
*/
private function arrayCopy(array $array) {
$result = array();
foreach ($array as $key => $val) {
if (is_array($val)) {
$result[$key] = self::arrayCopy($val);
}
elseif (is_object($val)) {
$result[$key] = clone $val;
}
else {
$result[$key] = $val;
}
}
return $result;
}
}
/**
* This class is used for saving H5P files
*/
class H5PStorage {
public $h5pF;
public $h5pC;
public $contentId = NULL; // Quick fix so WP can get ID of new content.
/**
* Constructor for the H5PStorage
*
* @param H5PFrameworkInterface|object $H5PFramework
* The frameworks implementation of the H5PFrameworkInterface
* @param H5PCore $H5PCore
*/
public function __construct(H5PFrameworkInterface $H5PFramework, H5PCore $H5PCore) {
$this->h5pF = $H5PFramework;
$this->h5pC = $H5PCore;
}
/**
* Saves a H5P file
*
* @param null $content
* @param int $contentMainId
* The main id for the content we are saving. This is used if the framework
* we're integrating with uses content id's and version id's
* @param bool $skipContent
* @param array $options
* @return bool TRUE if one or more libraries were updated
* TRUE if one or more libraries were updated
* FALSE otherwise
*/
public function savePackage($content = NULL, $contentMainId = NULL, $skipContent = FALSE, $options = array()) {
if ($this->h5pC->mayUpdateLibraries()) {
// Save the libraries we processed during validation
$this->saveLibraries();
}
if (!$skipContent) {
$basePath = $this->h5pF->getUploadedH5pFolderPath();
$current_path = $basePath . '/' . 'content';
// Save content
if ($content === NULL) {
$content = array();
}
if (!is_array($content)) {
$content = array('id' => $content);
}
// Find main library version
foreach ($this->h5pC->mainJsonData['preloadedDependencies'] as $dep) {
if ($dep['machineName'] === $this->h5pC->mainJsonData['mainLibrary']) {
$dep['libraryId'] = $this->h5pC->getLibraryId($dep);
$content['library'] = $dep;
break;
}
}
$content['params'] = file_get_contents($current_path . '/' . 'content.json');
if (isset($options['disable'])) {
$content['disable'] = $options['disable'];
}
$content['id'] = $this->h5pC->saveContent($content, $contentMainId);
$this->contentId = $content['id'];
try {
// Save content folder contents
$this->h5pC->fs->saveContent($current_path, $content);
}
catch (Exception $e) {
$this->h5pF->setErrorMessage($e->getMessage(), 'save-content-failed');
}
// Remove temp content folder
H5PCore::deleteFileTree($basePath);
}
}
/**
* Helps savePackage.
*
* @return int Number of libraries saved
*/
private function saveLibraries() {
// Keep track of the number of libraries that have been saved
$newOnes = 0;
$oldOnes = 0;
// Go through libraries that came with this package
foreach ($this->h5pC->librariesJsonData as $libString => &$library) {
// Find local library identifier
$libraryId = $this->h5pC->getLibraryId($library, $libString);
// Assume new library
$new = TRUE;
if ($libraryId) {
// Found old library
$library['libraryId'] = $libraryId;
if ($this->h5pF->isPatchedLibrary($library)) {
// This is a newer version than ours. Upgrade!
$new = FALSE;
}
else {
$library['saveDependencies'] = FALSE;
// This is an older version, no need to save.
continue;
}
}
// Indicate that the dependencies of this library should be saved.
$library['saveDependencies'] = TRUE;
// Convert metadataSettings values to boolean & json_encode it before saving
$library['metadataSettings'] = isset($library['metadataSettings']) ?
H5PMetadata::boolifyAndEncodeSettings($library['metadataSettings']) :
NULL;
$this->h5pF->saveLibraryData($library, $new);
// Save library folder
$this->h5pC->fs->saveLibrary($library);
// Remove cached assets that uses this library
if ($this->h5pC->aggregateAssets && isset($library['libraryId'])) {
$removedKeys = $this->h5pF->deleteCachedAssets($library['libraryId']);
$this->h5pC->fs->deleteCachedAssets($removedKeys);
}
// Remove tmp folder
H5PCore::deleteFileTree($library['uploadDirectory']);
if ($new) {
$newOnes++;
}
else {
$oldOnes++;
}
}
// Go through the libraries again to save dependencies.
$library_ids = array();
foreach ($this->h5pC->librariesJsonData as &$library) {
if (!$library['saveDependencies']) {
continue;
}
// TODO: Should the table be locked for this operation?
// Remove any old dependencies
$this->h5pF->deleteLibraryDependencies($library['libraryId']);
// Insert the different new ones
if (isset($library['preloadedDependencies'])) {
$this->h5pF->saveLibraryDependencies($library['libraryId'], $library['preloadedDependencies'], 'preloaded');
}
if (isset($library['dynamicDependencies'])) {
$this->h5pF->saveLibraryDependencies($library['libraryId'], $library['dynamicDependencies'], 'dynamic');
}
if (isset($library['editorDependencies'])) {
$this->h5pF->saveLibraryDependencies($library['libraryId'], $library['editorDependencies'], 'editor');
}
$library_ids[] = $library['libraryId'];
}
// Make sure libraries dependencies, parameter filtering and export files gets regenerated for all content who uses these libraries.
if (!empty($library_ids)) {
$this->h5pF->clearFilteredParameters($library_ids);
}
// Tell the user what we've done.
if ($newOnes && $oldOnes) {
if ($newOnes === 1) {
if ($oldOnes === 1) {
// Singular Singular
$message = $this->h5pF->t('Added %new new H5P library and updated %old old one.', array('%new' => $newOnes, '%old' => $oldOnes));
}
else {
// Singular Plural
$message = $this->h5pF->t('Added %new new H5P library and updated %old old ones.', array('%new' => $newOnes, '%old' => $oldOnes));
}
}
else {
// Plural
if ($oldOnes === 1) {
// Plural Singular
$message = $this->h5pF->t('Added %new new H5P libraries and updated %old old one.', array('%new' => $newOnes, '%old' => $oldOnes));
}
else {
// Plural Plural
$message = $this->h5pF->t('Added %new new H5P libraries and updated %old old ones.', array('%new' => $newOnes, '%old' => $oldOnes));
}
}
}
elseif ($newOnes) {
if ($newOnes === 1) {
// Singular
$message = $this->h5pF->t('Added %new new H5P library.', array('%new' => $newOnes));
}
else {
// Plural
$message = $this->h5pF->t('Added %new new H5P libraries.', array('%new' => $newOnes));
}
}
elseif ($oldOnes) {
if ($oldOnes === 1) {
// Singular
$message = $this->h5pF->t('Updated %old H5P library.', array('%old' => $oldOnes));
}
else {
// Plural
$message = $this->h5pF->t('Updated %old H5P libraries.', array('%old' => $oldOnes));
}
}
if (isset($message)) {
$this->h5pF->setInfoMessage($message);
}
}
/**
* Delete an H5P package
*
* @param $content
*/
public function deletePackage($content) {
$this->h5pC->fs->deleteContent($content);
$this->h5pC->fs->deleteExport(($content['slug'] ? $content['slug'] . '-' : '') . $content['id'] . '.h5p');
$this->h5pF->deleteContentData($content['id']);
}
/**
* Copy/clone an H5P package
*
* May for instance be used if the content is being revisioned without
* uploading a new H5P package
*
* @param int $contentId
* The new content id
* @param int $copyFromId
* The content id of the content that should be cloned
* @param int $contentMainId
* The main id of the new content (used in frameworks that support revisioning)
*/
public function copyPackage($contentId, $copyFromId, $contentMainId = NULL) {
$this->h5pC->fs->cloneContent($copyFromId, $contentId);
$this->h5pF->copyLibraryUsage($contentId, $copyFromId, $contentMainId);
}
}
/**
* This class is used for exporting zips
*/
Class H5PExport {
public $h5pF;
public $h5pC;
/**
* Constructor for the H5PExport
*
* @param H5PFrameworkInterface|object $H5PFramework
* The frameworks implementation of the H5PFrameworkInterface
* @param H5PCore $H5PCore
* Reference to an instance of H5PCore
*/
public function __construct(H5PFrameworkInterface $H5PFramework, H5PCore $H5PCore) {
$this->h5pF = $H5PFramework;
$this->h5pC = $H5PCore;
}
/**
* Reverts the replace pattern used by the text editor
*
* @param string $value
* @return string
*/
private static function revertH5PEditorTextEscape($value) {
return str_replace('&lt;', '<', str_replace('&gt;', '>', str_replace('&#039;', "'", str_replace('&quot;', '"', $value))));
}
/**
* Return path to h5p package.
*
* Creates package if not already created
*
* @param array $content
* @return string
*/
public function createExportFile($content) {
// Get path to temporary folder, where export will be contained
$tmpPath = $this->h5pC->fs->getTmpPath();
mkdir($tmpPath, 0777, true);
try {
// Create content folder and populate with files
$this->h5pC->fs->exportContent($content['id'], "{$tmpPath}/content");
}
catch (Exception $e) {
$this->h5pF->setErrorMessage($this->h5pF->t($e->getMessage()), 'failed-creating-export-file');
H5PCore::deleteFileTree($tmpPath);
return FALSE;
}
// Update content.json with content from database
file_put_contents("{$tmpPath}/content/content.json", $content['filtered']);
// Make embedType into an array
$embedTypes = explode(', ', $content['embedType']);
// Build h5p.json, the en-/de-coding will ensure proper escaping
$h5pJson = array (
'title' => self::revertH5PEditorTextEscape($content['title']),
'language' => (isset($content['language']) && strlen(trim($content['language'])) !== 0) ? $content['language'] : 'und',
'mainLibrary' => $content['library']['name'],
'embedTypes' => $embedTypes
);
foreach(array('authors', 'source', 'license', 'licenseVersion', 'licenseExtras' ,'yearFrom', 'yearTo', 'changes', 'authorComments', 'defaultLanguage') as $field) {
if (isset($content['metadata'][$field]) && $content['metadata'][$field] !== '') {
if (($field !== 'authors' && $field !== 'changes') || (count($content['metadata'][$field]) > 0)) {
$h5pJson[$field] = json_decode(json_encode($content['metadata'][$field], TRUE));
}
}
}
// Remove all values that are not set
foreach ($h5pJson as $key => $value) {
if (!isset($value)) {
unset($h5pJson[$key]);
}
}
// Add dependencies to h5p
foreach ($content['dependencies'] as $dependency) {
$library = $dependency['library'];
try {
$exportFolder = NULL;
// Determine path of export library
if (isset($this->h5pC) && isset($this->h5pC->h5pD)) {
// Tries to find library in development folder
$isDevLibrary = $this->h5pC->h5pD->getLibrary(
$library['machineName'],
$library['majorVersion'],
$library['minorVersion']
);
if ($isDevLibrary !== NULL && isset($library['path'])) {
$exportFolder = "/" . $library['path'];
}
}
// Export required libraries
$this->h5pC->fs->exportLibrary($library, $tmpPath, $exportFolder);
}
catch (Exception $e) {
$this->h5pF->setErrorMessage($this->h5pF->t($e->getMessage()), 'failed-creating-export-file');
H5PCore::deleteFileTree($tmpPath);
return FALSE;
}
// Do not add editor dependencies to h5p json.
if ($dependency['type'] === 'editor') {
continue;
}
// Add to h5p.json dependencies
$h5pJson[$dependency['type'] . 'Dependencies'][] = array(
'machineName' => $library['machineName'],
'majorVersion' => $library['majorVersion'],
'minorVersion' => $library['minorVersion']
);
}
// Save h5p.json
$results = print_r(json_encode($h5pJson), true);
file_put_contents("{$tmpPath}/h5p.json", $results);
// Get a complete file list from our tmp dir
$files = array();
self::populateFileList($tmpPath, $files);
// Get path to temporary export target file
$tmpFile = $this->h5pC->fs->getTmpPath();
// Create new zip instance.
$zip = new ZipArchive();
$zip->open($tmpFile, ZipArchive::CREATE | ZipArchive::OVERWRITE);
// Add all the files from the tmp dir.
foreach ($files as $file) {
// Please note that the zip format has no concept of folders, we must
// use forward slashes to separate our directories.
if (file_exists(realpath($file->absolutePath))) {
$zip->addFile(realpath($file->absolutePath), $file->relativePath);
}
}
// Close zip and remove tmp dir
$zip->close();
H5PCore::deleteFileTree($tmpPath);
$filename = $content['slug'] . '-' . $content['id'] . '.h5p';
try {
// Save export
$this->h5pC->fs->saveExport($tmpFile, $filename);
}
catch (Exception $e) {
$this->h5pF->setErrorMessage($this->h5pF->t($e->getMessage()), 'failed-creating-export-file');
return false;
}
unlink($tmpFile);
$this->h5pF->afterExportCreated($content, $filename);
return true;
}
/**
* Recursive function the will add the files of the given directory to the
* given files list. All files are objects with an absolute path and
* a relative path. The relative path is forward slashes only! Great for
* use in zip files and URLs.
*
* @param string $dir path
* @param array $files list
* @param string $relative prefix. Optional
*/
private static function populateFileList($dir, &$files, $relative = '') {
$strip = strlen($dir) + 1;
$contents = glob($dir . '/' . '*');
if (!empty($contents)) {
foreach ($contents as $file) {
$rel = $relative . substr($file, $strip);
if (is_dir($file)) {
self::populateFileList($file, $files, $rel . '/');
}
else {
$files[] = (object) array(
'absolutePath' => $file,
'relativePath' => $rel
);
}
}
}
}
/**
* Delete .h5p file
*
* @param array $content object
*/
public function deleteExport($content) {
$this->h5pC->fs->deleteExport(($content['slug'] ? $content['slug'] . '-' : '') . $content['id'] . '.h5p');
}
/**
* Add editor libraries to the list of libraries
*
* These are not supposed to go into h5p.json, but must be included with the rest
* of the libraries
*
* TODO This is a private function that is not currently being used
*
* @param array $libraries
* List of libraries keyed by machineName
* @param array $editorLibraries
* List of libraries keyed by machineName
* @return array List of libraries keyed by machineName
*/
private function addEditorLibraries($libraries, $editorLibraries) {
foreach ($editorLibraries as $editorLibrary) {
$libraries[$editorLibrary['machineName']] = $editorLibrary;
}
return $libraries;
}
}
abstract class H5PPermission {
const DOWNLOAD_H5P = 0;
const EMBED_H5P = 1;
const CREATE_RESTRICTED = 2;
const UPDATE_LIBRARIES = 3;
const INSTALL_RECOMMENDED = 4;
const COPY_H5P = 8;
}
abstract class H5PDisplayOptionBehaviour {
const NEVER_SHOW = 0;
const CONTROLLED_BY_AUTHOR_DEFAULT_ON = 1;
const CONTROLLED_BY_AUTHOR_DEFAULT_OFF = 2;
const ALWAYS_SHOW = 3;
const CONTROLLED_BY_PERMISSIONS = 4;
}
abstract class H5PContentHubSyncStatus {
const NOT_SYNCED = 0;
const SYNCED = 1;
const WAITING = 2;
const FAILED = 3;
}
abstract class H5PContentStatus {
const STATUS_UNPUBLISHED = 0;
const STATUS_DOWNLOADED = 1;
const STATUS_WAITING = 2;
const STATUS_FAILED_DOWNLOAD = 3;
const STATUS_FAILED_VALIDATION = 4;
const STATUS_SUSPENDED = 5;
}
abstract class H5PHubEndpoints {
const CONTENT_TYPES = 'api.h5p.org/v1/content-types/';
const SITES = 'api.h5p.org/v1/sites';
const METADATA = 'hub-api.h5p.org/v1/metadata';
const CONTENT = 'hub-api.h5p.org/v1/contents';
const REGISTER = 'hub-api.h5p.org/v1/accounts';
public static function createURL($endpoint) {
$protocol = (extension_loaded('openssl') ? 'https' : 'http');
return "{$protocol}://{$endpoint}";
}
}
/**
* Functions and storage shared by the other H5P classes
*/
class H5PCore {
public static $coreApi = array(
'majorVersion' => 1,
'minorVersion' => 24
);
public static $styles = array(
'styles/h5p.css',
'styles/h5p-confirmation-dialog.css',
'styles/h5p-core-button.css'
);
public static $scripts = array(
'js/jquery.js',
'js/h5p.js',
'js/h5p-event-dispatcher.js',
'js/h5p-x-api-event.js',
'js/h5p-x-api.js',
'js/h5p-content-type.js',
'js/h5p-confirmation-dialog.js',
'js/h5p-action-bar.js',
'js/request-queue.js',
);
public static $adminScripts = array(
'js/jquery.js',
'js/h5p-utils.js',
);
public static $defaultContentWhitelist = 'json png jpg jpeg gif bmp tif tiff svg eot ttf woff woff2 otf webm mp4 ogg mp3 m4a wav txt pdf rtf doc docx xls xlsx ppt pptx odt ods odp xml csv diff patch swf md textile vtt webvtt gltf glb';
public static $defaultLibraryWhitelistExtras = 'js css';
public $librariesJsonData, $contentJsonData, $mainJsonData, $h5pF, $fs, $h5pD, $disableFileCheck;
const SECONDS_IN_WEEK = 604800;
private $exportEnabled;
// Disable flags
const DISABLE_NONE = 0;
const DISABLE_FRAME = 1;
const DISABLE_DOWNLOAD = 2;
const DISABLE_EMBED = 4;
const DISABLE_COPYRIGHT = 8;
const DISABLE_ABOUT = 16;
const DISPLAY_OPTION_FRAME = 'frame';
const DISPLAY_OPTION_DOWNLOAD = 'export';
const DISPLAY_OPTION_EMBED = 'embed';
const DISPLAY_OPTION_COPYRIGHT = 'copyright';
const DISPLAY_OPTION_ABOUT = 'icon';
const DISPLAY_OPTION_COPY = 'copy';
// Map flags to string
public static $disable = array(
self::DISABLE_FRAME => self::DISPLAY_OPTION_FRAME,
self::DISABLE_DOWNLOAD => self::DISPLAY_OPTION_DOWNLOAD,
self::DISABLE_EMBED => self::DISPLAY_OPTION_EMBED,
self::DISABLE_COPYRIGHT => self::DISPLAY_OPTION_COPYRIGHT
);
/**
* Constructor for the H5PCore
*
* @param H5PFrameworkInterface $H5PFramework
* The frameworks implementation of the H5PFrameworkInterface
* @param string|\H5PFileStorage $path H5P file storage directory or class.
* @param string $url To file storage directory.
* @param string $language code. Defaults to english.
* @param boolean $export enabled?
*/
public function __construct(H5PFrameworkInterface $H5PFramework, $path, $url, $language = 'en', $export = FALSE) {
$this->h5pF = $H5PFramework;
$this->fs = ($path instanceof \H5PFileStorage ? $path : new \H5PDefaultStorage($path));
$this->url = $url;
$this->exportEnabled = $export;
$this->development_mode = H5PDevelopment::MODE_NONE;
$this->aggregateAssets = FALSE; // Off by default.. for now
$this->detectSiteType();
$this->fullPluginPath = preg_replace('/\/[^\/]+[\/]?$/', '' , dirname(__FILE__));
// Standard regex for converting copied files paths
$this->relativePathRegExp = '/^((\.\.\/){1,2})(.*content\/)?(\d+|editor)\/(.+)$/';
}
/**
* Save content and clear cache.
*
* @param array $content
* @param null|int $contentMainId
* @return int Content ID
*/
public function saveContent($content, $contentMainId = NULL) {
if (isset($content['id'])) {
$this->h5pF->updateContent($content, $contentMainId);
}
else {
$content['id'] = $this->h5pF->insertContent($content, $contentMainId);
}
// Some user data for content has to be reset when the content changes.
$this->h5pF->resetContentUserData($contentMainId ? $contentMainId : $content['id']);
return $content['id'];
}
/**
* Load content.
*
* @param int $id for content.
* @return object
*/
public function loadContent($id) {
$content = $this->h5pF->loadContent($id);
if ($content !== NULL) {
// Validate main content's metadata
$validator = new H5PContentValidator($this->h5pF, $this);
$content['metadata'] = $validator->validateMetadata($content['metadata']);
$content['library'] = array(
'id' => $content['libraryId'],
'name' => $content['libraryName'],
'majorVersion' => $content['libraryMajorVersion'],
'minorVersion' => $content['libraryMinorVersion'],
'embedTypes' => $content['libraryEmbedTypes'],
'fullscreen' => $content['libraryFullscreen'],
);
unset($content['libraryId'], $content['libraryName'], $content['libraryEmbedTypes'], $content['libraryFullscreen']);
// // TODO: Move to filterParameters?
// if (isset($this->h5pD)) {
// // TODO: Remove Drupal specific stuff
// $json_content_path = file_create_path(file_directory_path() . '/' . variable_get('h5p_default_path', 'h5p') . '/content/' . $id . '/content.json');
// if (file_exists($json_content_path) === TRUE) {
// $json_content = file_get_contents($json_content_path);
// if (json_decode($json_content, TRUE) !== FALSE) {
// drupal_set_message(t('Invalid json in json content'), 'warning');
// }
// $content['params'] = $json_content;
// }
// }
}
return $content;
}
/**
* Filter content run parameters, rebuild content dependency cache and export file.
*
* @param Object|array $content
* @return Object NULL on failure.
*/
public function filterParameters(&$content) {
if (!empty($content['filtered']) &&
(!$this->exportEnabled ||
($content['slug'] &&
$this->fs->hasExport($content['slug'] . '-' . $content['id'] . '.h5p')))) {
return $content['filtered'];
}
if (!(isset($content['library']) && isset($content['params']))) {
return NULL;
}
// Validate and filter against main library semantics.
$validator = new H5PContentValidator($this->h5pF, $this);
$params = (object) array(
'library' => H5PCore::libraryToString($content['library']),
'params' => json_decode($content['params'])
);
if (!$params->params) {
return NULL;
}
$validator->validateLibrary($params, (object) array('options' => array($params->library)));
// Handle addons:
$addons = $this->h5pF->loadAddons();
foreach ($addons as $addon) {
$add_to = json_decode($addon['addTo']);
if (isset($add_to->content->types)) {
foreach($add_to->content->types as $type) {
if (isset($type->text->regex) &&
$this->textAddonMatches($params->params, $type->text->regex)) {
$validator->addon($addon);
// An addon shall only be added once
break;
}
}
}
}
$params = json_encode($params->params);
// Update content dependencies.
$content['dependencies'] = $validator->getDependencies();
// Sometimes the parameters are filtered before content has been created
if ($content['id']) {
$this->h5pF->deleteLibraryUsage($content['id']);
$this->h5pF->saveLibraryUsage($content['id'], $content['dependencies']);
if (!$content['slug']) {
$content['slug'] = $this->generateContentSlug($content);
// Remove old export file
$this->fs->deleteExport($content['id'] . '.h5p');
}
if ($this->exportEnabled) {
// Recreate export file
$exporter = new H5PExport($this->h5pF, $this);
$content['filtered'] = $params;
$exporter->createExportFile($content);
}
// Cache.
$this->h5pF->updateContentFields($content['id'], array(
'filtered' => $params,
'slug' => $content['slug']
));
}
return $params;
}
/**
* Retrieve a value from a nested mixed array structure.
*
* @param Array $params Array to be looked in.
* @param String $path Supposed path to the value.
* @param String [$delimiter='.'] Property delimiter within the path.
* @return Object|NULL The object found or NULL.
*/
private function retrieveValue ($params, $path, $delimiter='.') {
$path = explode($delimiter, $path);
// Property not found
if (!isset($params[$path[0]])) {
return NULL;
}
$first = $params[$path[0]];
// End of path, done
if (sizeof($path) === 1) {
return $first;
}
// We cannot go deeper
if (!is_array($first)) {
return NULL;
}
// Regular Array
if (isset($first[0])) {
foreach($first as $number => $object) {
$found = $this->retrieveValue($object, implode($delimiter, array_slice($path, 1)));
if (isset($found)) {
return $found;
}
}
return NULL;
}
// Associative Array
return $this->retrieveValue($first, implode('.', array_slice($path, 1)));
}
/**
* Determine if params contain any match.
*
* @param {object} params - Parameters.
* @param {string} [pattern] - Regular expression to identify pattern.
* @param {boolean} [found] - Used for recursion.
* @return {boolean} True, if params matches pattern.
*/
private function textAddonMatches($params, $pattern, $found = false) {
$type = gettype($params);
if ($type === 'string') {
if (preg_match($pattern, $params) === 1) {
return true;
}
}
elseif ($type === 'array' || $type === 'object') {
foreach ($params as $value) {
$found = $this->textAddonMatches($value, $pattern, $found);
if ($found === true) {
return true;
}
}
}
return false;
}
/**
* Generate content slug
*
* @param array $content object
* @return string unique content slug
*/
private function generateContentSlug($content) {
$slug = H5PCore::slugify($content['title']);
$available = NULL;
while (!$available) {
if ($available === FALSE) {
// If not available, add number suffix.
$matches = array();
if (preg_match('/(.+-)([0-9]+)$/', $slug, $matches)) {
$slug = $matches[1] . (intval($matches[2]) + 1);
}
else {
$slug .= '-2';
}
}
$available = $this->h5pF->isContentSlugAvailable($slug);
}
return $slug;
}
/**
* Find the files required for this content to work.
*
* @param int $id for content.
* @param null $type
* @return array
*/
public function loadContentDependencies($id, $type = NULL) {
$dependencies = $this->h5pF->loadContentDependencies($id, $type);
if (isset($this->h5pD)) {
$developmentLibraries = $this->h5pD->getLibraries();
foreach ($dependencies as $key => $dependency) {
$libraryString = H5PCore::libraryToString($dependency);
if (isset($developmentLibraries[$libraryString])) {
$developmentLibraries[$libraryString]['dependencyType'] = $dependencies[$key]['dependencyType'];
$dependencies[$key] = $developmentLibraries[$libraryString];
}
}
}
return $dependencies;
}
/**
* Get all dependency assets of the given type
*
* @param array $dependency
* @param string $type
* @param array $assets
* @param string $prefix Optional. Make paths relative to another dir.
*/
private function getDependencyAssets($dependency, $type, &$assets, $prefix = '') {
// Check if dependency has any files of this type
if (empty($dependency[$type]) || $dependency[$type][0] === '') {
return;
}
// Check if we should skip CSS.
if ($type === 'preloadedCss' && (isset($dependency['dropCss']) && $dependency['dropCss'] === '1')) {
return;
}
foreach ($dependency[$type] as $file) {
$assets[] = (object) array(
'path' => $prefix . '/' . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file),
'version' => $dependency['version']
);
}
}
/**
* Combines path with cache buster / version.
*
* @param array $assets
* @return array
*/
public function getAssetsUrls($assets) {
$urls = array();
foreach ($assets as $asset) {
$url = $asset->path;
// Add URL prefix if not external
if (strpos($asset->path, '://') === FALSE) {
$url = $this->url . $url;
}
// Add version/cache buster if set
if (isset($asset->version)) {
$url .= $asset->version;
}
$urls[] = $url;
}
return $urls;
}
/**
* Return file paths for all dependencies files.
*
* @param array $dependencies
* @param string $prefix Optional. Make paths relative to another dir.
* @return array files.
*/
public function getDependenciesFiles($dependencies, $prefix = '') {
// Build files list for assets
$files = array(
'scripts' => array(),
'styles' => array()
);
$key = null;
// Avoid caching empty files
if (empty($dependencies)) {
return $files;
}
if ($this->aggregateAssets) {
// Get aggregated files for assets
$key = self::getDependenciesHash($dependencies);
$cachedAssets = $this->fs->getCachedAssets($key);
if ($cachedAssets !== NULL) {
return array_merge($files, $cachedAssets); // Using cached assets
}
}
// Using content dependencies
foreach ($dependencies as $dependency) {
if (isset($dependency['path']) === FALSE) {
$dependency['path'] = $this->getDependencyPath($dependency);
$dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']);
$dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']);
}
$dependency['version'] = "?ver={$dependency['majorVersion']}.{$dependency['minorVersion']}.{$dependency['patchVersion']}";
$this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts'], $prefix);
$this->getDependencyAssets($dependency, 'preloadedCss', $files['styles'], $prefix);
}
if ($this->aggregateAssets) {
// Aggregate and store assets
$this->fs->cacheAssets($files, $key);
// Keep track of which libraries have been cached in case they are updated
$this->h5pF->saveCachedAssets($key, $dependencies);
}
return $files;
}
/**
* Get the path to the dependency.
*
* @param array $dependency
* @return string
*/
protected function getDependencyPath(array $dependency) {
return 'libraries/' . H5PCore::libraryToString($dependency, TRUE);
}
private static function getDependenciesHash(&$dependencies) {
// Build hash of dependencies
$toHash = array();
// Use unique identifier for each library version
foreach ($dependencies as $dep) {
$toHash[] = "{$dep['machineName']}-{$dep['majorVersion']}.{$dep['minorVersion']}.{$dep['patchVersion']}";
}
// Sort in case the same dependencies comes in a different order
sort($toHash);
// Calculate hash sum
return hash('sha1', implode('', $toHash));
}
/**
* Load library semantics.
*
* @param $name
* @param $majorVersion
* @param $minorVersion
* @return string
*/
public function loadLibrarySemantics($name, $majorVersion, $minorVersion) {
$semantics = NULL;
if (isset($this->h5pD)) {
// Try to load from dev lib
$semantics = $this->h5pD->getSemantics($name, $majorVersion, $minorVersion);
}
if ($semantics === NULL) {
// Try to load from DB.
$semantics = $this->h5pF->loadLibrarySemantics($name, $majorVersion, $minorVersion);
}
if ($semantics !== NULL) {
$semantics = json_decode($semantics);
$this->h5pF->alterLibrarySemantics($semantics, $name, $majorVersion, $minorVersion);
}
return $semantics;
}
/**
* Load library.
*
* @param $name
* @param $majorVersion
* @param $minorVersion
* @return array or null.
*/
public function loadLibrary($name, $majorVersion, $minorVersion) {
$library = NULL;
if (isset($this->h5pD)) {
// Try to load from dev
$library = $this->h5pD->getLibrary($name, $majorVersion, $minorVersion);
if ($library !== NULL) {
$library['semantics'] = $this->h5pD->getSemantics($name, $majorVersion, $minorVersion);
}
}
if ($library === NULL) {
// Try to load from DB.
$library = $this->h5pF->loadLibrary($name, $majorVersion, $minorVersion);
}
return $library;
}
/**
* Deletes a library
*
* @param stdClass $libraryId
*/
public function deleteLibrary($libraryId) {
$this->h5pF->deleteLibrary($libraryId);
}
/**
* Recursive. Goes through the dependency tree for the given library and
* adds all the dependencies to the given array in a flat format.
*