Merge branch 'master' into contentupgrade

pull/48/head
Thomas Horn Sivertsen 2019-02-11 13:33:08 +01:00 committed by GitHub
commit b81b985d5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 360 additions and 184 deletions

View File

@ -311,13 +311,7 @@ class H5PDefaultStorage implements \H5PFileStorage {
// Add filename to path
$path .= '/' . $file->getName();
$fileData = $file->getData();
if ($fileData) {
file_put_contents($path, $fileData);
}
else {
copy($_FILES['file']['tmp_name'], $path);
}
copy($_FILES['file']['tmp_name'], $path);
return $file;
}
@ -464,6 +458,24 @@ class H5PDefaultStorage implements \H5PFileStorage {
return file_exists($filePath);
}
/**
* Check if upgrades script exist for library.
*
* @param string $machineName
* @param int $majorVersion
* @param int $minorVersion
* @return string Relative path
*/
public function getUpgradeScript($machineName, $majorVersion, $minorVersion) {
$upgrades = "/libraries/{$machineName}-{$majorVersion}.{$minorVersion}/upgrades.js";
if (file_exists($this->path . $upgrades)) {
return $upgrades;
}
else {
return NULL;
}
}
/**
* Recursive function for copying directories.
*

View File

@ -84,13 +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']);
if (!isset($library['metadata'])) {
$library['metadata'] = 0;
}
// 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;
}

View File

@ -199,4 +199,14 @@ interface H5PFileStorage {
* @return bool
*/
public function hasPresave($libraryName, $developmentPath = null);
/**
* Check if upgrades script exist for library.
*
* @param string $machineName
* @param int $majorVersion
* @param int $minorVersion
* @return string Relative path
*/
public function getUpgradeScript($machineName, $majorVersion, $minorVersion);
}

View File

@ -4,7 +4,7 @@
*/
abstract class H5PMetadata {
const FIELDS = array(
private static $fields = array(
'title' => array(
'type' => 'text',
'maxLength' => 255
@ -64,54 +64,83 @@ abstract class H5PMetadata {
',"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 bool $include_missing For metadata fields not being set, skip 'em.
* Relevant for content upgrade
* @param array $types
* @return array
*/
public static function toDBArray($metadata, $include_title = true, &$types = array()) {
public static function toDBArray($metadata, $include_title = true, $include_missing = true, &$types = array()) {
$fields = array();
if (!is_array($metadata)) {
$metadata = (array) $metadata;
}
foreach (self::FIELDS as $key => $config) {
foreach (self::$fields as $key => $config) {
// Ignore title?
if ($key === 'title' && !$include_title) {
continue;
}
if (isset($metadata[$key])) {
$value = $metadata[$key];
$db_field_name = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $key));
$exists = array_key_exists($key, $metadata);
switch ($config['type']) {
case 'text':
if (strlen($value) > $config['maxLength']) {
$value = mb_substr($value, 0, $config['maxLength']);
}
$types[] = '%s';
break;
case 'int':
$value = ($value !== null) ? intval($value): null;
$types[] = '%i';
break;
case 'json':
$value = json_encode($value);
$types[] = '%s';
break;
}
$fields[$db_field_name] = $value;
// Don't include missing fields
if (!$include_missing && !$exists) {
continue;
}
$value = $exists ? $metadata[$key] : null;
// lowerCamelCase to snake_case
$db_field_name = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $key));
switch ($config['type']) {
case 'text':
if ($value !== null && strlen($value) > $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);
}
}

View File

@ -210,7 +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
* - metadata: 1 if the library should support setting metadata (copyright etc)
* - 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:
@ -535,9 +537,10 @@ interface H5PFrameworkInterface {
* Get number of contents using library as main library.
*
* @param int $libraryId
* @param array $skip
* @return int
*/
public function getNumContent($libraryId);
public function getNumContent($libraryId, $skip = NULL);
/**
* Determines if content slug is used.
@ -612,6 +615,14 @@ interface H5PFrameworkInterface {
* 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);
}
/**
@ -648,7 +659,7 @@ class H5PValidator {
'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|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)$/',
'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})$/',
@ -681,7 +692,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,}$/',
'metadata' => '/^(0|1)$/',
'metadataSettings' => array(
'disable' => '/^(0|1)$/',
'disableExtraTitleField' => '/^(0|1)$/'
),
'dynamicDependencies' => array(
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
'majorVersion' => '/^[0-9]{1,5}$/',
@ -914,11 +928,27 @@ class H5PValidator {
}
if (!empty($missingLibraries)) {
foreach ($missingLibraries as $libString => $library) {
$this->h5pF->setErrorMessage($this->h5pF->t('Missing required library @library', array('@library' => $libString)), 'missing-required-library');
// 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 (!$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."));
if ($skipContent || !$mainDependency || !$this->h5pF->libraryHasUpgrade(array(
'machineName' => $mainDependency['mainLibrary'],
'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');
}
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 = empty($missingLibraries) && $valid;
@ -1440,10 +1470,11 @@ class H5PStorage {
// Indicate that the dependencies of this library should be saved.
$library['saveDependencies'] = TRUE;
// Save library meta data
if (!isset($library['metadata'])) {
$library['metadata'] = 0;
}
// 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
@ -1592,6 +1623,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('&lt;', '<', str_replace('&gt;', '>', str_replace('&#039;', "'", str_replace('&quot;', '"', $value))));
}
/**
* Return path to h5p package.
*
@ -1624,14 +1665,14 @@ Class H5PExport {
// 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
);
foreach(array('authors', 'source', 'license', 'licenseVersion', 'licenseExtras' ,'yearFrom', 'yearTo', 'changes', 'authorComments') as $field) {
if (isset($content['metadata'][$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));
}
@ -1826,7 +1867,7 @@ class H5PCore {
public static $coreApi = array(
'majorVersion' => 1,
'minorVersion' => 19
'minorVersion' => 20
);
public static $styles = array(
'styles/h5p.css',
@ -1848,7 +1889,7 @@ class H5PCore {
'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 wav txt pdf rtf doc docx xls xlsx ppt pptx odt ods odp xml csv diff patch swf md textile vtt webvtt';
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';
public static $defaultLibraryWhitelistExtras = 'js css';
public $librariesJsonData, $contentJsonData, $mainJsonData, $h5pF, $fs, $h5pD, $disableFileCheck;
@ -1906,8 +1947,6 @@ class H5PCore {
$this->relativePathRegExp = '/^((\.\.\/){1,2})(.*content\/)?(\d+|editor)\/(.+)$/';
}
/**
* Save content and clear cache.
*
@ -1941,7 +1980,7 @@ class H5PCore {
if ($content !== NULL) {
// Validate main content's metadata
$validator = new H5PContentValidator($this->h5pF, $this);
$validator->validateMetadata($content['metadata']);
$content['metadata'] = $validator->validateMetadata($content['metadata']);
$content['library'] = array(
'id' => $content['libraryId'],
@ -1984,6 +2023,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(
@ -3301,7 +3344,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'),
);
}
}
@ -3375,15 +3421,24 @@ class H5PContentValidator {
* Validate metadata
*
* @param array $metadata
* @return array Validated & filtered
*/
public function validateMetadata(&$metadata) {
public function validateMetadata($metadata) {
$semantics = $this->getMetadataSemantics();
$group = (object)$metadata;
// Stop complaining about "invalid selected option in select" for
// old content without license chosen.
if (!isset($group->license)) {
$group->license = 'U';
}
$this->validateGroup($group, (object) array(
'type' => 'group',
'fields' => $semantics,
), FALSE);
return (array)$group;
}
/**
@ -3910,7 +3965,7 @@ class H5PContentValidator {
// Validate subcontent's metadata
if (isset($value->metadata)) {
$this->validateMetadata($value->metadata);
$value->metadata = $this->validateMetadata($value->metadata);
}
$validKeys = array('library', 'params', 'subContentId', 'metadata');
@ -4330,7 +4385,7 @@ class H5PContentValidator {
(object) array(
'type' => 'optgroup',
'label' => $this->h5pF->t('Creative Commons'),
'options' => [
'options' => array(
(object) array(
'value' => 'CC BY',
'label' => $this->h5pF->t('Attribution (CC BY)'),
@ -4369,7 +4424,7 @@ class H5PContentValidator {
'value' => 'CC PDM',
'label' => $this->h5pF->t('Public Domain Mark (PDM)')
),
]
)
),
(object) array(
'value' => 'GNU GPL',
@ -4475,7 +4530,7 @@ class H5PContentValidator {
'field' => (object) array(
'name' => 'change',
'type' => 'group',
'label' => $this->h5pF->t('Change Log'),
'label' => $this->h5pF->t('Changelog'),
'fields' => array(
(object) array(
'name' => 'date',
@ -4507,7 +4562,12 @@ class H5PContentValidator {
'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;

View File

@ -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;
};

View File

@ -11,7 +11,7 @@
* @class
* @augments H5P.EventDispatcher
*/
H5P.ContentType = function (isRootLibrary, library) {
H5P.ContentType = function (isRootLibrary) {
function ContentType() {}

View File

@ -27,6 +27,7 @@ H5P.ContentUpgradeProcess = (function (Version) {
self.loadLibrary = loadLibrary;
self.upgrade(name, oldVersion, newVersion, params.params, params.metadata, function (err, upgradedParams, upgradedMetadata) {
if (err) {
err.id = id;
return done(err);
}
@ -53,6 +54,12 @@ H5P.ContentUpgradeProcess = (function (Version) {
if (err) {
return done(err);
}
if (library.semantics === null) {
return done({
type: 'libraryMissing',
library: library.name + ' ' + library.version.major + '.' + library.version.minor
});
}
// Run upgrade routines on params
self.processParams(library, oldVersion, newVersion, params, metadata, function (err, params, metadata) {
@ -127,10 +134,10 @@ H5P.ContentUpgradeProcess = (function (Version) {
}, {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);
}
@ -176,7 +183,11 @@ H5P.ContentUpgradeProcess = (function (Version) {
var usedVer = new Version(usedLib[1]);
var availableVer = new Version(availableLib[1]);
if (usedVer.major > availableVer.major || (usedVer.major === availableVer.major && usedVer.minor >= availableVer.minor)) {
return done(); // Larger or same version that's available
return done({
type: 'errorTooHighVersion',
used: usedLib[0] + ' ' + usedVer,
supported: availableLib[0] + ' ' + availableVer
}); // Larger or same version that's available
}
// A newer version is available, upgrade params
@ -192,7 +203,12 @@ H5P.ContentUpgradeProcess = (function (Version) {
});
}
}
done();
// Content type was not supporte by the higher version
done({
type: 'errorNotSupported',
used: usedLib[0] + ' ' + usedVer
});
break;
case 'group':

View File

@ -1,3 +1,4 @@
/* global importScripts */
var H5P = H5P || {};
importScripts('h5p-version.js', 'h5p-content-upgrade-process.js');

View File

@ -1,7 +1,7 @@
/*jshint -W083 */
/* global H5PAdminIntegration H5PUtils */
(function ($, Version, EventDispatcher) {
var info, $container, librariesCache = {}, scriptsCache = {};
var info, $log, $container, librariesCache = {}, scriptsCache = {};
// Initialize
$(document).ready(function () {
@ -9,7 +9,9 @@
info = H5PAdminIntegration.libraryInfo;
// Get and reset container
$container = $('#h5p-admin-container').html('<p>' + info.message + '</p>');
const $wrapper = $('#h5p-admin-container').html('');
$log = $('<ul class="content-upgrade-log"></ul>').appendTo($wrapper);
$container = $('<div><p>' + info.message + '</p></div>').appendTo($wrapper);
// Make it possible to select version
var $version = $(getVersionSelect(info.versions)).appendTo($container);
@ -132,9 +134,7 @@
},
error: function (error) {
self.printError(error.err);
// Stop everything
self.terminate();
self.workDone(error.id, null, this);
},
loadLibrary: function (details) {
var worker = this;
@ -196,7 +196,7 @@
self.token = inData.token;
// Start processing
self.processBatch(inData.params);
self.processBatch(inData.params, inData.skipped);
});
};
@ -217,11 +217,12 @@
*
* @param {Object} parameters
*/
ContentUpgrade.prototype.processBatch = function (parameters) {
ContentUpgrade.prototype.processBatch = function (parameters, skipped) {
var self = this;
// Track upgraded params
self.upgraded = {};
self.skipped = skipped;
// Track current batch
self.parameters = parameters;
@ -300,7 +301,7 @@
}, function done(err, result) {
if (err) {
self.printError(err);
return ;
result = null;
}
self.workDone(id, result);
@ -315,7 +316,12 @@
var self = this;
self.working--;
self.upgraded[id] = result;
if (result === null) {
self.skipped.push(id);
}
else {
self.upgraded[id] = result;
}
// Update progress message
var percentComplete = Math.round((info.total - self.left + self.current) / (info.total / 100));
@ -333,6 +339,7 @@
self.nextBatch({
libraryId: self.version.libraryId,
token: self.token,
skipped: JSON.stringify(self.skipped),
params: JSON.stringify(self.upgraded)
});
}
@ -443,11 +450,26 @@
ContentUpgrade.prototype.printError = function (error) {
var self = this;
if (error.type === 'errorParamsBroken') {
error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken;
}
else if (error.type === 'scriptMissing') {
error = info.errorScript.replace('%lib', error.library);
switch (error.type) {
case 'errorParamsBroken':
error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken;
break;
case 'libraryMissing':
error = info.errorLibrary.replace('%lib', error.library);
break;
case 'scriptMissing':
error = info.errorScript.replace('%lib', error.library);
break;
case 'errorTooHighVersion':
error = info.errorContent.replace('%id', error.id) + ' ' + info.errorTooHighVersion.replace('%used', error.used).replace('%supported', error.supported);
break;
case 'errorNotSupported':
error = info.errorContent.replace('%id', error.id) + ' ' + info.errorNotSupported.replace('%used', error.used);
break;
}
self.trigger('error', {
@ -455,7 +477,7 @@
infoError: info.error,
});
self.setStatus('<p>' + info.error + '<br/>' + error + '</p>');
$('<li>' + info.error + '<br/>' + error + '</li>').appendTo($log);
};
/**

View File

@ -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) {

View File

@ -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;

View File

@ -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<lastIndex; i++) {
for (; i<lastIndex; i++) {
var content = H5PLibraryDetails.currentContent[i];
H5PLibraryDetails.$contentTable.append(H5PUtils.createTableRow(['<a href="' + content.url + '">' + content.title + '</a>']));
}
@ -97,7 +98,7 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
H5PLibraryDetails.$next = $('<button type="button" class="next h5p-admin">></button>');
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);
}

View File

@ -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 $('<div>' + t.NA + '</div>');
}

View File

@ -20,8 +20,12 @@
// Make iframe responsive
iframe.style.width = '100%';
// Bugfix for Chrome: Force update of iframe width. If this is not done the
// document size may not be updated before the content resizes.
iframe.getBoundingClientRect();
// 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 +68,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';
};

View File

@ -1,3 +1,4 @@
/* global H5PAdminIntegration*/
var H5PUtils = H5PUtils || {};
(function ($) {
@ -9,7 +10,7 @@ var H5PUtils = H5PUtils || {};
H5PUtils.createTable = function (headers) {
var $table = $('<table class="h5p-admin-table' + (H5PAdminIntegration.extraTableClasses !== undefined ? ' ' + H5PAdminIntegration.extraTableClasses : '') + '"></table>');
if(headers) {
if (headers) {
var $thead = $('<thead></thead>');
var $tr = $('<tr></tr>');
@ -44,7 +45,7 @@ var H5PUtils = H5PUtils || {};
};
}
$('<td/>', value).appendTo($tr);
$('<td/>', value).appendTo($tr);
});
return $tr;

View File

@ -7,11 +7,24 @@ H5P.Version = (function () {
* @param {String} version
*/
function Version(version) {
var versionSplit = version.split('.', 3);
// Public
this.major =+ versionSplit[0];
this.minor =+ versionSplit[1];
if (typeof version === 'string') {
// Name version string (used by content upgrade)
var versionSplit = version.split('.', 3);
this.major =+ versionSplit[0];
this.minor =+ versionSplit[1];
}
else {
// Library objects (used by editor)
if (version.localMajorVersion !== undefined) {
this.major =+ version.localMajorVersion;
this.minor =+ version.localMinorVersion;
}
else {
this.major =+ version.majorVersion;
this.minor =+ version.minorVersion;
}
}
/**
* Public. Custom string for this object.

View File

@ -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']);
};
@ -239,7 +239,7 @@ H5P.XAPIEvent.prototype.getScore = function() {
*/
H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) {
var xAPIId;
if (instance.contentId && H5PIntegration && H5PIntegration.contents) {
if (instance.contentId && H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + instance.contentId]) {
xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url;
if (instance.subContentId) {
xAPIId += '?subContentId=' + instance.subContentId;
@ -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) {

View File

@ -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 &&

View File

@ -75,8 +75,9 @@ H5P.init = function (target) {
* @type {boolean}
*/
H5P.fullscreenSupported = !(H5P.isFramed && H5P.externalEmbed !== false) || !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled);
// We should consider document.msFullscreenEnabled when they get their
// element sizing corrected. Ref. https://connect.microsoft.com/IE/feedback/details/838286/ie-11-incorrectly-reports-dom-element-sizes-in-fullscreen-mode-when-fullscreened-element-is-within-an-iframe
// -We should consider document.msFullscreenEnabled when they get their
// -element sizing corrected. Ref. https://connect.microsoft.com/IE/feedback/details/838286/ie-11-incorrectly-reports-dom-element-sizes-in-fullscreen-mode-when-fullscreened-element-is-within-an-iframe
// Update: Seems to be no need as they've moved on to Webkit
}
// Deprecated variable, kept to maintain backwards compatability
@ -89,7 +90,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('<div class="h5p-container"></div>').appendTo($element);
var contentId = $element.data('content-id');
@ -139,6 +140,7 @@ H5P.init = function (target) {
'<div role="button" ' +
'tabindex="0" ' +
'class="h5p-enable-fullscreen" ' +
'aria-label="' + H5P.t('fullscreen') +
'title="' + H5P.t('fullscreen') + '">' +
'</div>' +
'</div>')
@ -301,7 +303,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
});
@ -494,7 +496,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;
}
@ -569,7 +571,7 @@ H5P.fullScreen = function ($element, instance, exitCallback, body, forceSemiFull
}
before('h5p-semi-fullscreen');
var $disable = H5P.jQuery('<div role="button" tabindex="0" class="h5p-disable-fullscreen" title="' + H5P.t('disableFullscreen') + '"></div>').appendTo($container.find('.h5p-content-controls'));
var $disable = H5P.jQuery('<div role="button" tabindex="0" class="h5p-disable-fullscreen" title="' + H5P.t('disableFullscreen') + '" aria-label="' + H5P.t('disableFullscreen') + '"></div>').appendTo($container.find('.h5p-content-controls'));
var keyup, disableSemiFullscreen = H5P.exitFullScreen = function () {
if (prevViewportContent) {
// Use content from the previous viewport tag
@ -924,7 +926,7 @@ H5P.Dialog = function (name, title, content, $element) {
<div class="h5p-inner">\
<h2>' + title + '</h2>\
<div class="h5p-scroll-content">' + content + '</div>\
<div class="h5p-close" role="button" tabindex="0" title="' + H5P.t('close') + '">\
<div class="h5p-close" role="button" tabindex="0" aria-label="' + H5P.t('close') + '" title="' + H5P.t('close') + '"></div>\
</div>\
</div>')
.insertAfter($element)
@ -1089,7 +1091,7 @@ H5P.findCopyrights = function (info, parameters, contentId, extras) {
}
}
function buildFromMetadata (data, name, contentId) {
function buildFromMetadata(data, name, contentId) {
if (data.metadata) {
const metadataCopyrights = H5P.buildMetadataCopyrights(data.metadata, name);
if (metadataCopyrights !== undefined) {
@ -1105,11 +1107,12 @@ H5P.findCopyrights = function (info, parameters, contentId, extras) {
}
};
H5P.buildMetadataCopyrights = function (metadata, contentTypeName) {
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) {
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,
@ -1117,24 +1120,12 @@ H5P.buildMetadataCopyrights = function (metadata, contentTypeName) {
license: metadata.license,
version: metadata.licenseVersion,
licenseExtras: metadata.licenseExtras,
changes: (metadata.changes && metadata.changes.length > 0) ? metadata.changes.map(function(change) {
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
};
if (contentTypeName) {
contentTypeName = contentTypeName
.split(' ')[0]
.replace(/^H5P\./, '')
.replace(/([a-z])([A-Z])/g, '$1' + ' ' + '$2');
}
return new H5P.MediaCopyright(
dataset,
{type: 'Content type', licenseExtras: 'License extras', changes: 'Changelog'},
['type', 'title', 'license', 'author', 'year', 'source', 'licenseExtras', 'changes'],
{type: contentTypeName}
);
return new H5P.MediaCopyright(dataset);
}
};
@ -1192,10 +1183,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();
@ -1212,7 +1203,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();
@ -1424,12 +1415,12 @@ 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);
@ -1625,7 +1616,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;
}
@ -1881,7 +1873,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);
});
@ -2109,7 +2101,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);
}
@ -2222,23 +2214,13 @@ H5P.createTitle = function (rawTitle, maxLength) {
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;
return parseClipboard();
};
/**
@ -2249,9 +2231,6 @@ H5P.createTitle = function (rawTitle, maxLength) {
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});
};
@ -2271,7 +2250,6 @@ H5P.createTitle = function (rawTitle, maxLength) {
* Get item from the H5P Clipboard.
*
* @private
* @param {boolean} [skipUpdateFileUrls]
* @return {Object}
*/
var parseClipboard = function () {
@ -2289,8 +2267,8 @@ H5P.createTitle = function (rawTitle, maxLength) {
return;
}
// Update file URLs
updateFileUrls(clipboardData.specific, function (path) {
// Update file URLs and reset content Ids
recursiveUpdate(clipboardData.specific, function (path) {
var isTmpFile = (path.substr(-4, 4) === '#tmp');
if (!isTmpFile && clipboardData.contentId) {
// Comes from existing content
@ -2311,22 +2289,20 @@ H5P.createTitle = function (rawTitle, maxLength) {
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.
* Update file URLs and reset content IDs.
* Useful when copying content.
*
* @private
* @param {object} params Reference
* @param {function} handler Modifies the path to work when pasted
*/
var updateFileUrls = function (params, handler) {
var recursiveUpdate = function (params, handler) {
for (var prop in params) {
if (params.hasOwnProperty(prop) && params[prop] instanceof Object) {
var obj = params[prop];
@ -2334,7 +2310,11 @@ H5P.createTitle = function (rawTitle, maxLength) {
obj.path = handler(obj.path);
}
else {
updateFileUrls(obj, handler);
if (obj.library !== undefined && obj.subContentId !== undefined) {
// Avoid multiple content with same ID
delete obj.subContentId;
}
recursiveUpdate(obj, handler);
}
}
}
@ -2346,9 +2326,6 @@ H5P.createTitle = function (rawTitle, maxLength) {
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});
}

5
js/jquery.js vendored
View File

@ -13,3 +13,8 @@ var H5P = window.H5P = window.H5P || {};
* @member
*/
H5P.jQuery = jQuery.noConflict(true);
H5P.jQuery.ajaxPrefilter(function (s) {
if (s.crossDomain) {
s.contents.script = false;
}
});

View File

@ -339,3 +339,6 @@ button.h5p-admin.disabled:hover {
.h5p-data-view .h5p-facet-tag > span:active {
color: #d20000;
}
.content-upgrade-log {
color: red;
}

View File

@ -455,3 +455,19 @@ div.h5p-fullscreen {
.h5p-dialog-ok-button:active {
background: #eeffee;
}
/* This is loaded as part of Core and not Editor since this needs to be outside the editor iframe */
.h5peditor-semi-fullscreen {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 101;
}
iframe.h5peditor-semi-fullscreen {
background: #fff;
z-index: 100001;
}