diff --git a/composer.json b/composer.json
index fac6c53..9b1cc88 100644
--- a/composer.json
+++ b/composer.json
@@ -28,7 +28,8 @@
"h5p-development.class.php",
"h5p-file-storage.interface.php",
"h5p-default-storage.class.php",
- "h5p-event-base.class.php"
+ "h5p-event-base.class.php",
+ "h5p-metadata.class.php"
]
}
}
diff --git a/fonts/h5p-core-18.eot b/fonts/h5p-core-18.eot
deleted file mode 100755
index eba9d6c..0000000
Binary files a/fonts/h5p-core-18.eot and /dev/null differ
diff --git a/fonts/h5p-core-18.svg b/fonts/h5p-core-18.svg
deleted file mode 100755
index 13da36e..0000000
--- a/fonts/h5p-core-18.svg
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/fonts/h5p-core-18.ttf b/fonts/h5p-core-18.ttf
deleted file mode 100755
index 37f845e..0000000
Binary files a/fonts/h5p-core-18.ttf and /dev/null differ
diff --git a/fonts/h5p-core-18.woff b/fonts/h5p-core-18.woff
deleted file mode 100755
index 8450f3d..0000000
Binary files a/fonts/h5p-core-18.woff and /dev/null differ
diff --git a/fonts/h5p-core-19.eot b/fonts/h5p-core-19.eot
new file mode 100644
index 0000000..2348e29
Binary files /dev/null and b/fonts/h5p-core-19.eot differ
diff --git a/fonts/h5p-core-19.svg b/fonts/h5p-core-19.svg
new file mode 100644
index 0000000..a808aa6
--- /dev/null
+++ b/fonts/h5p-core-19.svg
@@ -0,0 +1,54 @@
+
+
+
\ No newline at end of file
diff --git a/fonts/h5p-core-19.ttf b/fonts/h5p-core-19.ttf
new file mode 100644
index 0000000..729aea4
Binary files /dev/null and b/fonts/h5p-core-19.ttf differ
diff --git a/fonts/h5p-core-19.woff b/fonts/h5p-core-19.woff
new file mode 100644
index 0000000..be9ead2
Binary files /dev/null and b/fonts/h5p-core-19.woff differ
diff --git a/h5p-default-storage.class.php b/h5p-default-storage.class.php
index 38af307..26e3e01 100644
--- a/h5p-default-storage.class.php
+++ b/h5p-default-storage.class.php
@@ -451,6 +451,19 @@ class H5PDefaultStorage implements \H5PFileStorage {
return self::dirReady($this->path);
}
+ /**
+ * Check if the file presave.js exists in the root of the library
+ *
+ * @param string $libraryFolder
+ * @param string $developmentPath
+ * @return bool
+ */
+ public function hasPresave($libraryFolder, $developmentPath = null) {
+ $path = is_null($developmentPath) ? 'libraries' . DIRECTORY_SEPARATOR . $libraryFolder : $developmentPath;
+ $filePath = realpath($this->path . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . 'presave.js');
+ return file_exists($filePath);
+ }
+
/**
* Recursive function for copying directories.
*
diff --git a/h5p-development.class.php b/h5p-development.class.php
index 6c891f9..a60262a 100644
--- a/h5p-development.class.php
+++ b/h5p-development.class.php
@@ -84,10 +84,19 @@ class H5PDevelopment {
// TODO: Validate props? Not really needed, is it? this is a dev site.
- // Save/update library.
$library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']);
+
+ // Convert metadataSettings values to boolean & json_encode it before saving
+ $library['metadataSettings'] = isset($library['metadataSettings']) ?
+ H5PMetadata::boolifyAndEncodeSettings($library['metadataSettings']) :
+ NULL;
+
+ // Save/update library.
$this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE);
+ // Need to decode it again, since it is served from here.
+ $library['metadataSettings'] = json_decode($library['metadataSettings']);
+
$library['path'] = 'development/' . $contents[$i];
$this->libraries[H5PDevelopment::libraryToString($library['machineName'], $library['majorVersion'], $library['minorVersion'])] = $library;
}
diff --git a/h5p-file-storage.interface.php b/h5p-file-storage.interface.php
index 394210b..4dbdbc6 100644
--- a/h5p-file-storage.interface.php
+++ b/h5p-file-storage.interface.php
@@ -190,4 +190,13 @@ interface H5PFileStorage {
* @return bool True if server has the proper write access
*/
public function hasWriteAccess();
+
+ /**
+ * Check if the library has a presave.js in the root folder
+ *
+ * @param string $libraryName
+ * @param string $developmentPath
+ * @return bool
+ */
+ public function hasPresave($libraryName, $developmentPath = null);
}
diff --git a/h5p-metadata.class.php b/h5p-metadata.class.php
new file mode 100644
index 0000000..3a504e3
--- /dev/null
+++ b/h5p-metadata.class.php
@@ -0,0 +1,137 @@
+ array(
+ 'type' => 'text',
+ 'maxLength' => 255
+ ),
+ 'authors' => array(
+ 'type' => 'json'
+ ),
+ 'changes' => array(
+ 'type' => 'json'
+ ),
+ 'source' => array(
+ 'type' => 'text',
+ 'maxLength' => 255
+ ),
+ 'license' => array(
+ 'type' => 'text',
+ 'maxLength' => 32
+ ),
+ 'licenseVersion' => array(
+ 'type' => 'text',
+ 'maxLength' => 10
+ ),
+ 'licenseExtras' => array(
+ 'type' => 'text',
+ 'maxLength' => 5000
+ ),
+ 'authorComments' => array(
+ 'type' => 'text',
+ 'maxLength' => 5000
+ ),
+ 'yearFrom' => array(
+ 'type' => 'int'
+ ),
+ 'yearTo' => array(
+ 'type' => 'int'
+ )
+ );
+
+ /**
+ * JSON encode metadata
+ *
+ * @param object $content
+ * @return string
+ */
+ public static function toJSON($content) {
+ // Note: deliberatly creating JSON string "manually" to improve performance
+ return
+ '{"title":' . (isset($content->title) ? json_encode($content->title) : 'null') .
+ ',"authors":' . (isset($content->authors) ? $content->authors : 'null') .
+ ',"source":' . (isset($content->source) ? '"' . $content->source . '"' : 'null') .
+ ',"license":' . (isset($content->license) ? '"' . $content->license . '"' : 'null') .
+ ',"licenseVersion":' . (isset($content->license_version) ? '"' . $content->license_version . '"' : 'null') .
+ ',"licenseExtras":' . (isset($content->license_extras) ? json_encode($content->license_extras) : 'null') .
+ ',"yearFrom":' . (isset($content->year_from) ? $content->year_from : 'null') .
+ ',"yearTo":' . (isset($content->year_to) ? $content->year_to : 'null') .
+ ',"changes":' . (isset($content->changes) ? $content->changes : 'null') .
+ ',"authorComments":' . (isset($content->author_comments) ? json_encode($content->author_comments) : 'null') . '}';
+ }
+
+
+ /**
+ * Make the metadata into an associative array keyed by the property names
+ * @param mixed $metadata Array or object containing metadata
+ * @param bool $include_title
+ * @param array $types
+ * @return array
+ */
+ public static function toDBArray($metadata, $include_title = true, &$types = array()) {
+ $fields = array();
+
+ if (!is_array($metadata)) {
+ $metadata = (array) $metadata;
+ }
+
+ foreach (self::$fields as $key => $config) {
+
+ if ($key === 'title' && !$include_title) {
+ continue;
+ }
+
+ if (array_key_exists($key, $metadata)) {
+ $value = $metadata[$key];
+ $db_field_name = strtolower(preg_replace('/(? $config['maxLength']) {
+ $value = mb_substr($value, 0, $config['maxLength']);
+ }
+ $types[] = '%s';
+ break;
+
+ case 'int':
+ $value = ($value !== null) ? intval($value): null;
+ $types[] = '%d';
+ break;
+
+ case 'json':
+ $value = ($value !== null) ? json_encode($value) : null;
+ $types[] = '%s';
+ break;
+ }
+
+ $fields[$db_field_name] = $value;
+ }
+ }
+
+ return $fields;
+ }
+
+ /**
+ * The metadataSettings field in libraryJson uses 1 for true and 0 for false.
+ * Here we are converting these to booleans, and also doing JSON encoding.
+ * This is invoked before the library data is beeing inserted/updated to DB.
+ *
+ * @param array $metadataSettings
+ * @return string
+ */
+ public static function boolifyAndEncodeSettings($metadataSettings) {
+ // Convert metadataSettings values to boolean
+ if (isset($metadataSettings['disable'])) {
+ $metadataSettings['disable'] = $metadataSettings['disable'] === 1;
+ }
+ if (isset($metadataSettings['disableExtraTitleField'])) {
+ $metadataSettings['disableExtraTitleField'] = $metadataSettings['disableExtraTitleField'] === 1;
+ }
+
+ return json_encode($metadataSettings);
+ }
+}
diff --git a/h5p.classes.php b/h5p.classes.php
index 2efebbd..b0099bc 100644
--- a/h5p.classes.php
+++ b/h5p.classes.php
@@ -101,6 +101,21 @@ interface H5PFrameworkInterface {
*/
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
*
@@ -195,6 +210,9 @@ interface H5PFrameworkInterface {
* - 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:
@@ -620,16 +638,34 @@ class H5PValidator {
private $h5pOptional = array(
'contentType' => '/^.{1,255}$/',
- '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)$/',
'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}$/',
+ '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}$/'
+ ),
+ 'authorComments' => '/^.{1,5000}$/',
'w' => '/^[0-9]{1,4}$/',
'h' => '/^[0-9]{1,4}$/',
+ // deprecated
'metaKeywords' => '/^.{1,}$/',
+ // deprecated
'metaDescription' => '/^.{1,}$/',
);
@@ -647,6 +683,10 @@ class H5PValidator {
'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}$/',
@@ -1405,7 +1445,11 @@ class H5PStorage {
// Indicate that the dependencies of this library should be saved.
$library['saveDependencies'] = TRUE;
- // Save library meta data
+ // 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
@@ -1554,6 +1598,16 @@ Class H5PExport {
$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('<', '<', str_replace('>', '>', str_replace(''', "'", str_replace('"', '"', $value))));
+ }
+
/**
* Return path to h5p package.
*
@@ -1584,14 +1638,29 @@ Class H5PExport {
// Make embedType into an array
$embedTypes = explode(', ', $content['embedType']);
- // Build h5p.json
+ // Build h5p.json, the en-/de-coding will ensure proper escaping
$h5pJson = array (
- 'title' => $content['title'],
+ 'title' => self::revertH5PEditorTextEscape($content['title']),
'language' => (isset($content['language']) && strlen(trim($content['language'])) !== 0) ? $content['language'] : 'und',
'mainLibrary' => $content['library']['name'],
- 'embedTypes' => $embedTypes,
+ 'embedTypes' => $embedTypes
);
+ foreach(array('authors', 'source', 'license', 'licenseVersion', 'licenseExtras' ,'yearFrom', 'yearTo', 'changes', 'authorComments') 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'];
@@ -1609,7 +1678,7 @@ Class H5PExport {
$library['minorVersion']
);
- if ($isDevLibrary !== NULL) {
+ if ($isDevLibrary !== NULL && isset($library['path'])) {
$exportFolder = "/" . $library['path'];
}
}
@@ -1770,10 +1839,10 @@ abstract class H5PHubEndpoints {
* Functions and storage shared by the other H5P classes
*/
class H5PCore {
-
+
public static $coreApi = array(
'majorVersion' => 1,
- 'minorVersion' => 16
+ 'minorVersion' => 19
);
public static $styles = array(
'styles/h5p.css',
@@ -1886,6 +1955,10 @@ class H5PCore {
$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'],
@@ -1927,6 +2000,10 @@ class H5PCore {
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(
@@ -1938,6 +2015,25 @@ class H5PCore {
}
$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.
@@ -1970,6 +2066,75 @@ class H5PCore {
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
*
@@ -2848,7 +3013,7 @@ class H5PCore {
*/
private static function printJson($data, $status_code = NULL) {
header('Cache-Control: no-cache');
- header('Content-type: application/json; charset=utf-8');
+ header('Content-Type: application/json; charset=utf-8');
print json_encode($data);
}
@@ -3156,7 +3321,10 @@ class H5PCore {
'licensePD' => $this->h5pF->t('Public Domain'),
'licenseCC010' => $this->h5pF->t('CC0 1.0 Universal (CC0 1.0) Public Domain Dedication'),
'licensePDM' => $this->h5pF->t('Public Domain Mark'),
- 'licenseC' => $this->h5pF->t('Copyright')
+ 'licenseC' => $this->h5pF->t('Copyright'),
+ 'contentType' => $this->h5pF->t('Content Type'),
+ 'licenseExtras' => $this->h5pF->t('License Extras'),
+ 'changes' => $this->h5pF->t('Changelog'),
);
}
}
@@ -3204,6 +3372,19 @@ class H5PContentValidator {
$this->dependencies = array();
}
+ /**
+ * Add Addon library.
+ */
+ public function addon($library) {
+ $depKey = 'preloaded-' . $library['machineName'];
+ $this->dependencies[$depKey] = array(
+ 'library' => $library,
+ 'type' => 'preloaded'
+ );
+ $this->nextWeight = $this->h5pC->findLibraryDependencies($this->dependencies, $library, $this->nextWeight);
+ $this->dependencies[$depKey]['weight'] = $this->nextWeight++;
+ }
+
/**
* Get the flat dependency tree.
*
@@ -3213,6 +3394,24 @@ class H5PContentValidator {
return $this->dependencies;
}
+ /**
+ * Validate metadata
+ *
+ * @param array $metadata
+ * @return array Validated & filtered
+ */
+ public function validateMetadata($metadata) {
+ $semantics = $this->getMetadataSemantics();
+
+ $group = (object)$metadata;
+ $this->validateGroup($group, (object) array(
+ 'type' => 'group',
+ 'fields' => $semantics,
+ ), FALSE);
+
+ return (array)$group;
+ }
+
/**
* Validate given text value against text semantics.
* @param $text
@@ -3401,8 +3600,17 @@ class H5PContentValidator {
// We have a strict set of options to choose from.
$strict = TRUE;
$options = array();
+
foreach ($semantics->options as $option) {
- $options[$option->value] = TRUE;
+ // Support optgroup - just flatten options into one
+ if (isset($option->type) && $option->type === 'optgroup') {
+ foreach ($option->options as $suboption) {
+ $options[$suboption->value] = TRUE;
+ }
+ }
+ elseif (isset($option->value)) {
+ $options[$option->value] = TRUE;
+ }
}
}
@@ -3667,12 +3875,24 @@ class H5PContentValidator {
$value = NULL;
return;
}
- if (!in_array($value->library, $semantics->options)) {
+
+ // Check for array of objects or array of strings
+ if (is_object($semantics->options[0])) {
+ $getLibraryNames = function ($item) {
+ return $item->name;
+ };
+ $libraryNames = array_map($getLibraryNames, $semantics->options);
+ }
+ else {
+ $libraryNames = $semantics->options;
+ }
+
+ if (!in_array($value->library, $libraryNames)) {
$message = NULL;
// Create an understandable error message:
$machineNameArray = explode(' ', $value->library);
$machineName = $machineNameArray[0];
- foreach ($semantics->options as $semanticsLibrary) {
+ foreach ($libraryNames as $semanticsLibrary) {
$semanticsMachineNameArray = explode(' ', $semanticsLibrary);
$semanticsMachineName = $semanticsMachineNameArray[0];
if ($machineName === $semanticsMachineName) {
@@ -3708,14 +3928,22 @@ class H5PContentValidator {
$library = $this->libraries[$value->library];
}
+ // Validate parameters
$this->validateGroup($value->params, (object) array(
'type' => 'group',
'fields' => $library['semantics'],
), FALSE);
- $validKeys = array('library', 'params', 'subContentId');
+
+ // Validate subcontent's metadata
+ if (isset($value->metadata)) {
+ $value->metadata = $this->validateMetadata($value->metadata);
+ }
+
+ $validKeys = array('library', 'params', 'subContentId', 'metadata');
if (isset($semantics->extraAttributes)) {
$validKeys = array_merge($validKeys, $semantics->extraAttributes);
}
+
$this->filterParams($value, $validKeys);
if (isset($value->subContentId) && ! preg_match('/^\{?[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}?$/', $value->subContentId)) {
unset($value->subContentId);
@@ -4082,6 +4310,240 @@ class H5PContentValidator {
return $uri;
}
+ public function getMetadataSemantics() {
+ static $semantics;
+
+ $cc_versions = array(
+ (object) array(
+ 'value' => '4.0',
+ 'label' => $this->h5pF->t('4.0 International')
+ ),
+ (object) array(
+ 'value' => '3.0',
+ 'label' => $this->h5pF->t('3.0 Unported')
+ ),
+ (object) array(
+ 'value' => '2.5',
+ 'label' => $this->h5pF->t('2.5 Generic')
+ ),
+ (object) array(
+ 'value' => '2.0',
+ 'label' => $this->h5pF->t('2.0 Generic')
+ ),
+ (object) array(
+ 'value' => '1.0',
+ 'label' => $this->h5pF->t('1.0 Generic')
+ )
+ );
+
+ $semantics = array(
+ (object) array(
+ 'name' => 'title',
+ 'type' => 'text',
+ 'label' => $this->h5pF->t('Title'),
+ 'placeholder' => 'La Gioconda'
+ ),
+ (object) array(
+ 'name' => 'license',
+ 'type' => 'select',
+ 'label' => $this->h5pF->t('License'),
+ 'default' => 'U',
+ 'options' => array(
+ (object) array(
+ 'value' => 'U',
+ 'label' => $this->h5pF->t('Undisclosed')
+ ),
+ (object) array(
+ 'type' => 'optgroup',
+ 'label' => $this->h5pF->t('Creative Commons'),
+ 'options' => [
+ (object) array(
+ 'value' => 'CC BY',
+ 'label' => $this->h5pF->t('Attribution (CC BY)'),
+ 'versions' => $cc_versions
+ ),
+ (object) array(
+ 'value' => 'CC BY-SA',
+ 'label' => $this->h5pF->t('Attribution-ShareAlike (CC BY-SA)'),
+ 'versions' => $cc_versions
+ ),
+ (object) array(
+ 'value' => 'CC BY-ND',
+ 'label' => $this->h5pF->t('Attribution-NoDerivs (CC BY-ND)'),
+ 'versions' => $cc_versions
+ ),
+ (object) array(
+ 'value' => 'CC BY-NC',
+ 'label' => $this->h5pF->t('Attribution-NonCommercial (CC BY-NC)'),
+ 'versions' => $cc_versions
+ ),
+ (object) array(
+ 'value' => 'CC BY-NC-SA',
+ 'label' => $this->h5pF->t('Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)'),
+ 'versions' => $cc_versions
+ ),
+ (object) array(
+ 'value' => 'CC BY-NC-ND',
+ 'label' => $this->h5pF->t('Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)'),
+ 'versions' => $cc_versions
+ ),
+ (object) array(
+ 'value' => 'CC0 1.0',
+ 'label' => $this->h5pF->t('Public Domain Dedication (CC0)')
+ ),
+ (object) array(
+ 'value' => 'CC PDM',
+ 'label' => $this->h5pF->t('Public Domain Mark (PDM)')
+ ),
+ ]
+ ),
+ (object) array(
+ 'value' => 'GNU GPL',
+ 'label' => $this->h5pF->t('General Public License v3')
+ ),
+ (object) array(
+ 'value' => 'PD',
+ 'label' => $this->h5pF->t('Public Domain')
+ ),
+ (object) array(
+ 'value' => 'ODC PDDL',
+ 'label' => $this->h5pF->t('Public Domain Dedication and Licence')
+ ),
+ (object) array(
+ 'value' => 'C',
+ 'label' => $this->h5pF->t('Copyright')
+ )
+ )
+ ),
+ (object) array(
+ 'name' => 'licenseVersion',
+ 'type' => 'select',
+ 'label' => $this->h5pF->t('License Version'),
+ 'options' => $cc_versions,
+ 'optional' => TRUE
+ ),
+ (object) array(
+ 'name' => 'yearFrom',
+ 'type' => 'number',
+ 'label' => $this->h5pF->t('Years (from)'),
+ 'placeholder' => '1991',
+ 'min' => '-9999',
+ 'max' => '9999',
+ 'optional' => TRUE
+ ),
+ (object) array(
+ 'name' => 'yearTo',
+ 'type' => 'number',
+ 'label' => $this->h5pF->t('Years (to)'),
+ 'placeholder' => '1992',
+ 'min' => '-9999',
+ 'max' => '9999',
+ 'optional' => TRUE
+ ),
+ (object) array(
+ 'name' => 'source',
+ 'type' => 'text',
+ 'label' => $this->h5pF->t('Source'),
+ 'placeholder' => 'https://',
+ 'optional' => TRUE
+ ),
+ (object) array(
+ 'name' => 'authors',
+ 'type' => 'list',
+ 'field' => (object) array (
+ 'name' => 'author',
+ 'type' => 'group',
+ 'fields'=> array(
+ (object) array(
+ 'label' => $this->h5pF->t("Author's name"),
+ 'name' => 'name',
+ 'optional' => TRUE,
+ 'type' => 'text'
+ ),
+ (object) array(
+ 'name' => 'role',
+ 'type' => 'select',
+ 'label' => $this->h5pF->t("Author's role"),
+ 'default' => 'Author',
+ 'options' => array(
+ (object) array(
+ 'value' => 'Author',
+ 'label' => $this->h5pF->t('Author')
+ ),
+ (object) array(
+ 'value' => 'Editor',
+ 'label' => $this->h5pF->t('Editor')
+ ),
+ (object) array(
+ 'value' => 'Licensee',
+ 'label' => $this->h5pF->t('Licensee')
+ ),
+ (object) array(
+ 'value' => 'Originator',
+ 'label' => $this->h5pF->t('Originator')
+ )
+ )
+ )
+ )
+ )
+ ),
+ (object) array(
+ 'name' => 'licenseExtras',
+ 'type' => 'text',
+ 'widget' => 'textarea',
+ 'label' => $this->h5pF->t('License Extras'),
+ 'optional' => TRUE,
+ 'description' => $this->h5pF->t('Any additional information about the license')
+ ),
+ (object) array(
+ 'name' => 'changes',
+ 'type' => 'list',
+ 'field' => (object) array(
+ 'name' => 'change',
+ 'type' => 'group',
+ 'label' => $this->h5pF->t('Changelog'),
+ 'fields' => array(
+ (object) array(
+ 'name' => 'date',
+ 'type' => 'text',
+ 'label' => $this->h5pF->t('Date'),
+ 'optional' => TRUE
+ ),
+ (object) array(
+ 'name' => 'author',
+ 'type' => 'text',
+ 'label' => $this->h5pF->t('Changed by'),
+ 'optional' => TRUE
+ ),
+ (object) array(
+ 'name' => 'log',
+ 'type' => 'text',
+ 'widget' => 'textarea',
+ 'label' => $this->h5pF->t('Description of change'),
+ 'placeholder' => $this->h5pF->t('Photo cropped, text changed, etc.'),
+ 'optional' => TRUE
+ )
+ )
+ )
+ ),
+ (object) array (
+ 'name' => 'authorComments',
+ 'type' => 'text',
+ 'widget' => 'textarea',
+ 'label' => $this->h5pF->t('Author comments'),
+ 'description' => $this->h5pF->t('Comments for the editor of the content (This text will not be published as a part of copyright info)'),
+ 'optional' => TRUE
+ ),
+ (object) array(
+ 'name' => 'contentType',
+ 'type' => 'text',
+ 'widget' => 'none'
+ ),
+ );
+
+ return $semantics;
+ }
+
public function getCopyrightSemantics() {
static $semantics;
diff --git a/js/h5p-confirmation-dialog.js b/js/h5p-confirmation-dialog.js
index a6dd998..fa623c6 100644
--- a/js/h5p-confirmation-dialog.js
+++ b/js/h5p-confirmation-dialog.js
@@ -351,7 +351,7 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
*
* @param {number|null} minHeight
*/
- this.setViewPortMinimumHeight = function(minHeight) {
+ this.setViewPortMinimumHeight = function (minHeight) {
var container = document.querySelector('.h5p-container') || document.body;
container.style.minHeight = (typeof minHeight === 'number') ? (minHeight + 'px') : minHeight;
};
diff --git a/js/h5p-content-type.js b/js/h5p-content-type.js
index 8be8fcd..47c4d21 100644
--- a/js/h5p-content-type.js
+++ b/js/h5p-content-type.js
@@ -11,7 +11,7 @@
* @class
* @augments H5P.EventDispatcher
*/
-H5P.ContentType = function (isRootLibrary, library) {
+H5P.ContentType = function (isRootLibrary) {
function ContentType() {}
diff --git a/js/h5p-content-upgrade-process.js b/js/h5p-content-upgrade-process.js
index e54dbb7..e683902 100644
--- a/js/h5p-content-upgrade-process.js
+++ b/js/h5p-content-upgrade-process.js
@@ -25,19 +25,27 @@ H5P.ContentUpgradeProcess = (function (Version) {
}
self.loadLibrary = loadLibrary;
- self.upgrade(name, oldVersion, newVersion, params, function (err, result) {
+ self.upgrade(name, oldVersion, newVersion, params.params, params.metadata, function (err, upgradedParams, upgradedMetadata) {
if (err) {
return done(err);
}
- done(null, JSON.stringify(params));
+ done(null, JSON.stringify({params: upgradedParams, metadata: upgradedMetadata}));
});
}
/**
+ * Run content upgrade.
*
+ * @public
+ * @param {string} name
+ * @param {Version} oldVersion
+ * @param {Version} newVersion
+ * @param {Object} params
+ * @param {Object} metadata
+ * @param {Function} done
*/
- ContentUpgradeProcess.prototype.upgrade = function (name, oldVersion, newVersion, params, done) {
+ ContentUpgradeProcess.prototype.upgrade = function (name, oldVersion, newVersion, params, metadata, done) {
var self = this;
// Load library details and upgrade routines
@@ -47,7 +55,7 @@ H5P.ContentUpgradeProcess = (function (Version) {
}
// Run upgrade routines on params
- self.processParams(library, oldVersion, newVersion, params, function (err, params) {
+ self.processParams(library, oldVersion, newVersion, params, metadata, function (err, params, metadata) {
if (err) {
return done(err);
}
@@ -61,7 +69,7 @@ H5P.ContentUpgradeProcess = (function (Version) {
next(err);
});
}, function (err) {
- done(err, params);
+ done(err, params, metadata);
});
});
});
@@ -77,7 +85,7 @@ H5P.ContentUpgradeProcess = (function (Version) {
* @param {Object} params
* @param {Function} next
*/
- ContentUpgradeProcess.prototype.processParams = function (library, oldVersion, newVersion, params, next) {
+ ContentUpgradeProcess.prototype.processParams = function (library, oldVersion, newVersion, params, metadata, next) {
if (H5PUpgrades[library.name] === undefined) {
if (library.upgradesScript) {
// Upgrades script should be loaded so the upgrades should be here.
@@ -110,16 +118,19 @@ H5P.ContentUpgradeProcess = (function (Version) {
var unnecessaryWrapper = (upgrade.contentUpgrade !== undefined ? upgrade.contentUpgrade : upgrade);
try {
- unnecessaryWrapper(params, function (err, upgradedParams) {
+ unnecessaryWrapper(params, function (err, upgradedParams, upgradedExtras) {
params = upgradedParams;
+ if (upgradedExtras && upgradedExtras.metadata) { // Optional
+ metadata = upgradedExtras.metadata;
+ }
nextMinor(err);
- });
+ }, {metadata: metadata});
}
catch (err) {
- if (console && console.log) {
- console.log("Error", err.stack);
- console.log("Error", err.name);
- console.log("Error", err.message);
+ if (console && console.error) {
+ console.error("Error", err.stack);
+ console.error("Error", err.name);
+ console.error("Error", err.message);
}
next(err);
}
@@ -127,7 +138,7 @@ H5P.ContentUpgradeProcess = (function (Version) {
}, nextMajor);
}
}, function (err) {
- next(err, params);
+ next(err, params, metadata);
});
};
@@ -155,7 +166,7 @@ H5P.ContentUpgradeProcess = (function (Version) {
// Look for available upgrades
var usedLib = params.library.split(' ', 2);
for (var i = 0; i < field.options.length; i++) {
- var availableLib = field.options[i].split(' ', 2);
+ var availableLib = (typeof field.options[i] === 'string') ? field.options[i].split(' ', 2) : field.options[i].name.split(' ', 2);
if (availableLib[0] === usedLib[0]) {
if (availableLib[1] === usedLib[1]) {
return done(); // Same version
@@ -169,10 +180,13 @@ H5P.ContentUpgradeProcess = (function (Version) {
}
// A newer version is available, upgrade params
- return self.upgrade(availableLib[0], usedVer, availableVer, params.params, function (err, upgraded) {
+ return self.upgrade(availableLib[0], usedVer, availableVer, params.params, params.metadata, function (err, upgradedParams, upgradedMetadata) {
if (!err) {
params.library = availableLib[0] + ' ' + availableVer.major + '.' + availableVer.minor;
- params.params = upgraded;
+ params.params = upgradedParams;
+ if (upgradedMetadata) {
+ params.metadata = upgradedMetadata;
+ }
}
done(err, params);
});
diff --git a/js/h5p-content-upgrade-worker.js b/js/h5p-content-upgrade-worker.js
index 26ad038..3507a35 100644
--- a/js/h5p-content-upgrade-worker.js
+++ b/js/h5p-content-upgrade-worker.js
@@ -1,3 +1,4 @@
+/* global importScripts */
var H5P = H5P || {};
importScripts('h5p-version.js', 'h5p-content-upgrade-process.js');
diff --git a/js/h5p-content-upgrade.js b/js/h5p-content-upgrade.js
index 9c1e4ce..bb5244a 100644
--- a/js/h5p-content-upgrade.js
+++ b/js/h5p-content-upgrade.js
@@ -1,4 +1,4 @@
-/*jshint -W083 */
+/* global H5PAdminIntegration H5PUtils */
(function ($, Version) {
var info, $container, librariesCache = {}, scriptsCache = {};
diff --git a/js/h5p-data-view.js b/js/h5p-data-view.js
index 89080e0..2f708f8 100644
--- a/js/h5p-data-view.js
+++ b/js/h5p-data-view.js
@@ -1,3 +1,4 @@
+/* global H5PUtils */
var H5PDataView = (function ($) {
/**
@@ -198,7 +199,6 @@ var H5PDataView = (function ($) {
* @param number col ID of column
*/
H5PDataView.prototype.createFacets = function (input, col) {
- var self = this;
var facets = '';
if (input instanceof Array) {
diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js
index a6707b4..2027cf7 100644
--- a/js/h5p-event-dispatcher.js
+++ b/js/h5p-event-dispatcher.js
@@ -10,7 +10,7 @@ var H5P = window.H5P = window.H5P || {};
* @param {boolean} [extras.bubbles]
* @param {boolean} [extras.external]
*/
-H5P.Event = function(type, data, extras) {
+H5P.Event = function (type, data, extras) {
this.type = type;
this.data = data;
var bubbles = false;
@@ -34,7 +34,7 @@ H5P.Event = function(type, data, extras) {
/**
* Prevent this event from bubbling up to parent
*/
- this.preventBubbling = function() {
+ this.preventBubbling = function () {
bubbles = false;
};
@@ -44,7 +44,7 @@ H5P.Event = function(type, data, extras) {
* @returns {boolean}
* true if bubbling false otherwise
*/
- this.getBubbles = function() {
+ this.getBubbles = function () {
return bubbles;
};
@@ -54,7 +54,7 @@ H5P.Event = function(type, data, extras) {
* @returns {boolean}
* true if external and not already scheduled, otherwise false
*/
- this.scheduleForExternal = function() {
+ this.scheduleForExternal = function () {
if (external && !scheduledForExternal) {
scheduledForExternal = true;
return true;
diff --git a/js/h5p-library-details.js b/js/h5p-library-details.js
index 77d366b..b5ee012 100644
--- a/js/h5p-library-details.js
+++ b/js/h5p-library-details.js
@@ -1,4 +1,5 @@
-var H5PLibraryDetails= H5PLibraryDetails || {};
+/* global H5PAdminIntegration H5PUtils */
+var H5PLibraryDetails = H5PLibraryDetails || {};
(function ($) {
@@ -68,7 +69,7 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
*/
H5PLibraryDetails.createContentTable = function () {
// Remove it if it exists:
- if(H5PLibraryDetails.$contentTable) {
+ if (H5PLibraryDetails.$contentTable) {
H5PLibraryDetails.$contentTable.remove();
}
@@ -77,10 +78,10 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
var i = (H5PLibraryDetails.currentPage*H5PLibraryDetails.PAGER_SIZE);
var lastIndex = (i+H5PLibraryDetails.PAGER_SIZE);
- if(lastIndex > H5PLibraryDetails.currentContent.length) {
+ if (lastIndex > H5PLibraryDetails.currentContent.length) {
lastIndex = H5PLibraryDetails.currentContent.length;
}
- for(; i' + content.title + '']));
}
@@ -97,7 +98,7 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
H5PLibraryDetails.$next = $('');
H5PLibraryDetails.$previous.on('click', function () {
- if(H5PLibraryDetails.$previous.hasClass('disabled')) {
+ if (H5PLibraryDetails.$previous.hasClass('disabled')) {
return;
}
@@ -107,7 +108,7 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
});
H5PLibraryDetails.$next.on('click', function () {
- if(H5PLibraryDetails.$next.hasClass('disabled')) {
+ if (H5PLibraryDetails.$next.hasClass('disabled')) {
return;
}
@@ -127,7 +128,7 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
H5PLibraryDetails.$pagerInfo.hide();
// User has updated the pageNumber
- var pageNumerUpdated = function() {
+ var pageNumerUpdated = function () {
var newPageNum = $gotoInput.val()-1;
var intRegex = /^\d+$/;
@@ -135,7 +136,7 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
H5PLibraryDetails.$pagerInfo.css({display: 'inline-block'});
// Check if input value is valid, and that it has actually changed
- if(!(intRegex.test(newPageNum) && newPageNum >= 0 && newPageNum < H5PLibraryDetails.getNumPages() && newPageNum != H5PLibraryDetails.currentPage)) {
+ if (!(intRegex.test(newPageNum) && newPageNum >= 0 && newPageNum < H5PLibraryDetails.getNumPages() && newPageNum != H5PLibraryDetails.currentPage)) {
return;
}
@@ -185,7 +186,7 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
H5PLibraryDetails.updatePager = function () {
H5PLibraryDetails.$pagerInfo.css({display: 'inline-block'});
- if(H5PLibraryDetails.getNumPages() > 0) {
+ if (H5PLibraryDetails.getNumPages() > 0) {
var message = H5PUtils.translateReplace(H5PLibraryDetails.library.translations.pageXOfY, {
'$x': (H5PLibraryDetails.currentPage+1),
'$y': H5PLibraryDetails.getNumPages()
@@ -211,7 +212,7 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
var searchString = $('.h5p-content-search > input').val();
// If search string same as previous, just do nothing
- if(H5PLibraryDetails.currentFilter === searchString) {
+ if (H5PLibraryDetails.currentFilter === searchString) {
return;
}
@@ -219,7 +220,7 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
// If empty search, use the complete list
H5PLibraryDetails.currentContent = H5PLibraryDetails.library.content;
}
- else if(H5PLibraryDetails.filterCache[searchString]) {
+ else if (H5PLibraryDetails.filterCache[searchString]) {
// If search is cached, no need to filter
H5PLibraryDetails.currentContent = H5PLibraryDetails.filterCache[searchString];
}
@@ -227,10 +228,10 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
var listToFilter = H5PLibraryDetails.library.content;
// Check if we can filter the already filtered results (for performance)
- if(searchString.length > 1 && H5PLibraryDetails.currentFilter === searchString.substr(0, H5PLibraryDetails.currentFilter.length)) {
+ if (searchString.length > 1 && H5PLibraryDetails.currentFilter === searchString.substr(0, H5PLibraryDetails.currentFilter.length)) {
listToFilter = H5PLibraryDetails.currentContent;
}
- H5PLibraryDetails.currentContent = $.grep(listToFilter, function(content) {
+ H5PLibraryDetails.currentContent = $.grep(listToFilter, function (content) {
return content.title && content.title.match(new RegExp(searchString, 'i'));
});
}
@@ -256,7 +257,7 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
$('input', H5PLibraryDetails.$search).on('change keypress paste input', function () {
// Here we start the filtering
// We wait at least 500 ms after last input to perform search
- if(inputTimer) {
+ if (inputTimer) {
clearTimeout(inputTimer);
}
diff --git a/js/h5p-library-list.js b/js/h5p-library-list.js
index 1ee1bc8..344b736 100644
--- a/js/h5p-library-list.js
+++ b/js/h5p-library-list.js
@@ -1,4 +1,4 @@
-/*jshint multistr: true */
+/* global H5PAdminIntegration H5PUtils */
var H5PLibraryList = H5PLibraryList || {};
(function ($) {
@@ -25,7 +25,7 @@ var H5PLibraryList = H5PLibraryList || {};
*/
H5PLibraryList.createLibraryList = function (libraries) {
var t = H5PAdminIntegration.l10n;
- if(libraries.listData === undefined || libraries.listData.length === 0) {
+ if (libraries.listData === undefined || libraries.listData.length === 0) {
return $('' + t.NA + '
');
}
diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js
index 772cafd..4ed65e0 100644
--- a/js/h5p-resizer.js
+++ b/js/h5p-resizer.js
@@ -21,7 +21,7 @@
iframe.style.width = '100%';
// Tell iframe that it needs to resize when our window resizes
- var resize = function (event) {
+ var resize = function () {
if (iframe.contentWindow) {
// Limit resize calls to avoid flickering
respond('resize');
@@ -64,7 +64,7 @@
* @param {Object} data Payload
* @param {Function} respond Send a response to the iframe
*/
- actionHandlers.resize = function (iframe, data, respond) {
+ actionHandlers.resize = function (iframe, data) {
// Resize iframe so all content is visible. Use scrollHeight to make sure we get everything
iframe.style.height = data.scrollHeight + 'px';
};
diff --git a/js/h5p-utils.js b/js/h5p-utils.js
index f47d1b6..b5aa333 100644
--- a/js/h5p-utils.js
+++ b/js/h5p-utils.js
@@ -1,3 +1,4 @@
+/* global H5PAdminIntegration*/
var H5PUtils = H5PUtils || {};
(function ($) {
@@ -9,7 +10,7 @@ var H5PUtils = H5PUtils || {};
H5PUtils.createTable = function (headers) {
var $table = $('');
- if(headers) {
+ if (headers) {
var $thead = $('');
var $tr = $('
');
@@ -44,7 +45,7 @@ var H5PUtils = H5PUtils || {};
};
}
- $(' | ', value).appendTo($tr);
+ $(' | ', value).appendTo($tr);
});
return $tr;
diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js
index c1d6c66..e012ac1 100644
--- a/js/h5p-x-api-event.js
+++ b/js/h5p-x-api-event.js
@@ -133,9 +133,10 @@ H5P.XAPIEvent.prototype.setObject = function (instance) {
}
}
else {
- if (H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + instance.contentId].title) {
+ var content = H5P.getContentForInstance(instance.contentId);
+ if (content && content.metadata && content.metadata.title) {
this.data.statement.object.definition.name = {
- "en-US": H5P.createTitle(H5PIntegration.contents['cid-' + instance.contentId].title)
+ "en-US": H5P.createTitle(content.metadata.title)
};
}
}
@@ -150,7 +151,6 @@ H5P.XAPIEvent.prototype.setObject = function (instance) {
*/
H5P.XAPIEvent.prototype.setContext = function (instance) {
if (instance.parent && (instance.parent.contentId || instance.parent.subContentId)) {
- var parentId = instance.parent.subContentId === undefined ? instance.parent.contentId : instance.parent.subContentId;
this.data.statement.context = {
"contextActivities": {
"parent": [
@@ -217,7 +217,7 @@ H5P.XAPIEvent.prototype.setActor = function () {
* @returns {number}
* The max score, or null if not defined
*/
-H5P.XAPIEvent.prototype.getMaxScore = function() {
+H5P.XAPIEvent.prototype.getMaxScore = function () {
return this.getVerifiedStatementValue(['result', 'score', 'max']);
};
@@ -227,7 +227,7 @@ H5P.XAPIEvent.prototype.getMaxScore = function() {
* @returns {number}
* The score, or null if not defined
*/
-H5P.XAPIEvent.prototype.getScore = function() {
+H5P.XAPIEvent.prototype.getScore = function () {
return this.getVerifiedStatementValue(['result', 'score', 'raw']);
};
@@ -256,7 +256,7 @@ H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) {
H5P.XAPIEvent.prototype.isFromChild = function () {
var parentId = this.getVerifiedStatementValue(['context', 'contextActivities', 'parent', 0, 'id']);
return !parentId || parentId.indexOf('subContentId') === -1;
-}
+};
/**
* Figure out if a property exists in the statement and return it
@@ -267,7 +267,7 @@ H5P.XAPIEvent.prototype.isFromChild = function () {
* @returns {*}
* The value of the property if it is set, null otherwise.
*/
-H5P.XAPIEvent.prototype.getVerifiedStatementValue = function(keys) {
+H5P.XAPIEvent.prototype.getVerifiedStatementValue = function (keys) {
var val = this.data.statement;
for (var i = 0; i < keys.length; i++) {
if (val[keys[i]] === undefined) {
diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js
index 8a27eb9..66971cd 100644
--- a/js/h5p-x-api.js
+++ b/js/h5p-x-api.js
@@ -92,7 +92,7 @@ H5P.EventDispatcher.prototype.triggerXAPIScored = function (score, maxScore, ver
this.trigger(event);
};
-H5P.EventDispatcher.prototype.setActivityStarted = function() {
+H5P.EventDispatcher.prototype.setActivityStarted = function () {
if (this.activityStartTime === undefined) {
// Don't trigger xAPI events in the editor
if (this.contentId !== undefined &&
diff --git a/js/h5p.js b/js/h5p.js
index ef56834..7f63c1f 100644
--- a/js/h5p.js
+++ b/js/h5p.js
@@ -89,7 +89,7 @@ H5P.init = function (target) {
}
// H5Ps added in normal DIV.
- var $containers = H5P.jQuery('.h5p-content:not(.h5p-initialized)', target).each(function () {
+ H5P.jQuery('.h5p-content:not(.h5p-initialized)', target).each(function () {
var $element = H5P.jQuery(this).addClass('h5p-initialized');
var $container = H5P.jQuery('').appendTo($element);
var contentId = $element.data('content-id');
@@ -99,7 +99,8 @@ H5P.init = function (target) {
}
var library = {
library: contentData.library,
- params: JSON.parse(contentData.jsonContent)
+ params: JSON.parse(contentData.jsonContent),
+ metadata: contentData.metadata
};
H5P.getUserData(contentId, 'state', function (err, previousState) {
@@ -163,7 +164,7 @@ H5P.init = function (target) {
if (displayOptions.frame) {
// Special handling of copyrights
if (displayOptions.copyright) {
- var copyrights = H5P.getCopyrights(instance, library.params, contentId);
+ var copyrights = H5P.getCopyrights(instance, library.params, contentId, library.metadata);
if (!copyrights) {
displayOptions.copyright = false;
}
@@ -300,7 +301,7 @@ H5P.init = function (target) {
});
// When resize has been prepared tell parent window to resize
- H5P.communicator.on('resizePrepared', function (data) {
+ H5P.communicator.on('resizePrepared', function () {
H5P.communicator.send('resize', {
scrollHeight: document.body.scrollHeight
});
@@ -493,7 +494,7 @@ H5P.fullScreen = function ($element, instance, exitCallback, body, forceSemiFull
}
var $container = $element;
- var $classes, $iframe;
+ var $classes, $iframe, $body;
if (body === undefined) {
$body = H5P.$body;
}
@@ -795,6 +796,10 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) {
extras.previousState = library.userDatas.state;
}
+ if (library.metadata) {
+ extras.metadata = library.metadata;
+ }
+
// Makes all H5P libraries extend H5P.ContentType:
var standalone = extras.standalone || false;
// This order makes it possible for an H5P library to override H5P.ContentType functions!
@@ -973,9 +978,11 @@ H5P.Dialog = function (name, title, content, $element) {
* Parameters of the content instance.
* @param {number} contentId
* Identifies the H5P content
+ * @param {Object} metadata
+ * Metadata of the content instance.
* @returns {string} Copyright information.
*/
-H5P.getCopyrights = function (instance, parameters, contentId) {
+H5P.getCopyrights = function (instance, parameters, contentId, metadata) {
var copyrights;
if (instance.getCopyrights !== undefined) {
@@ -994,6 +1001,11 @@ H5P.getCopyrights = function (instance, parameters, contentId) {
H5P.findCopyrights(copyrights, parameters, contentId);
}
+ var metadataCopyrights = H5P.buildMetadataCopyrights(metadata, instance.libraryInfo.machineName);
+ if (metadataCopyrights !== undefined) {
+ copyrights.addMediaInFront(metadataCopyrights);
+ }
+
if (copyrights !== undefined) {
// Convert to string
copyrights = copyrights.toString();
@@ -1010,8 +1022,19 @@ H5P.getCopyrights = function (instance, parameters, contentId) {
* To search for file objects in.
* @param {number} contentId
* Used to insert thumbnails for images.
+ * @param {Object} extras - Extras.
+ * @param {object} extras.metadata - Metadata
+ * @param {object} extras.machineName - Library name of some kind.
+ * Metadata of the content instance.
*/
-H5P.findCopyrights = function (info, parameters, contentId) {
+H5P.findCopyrights = function (info, parameters, contentId, extras) {
+ // If extras are
+ if (extras) {
+ extras.params = parameters;
+ buildFromMetadata(extras, extras.machineName, contentId);
+ }
+
+ var lastContentTypeName;
// Cycle through parameters
for (var field in parameters) {
if (!parameters.hasOwnProperty(field)) {
@@ -1021,6 +1044,8 @@ H5P.findCopyrights = function (info, parameters, contentId) {
/**
* @deprecated This hack should be removed after 2017-11-01
* The code that was using this was removed by HFP-574
+ * This note was seen on 2018-04-04, and consultation with
+ * higher authorities lead to keeping the code for now ;-)
*/
if (field === 'overrideSettings') {
console.warn("The semantics field 'overrideSettings' is DEPRECATED and should not be used.");
@@ -1030,12 +1055,21 @@ H5P.findCopyrights = function (info, parameters, contentId) {
var value = parameters[field];
+ if (value && value.library && typeof value.library === 'string') {
+ lastContentTypeName = value.library.split(' ')[0];
+ }
+ else if (value && value.library && typeof value.library === 'object') {
+ lastContentTypeName = (value.library.library && typeof value.library.library === 'string') ? value.library.library.split(' ')[0] : lastContentTypeName;
+ }
+
if (value instanceof Array) {
// Cycle through array
H5P.findCopyrights(info, value, contentId);
}
else if (value instanceof Object) {
- // Check if object is a file with copyrights
+ buildFromMetadata(value, lastContentTypeName, contentId);
+
+ // Check if object is a file with copyrights (old core)
if (value.copyright === undefined ||
value.copyright.license === undefined ||
value.path === undefined ||
@@ -1054,6 +1088,43 @@ H5P.findCopyrights = function (info, parameters, contentId) {
}
}
}
+
+ function buildFromMetadata(data, name, contentId) {
+ if (data.metadata) {
+ const metadataCopyrights = H5P.buildMetadataCopyrights(data.metadata, name);
+ if (metadataCopyrights !== undefined) {
+ if (data.params && data.params.contentName === 'Image' && data.params.file) {
+ const path = data.params.file.path;
+ const width = data.params.file.width;
+ const height = data.params.file.height;
+ metadataCopyrights.setThumbnail(new H5P.Thumbnail(H5P.getPath(path, contentId), width, height));
+ }
+ info.addMedia(metadataCopyrights);
+ }
+ }
+ }
+};
+
+H5P.buildMetadataCopyrights = function (metadata) {
+ if (metadata && metadata.license !== undefined && metadata.license !== 'U') {
+ var dataset = {
+ contentType: metadata.contentType,
+ title: metadata.title,
+ author: (metadata.authors && metadata.authors.length > 0) ? metadata.authors.map(function (author) {
+ return (author.role) ? author.name + ' (' + author.role + ')' : author.name;
+ }).join(', ') : undefined,
+ source: metadata.source,
+ year: (metadata.yearFrom) ? (metadata.yearFrom + ((metadata.yearTo) ? '-' + metadata.yearTo: '')) : undefined,
+ license: metadata.license,
+ version: metadata.licenseVersion,
+ licenseExtras: metadata.licenseExtras,
+ changes: (metadata.changes && metadata.changes.length > 0) ? metadata.changes.map(function (change) {
+ return change.log + (change.author ? ', ' + change.author : '') + (change.date ? ', ' + change.date : '');
+ }).join(' / ') : undefined
+ };
+
+ return new H5P.MediaCopyright(dataset);
+ }
};
/**
@@ -1110,10 +1181,10 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) {
updateEmbed();
// Select text and expand textareas
- $dialog.find('.h5p-embed-code-container').each(function(index, value) {
- H5P.jQuery(this).css('height', this.scrollHeight + 'px').focus(function() {
- H5P.jQuery(this).select();
- });
+ $dialog.find('.h5p-embed-code-container').each(function () {
+ H5P.jQuery(this).css('height', this.scrollHeight + 'px').focus(function () {
+ H5P.jQuery(this).select();
+ });
});
$dialog.find('.h5p-embed-code-container').eq(0).select();
positionInner();
@@ -1130,7 +1201,7 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) {
$expander.addClass('h5p-open').text(H5P.t('hideAdvanced'));
$content.show();
}
- $dialog.find('.h5p-embed-code-container').each(function(index, value) {
+ $dialog.find('.h5p-embed-code-container').each(function () {
H5P.jQuery(this).css('height', this.scrollHeight + 'px');
});
positionInner();
@@ -1175,6 +1246,17 @@ H5P.ContentCopyrights = function () {
}
};
+ /**
+ * Add sub content in front.
+ *
+ * @param {H5P.MediaCopyright} newMedia
+ */
+ this.addMediaInFront = function (newMedia) {
+ if (newMedia !== undefined) {
+ media.unshift(newMedia);
+ }
+ };
+
/**
* Add sub content.
*
@@ -1294,7 +1376,7 @@ H5P.MediaCopyright = function (copyright, labels, order, extraFields) {
link = copyrightLicense.link.replace(':version', copyrightLicense.linkVersions ? copyrightLicense.linkVersions[version] : version);
}
else if (versionInfo && copyrightLicense.hasOwnProperty('link')) {
- link = versionInfo.link
+ link = versionInfo.link;
}
if (link) {
value = '' + value + '';
@@ -1331,16 +1413,19 @@ H5P.MediaCopyright = function (copyright, labels, order, extraFields) {
if (order === undefined) {
// Set default order
- order = ['title', 'author', 'year', 'source', 'license'];
+ order = ['contentType', 'title', 'license', 'author', 'year', 'source', 'licenseExtras', 'changes'];
}
for (var i = 0; i < order.length; i++) {
var fieldName = order[i];
- if (copyright[fieldName] !== undefined) {
+ if (copyright[fieldName] !== undefined && copyright[fieldName] !== '') {
var humanValue = copyright[fieldName];
if (fieldName === 'license') {
humanValue = humanizeLicense(copyright.license, copyright.version);
}
+ if (fieldName === 'source') {
+ humanValue = (humanValue) ? '' + humanValue + '' : undefined;
+ }
list.add(new H5P.Field(getLabel(fieldName), humanValue));
}
}
@@ -1529,7 +1614,8 @@ H5P.Coords = function (x, y, w, h) {
this.y = x.y;
this.w = x.w;
this.h = x.h;
- } else {
+ }
+ else {
if (x !== undefined) {
this.x = x;
}
@@ -1561,8 +1647,8 @@ H5P.libraryFromString = function (library) {
if (res !== null) {
return {
'machineName': res[1],
- 'majorVersion': res[2],
- 'minorVersion': res[3]
+ 'majorVersion': parseInt(res[2]),
+ 'minorVersion': parseInt(res[3])
};
}
else {
@@ -1785,7 +1871,7 @@ H5P.on = function (instance, eventType, handler) {
* @returns {string} UUID
*/
H5P.createUUID = function () {
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(char) {
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (char) {
var random = Math.random()*16|0, newChar = char === 'x' ? random : (random&0x3|0x8);
return newChar.toString(16);
});
@@ -1944,8 +2030,11 @@ H5P.createTitle = function (rawTitle, maxLength) {
* @returns {string|null} Returns the string that should be set as crossorigin policy for elements or null if
* no policy is set.
*/
- H5P.getCrossOrigin = function () {
- return H5PIntegration.crossorigin ? H5PIntegration.crossorigin : null;
+ H5P.getCrossOrigin = function (url) {
+ var crossorigin = H5PIntegration.crossorigin;
+ var urlRegex = H5PIntegration.crossoriginRegex;
+
+ return crossorigin && urlRegex && url.match(urlRegex) ? crossorigin : null;
};
/**
@@ -2010,7 +2099,7 @@ H5P.createTitle = function (rawTitle, maxLength) {
}
preloadedData[options.subContentId][dataId] = data;
- contentUserDataAjax(contentId, dataId, options.subContentId, function (error, data) {
+ contentUserDataAjax(contentId, dataId, options.subContentId, function (error) {
if (options.errorCallback && error) {
options.errorCallback(error);
}
@@ -2041,9 +2130,220 @@ H5P.createTitle = function (rawTitle, maxLength) {
contentUserDataAjax(contentId, dataId, subContentId, undefined, null);
};
+ /**
+ * Function for getting content for a certain ID
+ *
+ * @param {number} contentId
+ * @return {Object}
+ */
+ H5P.getContentForInstance = function (contentId) {
+ var key = 'cid-' + contentId;
+ var exists = H5PIntegration && H5PIntegration.contents &&
+ H5PIntegration.contents[key];
+
+ return exists ? H5PIntegration.contents[key] : undefined;
+ };
+
+ /**
+ * Prepares the content parameters for storing in the clipboard.
+ *
+ * @class
+ * @param {Object} parameters The parameters for the content to store
+ * @param {string} [genericProperty] If only part of the parameters are generic, which part
+ * @param {string} [specificKey] If the parameters are specific, what content type does it fit
+ * @returns {Object} Ready for the clipboard
+ */
+ H5P.ClipboardItem = function (parameters, genericProperty, specificKey) {
+ var self = this;
+
+ /**
+ * Set relative dimensions when params contains a file with a width and a height.
+ * Very useful to be compatible with wysiwyg editors.
+ *
+ * @private
+ */
+ var setDimensionsFromFile = function () {
+ if (!self.generic) {
+ return;
+ }
+ var params = self.specific[self.generic];
+ if (!params.params.file || !params.params.file.width || !params.params.file.height) {
+ return;
+ }
+
+ self.width = 20; // %
+ self.height = (params.params.file.height / params.params.file.width) * self.width;
+ };
+
+ if (!genericProperty) {
+ genericProperty = 'action';
+ parameters = {
+ action: parameters
+ };
+ }
+
+ self.specific = parameters;
+
+ if (genericProperty && parameters[genericProperty]) {
+ self.generic = genericProperty;
+ }
+ if (specificKey) {
+ self.from = specificKey;
+ }
+
+ if (window.H5PEditor && H5PEditor.contentId) {
+ self.contentId = H5PEditor.contentId;
+ }
+
+ if (!self.specific.width && !self.specific.height) {
+ setDimensionsFromFile();
+ }
+ };
+
+ /**
+ * Store item in the H5P Clipboard.
+ *
+ * @param {H5P.ClipboardItem|*} clipboardItem
+ */
+ H5P.clipboardify = function (clipboardItem) {
+ if (!(clipboardItem instanceof H5P.ClipboardItem)) {
+ clipboardItem = new H5P.ClipboardItem(clipboardItem);
+ }
+ H5P.setClipboard(clipboardItem);
+ };
+
+ /**
+ * This is a cache for pasted data to prevent parsing multiple times.
+ * @type {Object}
+ */
+ var parsedClipboard = null;
+
+ /**
+ * Retrieve parsed clipboard data.
+ *
+ * @return {Object}
+ */
+ H5P.getClipboard = function () {
+ if (!parsedClipboard) {
+ parsedClipboard = parseClipboard();
+ }
+
+ return parsedClipboard;
+ };
+
+ /**
+ * Set item in the H5P Clipboard.
+ *
+ * @param {H5P.ClipboardItem|object} clipboardItem - Data to be set.
+ */
+ H5P.setClipboard = function (clipboardItem) {
+ localStorage.setItem('h5pClipboard', JSON.stringify(clipboardItem));
+
+ // Clear cache
+ parsedClipboard = null;
+
+ // Trigger an event so all 'Paste' buttons may be enabled.
+ H5P.externalDispatcher.trigger('datainclipboard', {reset: false});
+ };
+
+ /**
+ * Get config for a library
+ *
+ * @param string machineName
+ * @return Object
+ */
+ H5P.getLibraryConfig = function (machineName) {
+ var hasConfig = H5PIntegration.libraryConfig && H5PIntegration.libraryConfig[machineName];
+ return hasConfig ? H5PIntegration.libraryConfig[machineName] : {};
+ };
+
+ /**
+ * Get item from the H5P Clipboard.
+ *
+ * @private
+ * @param {boolean} [skipUpdateFileUrls]
+ * @return {Object}
+ */
+ var parseClipboard = function () {
+ var clipboardData = localStorage.getItem('h5pClipboard');
+ if (!clipboardData) {
+ return;
+ }
+
+ // Try to parse clipboard dat
+ try {
+ clipboardData = JSON.parse(clipboardData);
+ }
+ catch (err) {
+ console.error('Unable to parse JSON from clipboard.', err);
+ return;
+ }
+
+ // Update file URLs
+ updateFileUrls(clipboardData.specific, function (path) {
+ var isTmpFile = (path.substr(-4, 4) === '#tmp');
+ if (!isTmpFile && clipboardData.contentId) {
+ // Comes from existing content
+
+ if (H5PEditor.contentId) {
+ // .. to existing content
+ return '../' + clipboardData.contentId + '/' + path;
+ }
+ else {
+ // .. to new content
+ return (H5PEditor.contentRelUrl ? H5PEditor.contentRelUrl : '../content/') + clipboardData.contentId + '/' + path;
+ }
+ }
+ return path; // Will automatically be looked for in tmp folder
+ });
+
+
+ if (clipboardData.generic) {
+ // Use reference instead of key
+ clipboardData.generic = clipboardData.specific[clipboardData.generic];
+
+ // Avoid multiple content with same ID
+ delete clipboardData.generic.subContentId;
+ }
+
+ return clipboardData;
+ };
+
+ /**
+ * Update file URLs. Useful when copying content.
+ *
+ * @private
+ * @param {object} params Reference
+ * @param {function} handler Modifies the path to work when pasted
+ */
+ var updateFileUrls = function (params, handler) {
+ for (var prop in params) {
+ if (params.hasOwnProperty(prop) && params[prop] instanceof Object) {
+ var obj = params[prop];
+ if (obj.path !== undefined && obj.mime !== undefined) {
+ obj.path = handler(obj.path);
+ }
+ else {
+ updateFileUrls(obj, handler);
+ }
+ }
+ }
+ };
+
// Init H5P when page is fully loadded
$(document).ready(function () {
+ window.addEventListener('storage', function (event) {
+ // Pick up clipboard changes from other tabs
+ if (event.key === 'h5pClipboard') {
+ // Clear cache
+ parsedClipboard = null;
+
+ // Trigger an event so all 'Paste' buttons may be enabled.
+ H5P.externalDispatcher.trigger('datainclipboard', {reset: event.newValue === null});
+ }
+ });
+
var ccVersions = {
'default': '4.0',
'4.0': H5P.t('licenseCC40'),
@@ -2062,34 +2362,38 @@ H5P.createTitle = function (rawTitle, maxLength) {
'U': H5P.t('licenseU'),
'CC BY': {
label: H5P.t('licenseCCBY'),
- link: 'http://creativecommons.org/licenses/by/:version/legalcode',
+ link: 'http://creativecommons.org/licenses/by/:version',
versions: ccVersions
},
'CC BY-SA': {
label: H5P.t('licenseCCBYSA'),
- link: 'http://creativecommons.org/licenses/by-sa/:version/legalcode',
+ link: 'http://creativecommons.org/licenses/by-sa/:version',
versions: ccVersions
},
'CC BY-ND': {
label: H5P.t('licenseCCBYND'),
- link: 'http://creativecommons.org/licenses/by-nd/:version/legalcode',
+ link: 'http://creativecommons.org/licenses/by-nd/:version',
versions: ccVersions
},
'CC BY-NC': {
label: H5P.t('licenseCCBYNC'),
- link: 'http://creativecommons.org/licenses/by-nc/:version/legalcode',
+ link: 'http://creativecommons.org/licenses/by-nc/:version',
versions: ccVersions
},
'CC BY-NC-SA': {
label: H5P.t('licenseCCBYNCSA'),
- link: 'http://creativecommons.org/licenses/by-nc-sa/:version/legalcode',
+ link: 'http://creativecommons.org/licenses/by-nc-sa/:version',
versions: ccVersions
},
'CC BY-NC-ND': {
label: H5P.t('licenseCCBYNCND'),
- link: 'http://creativecommons.org/licenses/by-nc-nd/:version/legalcode',
+ link: 'http://creativecommons.org/licenses/by-nc-nd/:version',
versions: ccVersions
},
+ 'CC0 1.0': {
+ label: H5P.t('licenseCC010'),
+ link: 'https://creativecommons.org/publicdomain/zero/1.0/'
+ },
'GNU GPL': {
label: H5P.t('licenseGPL'),
link: 'http://www.gnu.org/licenses/gpl-:version-standalone.html',
@@ -2119,7 +2423,10 @@ H5P.createTitle = function (rawTitle, maxLength) {
}
},
'ODC PDDL': 'Public Domain Dedication and Licence',
- 'CC PDM': H5P.t('licensePDM'),
+ 'CC PDM': {
+ label: H5P.t('licensePDM'),
+ link: 'https://creativecommons.org/publicdomain/mark/1.0/'
+ },
'C': H5P.t('licenseC'),
};
diff --git a/styles/h5p.css b/styles/h5p.css
index f4324a7..e3ece5c 100644
--- a/styles/h5p.css
+++ b/styles/h5p.css
@@ -3,11 +3,11 @@
/* Custom H5P font to use for icons. */
@font-face {
font-family: 'h5p';
- src: url('../fonts/h5p-core-18.eot?cb8kvi');
- src: url('../fonts/h5p-core-18.eot?cb8kvi#iefix') format('embedded-opentype'),
- url('../fonts/h5p-core-18.ttf?cb8kvi') format('truetype'),
- url('../fonts/h5p-core-18.woff?cb8kvi') format('woff'),
- url('../fonts/h5p-core-18.svg?cb8kvi#h5p') format('svg');
+ src: url('../fonts/h5p-core-19.eot?cb8kvi');
+ src: url('../fonts/h5p-core-19.eot?cb8kvi#iefix') format('embedded-opentype'),
+ url('../fonts/h5p-core-19.ttf?cb8kvi') format('truetype'),
+ url('../fonts/h5p-core-19.woff?cb8kvi') format('woff'),
+ url('../fonts/h5p-core-19.svg?cb8kvi#h5p') format('svg');
font-weight: normal;
font-style: normal;
}