Merge branch 'release' into smart-file-names
commit
3eea931332
|
@ -9,7 +9,7 @@ class H5PDevelopment {
|
||||||
const MODE_CONTENT = 1;
|
const MODE_CONTENT = 1;
|
||||||
const MODE_LIBRARY = 2;
|
const MODE_LIBRARY = 2;
|
||||||
|
|
||||||
private $h5pF, $libraries, $language;
|
private $h5pF, $libraries, $language, $filesPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
|
@ -23,6 +23,7 @@ class H5PDevelopment {
|
||||||
public function __construct($H5PFramework, $filesPath, $language, $libraries = NULL) {
|
public function __construct($H5PFramework, $filesPath, $language, $libraries = NULL) {
|
||||||
$this->h5pF = $H5PFramework;
|
$this->h5pF = $H5PFramework;
|
||||||
$this->language = $language;
|
$this->language = $language;
|
||||||
|
$this->filesPath = $filesPath;
|
||||||
if ($libraries !== NULL) {
|
if ($libraries !== NULL) {
|
||||||
$this->libraries = $libraries;
|
$this->libraries = $libraries;
|
||||||
}
|
}
|
||||||
|
@ -86,7 +87,7 @@ class H5PDevelopment {
|
||||||
$library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']);
|
$library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']);
|
||||||
$this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE);
|
$this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE);
|
||||||
|
|
||||||
$library['path'] = $libraryPath;
|
$library['path'] = 'development/' . $contents[$i];
|
||||||
$this->libraries[H5PDevelopment::libraryToString($library['machineName'], $library['majorVersion'], $library['minorVersion'])] = $library;
|
$this->libraries[H5PDevelopment::libraryToString($library['machineName'], $library['majorVersion'], $library['minorVersion'])] = $library;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,12 +140,10 @@ class H5PDevelopment {
|
||||||
*/
|
*/
|
||||||
public function getSemantics($name, $majorVersion, $minorVersion) {
|
public function getSemantics($name, $majorVersion, $minorVersion) {
|
||||||
$library = H5PDevelopment::libraryToString($name, $majorVersion, $minorVersion);
|
$library = H5PDevelopment::libraryToString($name, $majorVersion, $minorVersion);
|
||||||
|
|
||||||
if (isset($this->libraries[$library]) === FALSE) {
|
if (isset($this->libraries[$library]) === FALSE) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/semantics.json');
|
||||||
return $this->getFileContents($this->libraries[$library]['path'] . '/semantics.json');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -162,7 +161,7 @@ class H5PDevelopment {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getFileContents($this->libraries[$library]['path'] . '/language/' . $language . '.json');
|
return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/language/' . $language . '.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
209
h5p.classes.php
209
h5p.classes.php
|
@ -74,12 +74,6 @@ interface H5PFrameworkInterface {
|
||||||
*/
|
*/
|
||||||
public function getUploadedH5pFolderPath();
|
public function getUploadedH5pFolderPath();
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
* Path to the folder where all h5p files are stored
|
|
||||||
*/
|
|
||||||
public function getH5pPath();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the path to the last uploaded h5p file
|
* Get the path to the last uploaded h5p file
|
||||||
*
|
*
|
||||||
|
@ -253,6 +247,13 @@ interface H5PFrameworkInterface {
|
||||||
*/
|
*/
|
||||||
public function updateContent($content, $contentMainId = NULL);
|
public function updateContent($content, $contentMainId = NULL);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets marked user data for the given content.
|
||||||
|
*
|
||||||
|
* @param int $contentId
|
||||||
|
*/
|
||||||
|
public function resetContentUserData($contentId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save what libraries a library is dependending on
|
* Save what libraries a library is dependending on
|
||||||
*
|
*
|
||||||
|
@ -1247,7 +1248,7 @@ class H5PStorage {
|
||||||
* TRUE if one or more libraries were updated
|
* TRUE if one or more libraries were updated
|
||||||
* FALSE otherwise
|
* FALSE otherwise
|
||||||
*/
|
*/
|
||||||
public function savePackage($content = NULL, $contentMainId = NULL, $skipContent = FALSE) {
|
public function savePackage($content = NULL, $contentMainId = NULL, $skipContent = FALSE, $options = array()) {
|
||||||
if ($this->h5pF->mayUpdateLibraries()) {
|
if ($this->h5pF->mayUpdateLibraries()) {
|
||||||
// Save the libraries we processed during validation
|
// Save the libraries we processed during validation
|
||||||
$this->saveLibraries();
|
$this->saveLibraries();
|
||||||
|
@ -1275,10 +1276,14 @@ class H5PStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
$content['params'] = file_get_contents($current_path . DIRECTORY_SEPARATOR . 'content.json');
|
$content['params'] = file_get_contents($current_path . DIRECTORY_SEPARATOR . 'content.json');
|
||||||
|
|
||||||
|
if (isset($options['disable'])) {
|
||||||
|
$content['disable'] = $options['disable'];
|
||||||
|
}
|
||||||
$contentId = $this->h5pC->saveContent($content, $contentMainId);
|
$contentId = $this->h5pC->saveContent($content, $contentMainId);
|
||||||
$this->contentId = $contentId;
|
$this->contentId = $contentId;
|
||||||
|
|
||||||
$contents_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content';
|
$contents_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content';
|
||||||
if (!is_dir($contents_path)) {
|
if (!is_dir($contents_path)) {
|
||||||
mkdir($contents_path, 0777, true);
|
mkdir($contents_path, 0777, true);
|
||||||
}
|
}
|
||||||
|
@ -1306,7 +1311,7 @@ class H5PStorage {
|
||||||
$oldOnes = 0;
|
$oldOnes = 0;
|
||||||
|
|
||||||
// Find libraries directory and make sure it exists
|
// Find libraries directory and make sure it exists
|
||||||
$libraries_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'libraries';
|
$libraries_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'libraries';
|
||||||
if (!is_dir($libraries_path)) {
|
if (!is_dir($libraries_path)) {
|
||||||
mkdir($libraries_path, 0777, true);
|
mkdir($libraries_path, 0777, true);
|
||||||
}
|
}
|
||||||
|
@ -1404,8 +1409,9 @@ class H5PStorage {
|
||||||
* The content id
|
* The content id
|
||||||
*/
|
*/
|
||||||
public function deletePackage($contentId) {
|
public function deletePackage($contentId) {
|
||||||
H5PCore::deleteFileTree($this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId);
|
H5PCore::deleteFileTree($this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId);
|
||||||
$this->h5pF->deleteContentData($contentId);
|
$this->h5pF->deleteContentData($contentId);
|
||||||
|
// TODO: Delete export?
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1419,9 +1425,9 @@ class H5PStorage {
|
||||||
* TRUE if one or more libraries were updated
|
* TRUE if one or more libraries were updated
|
||||||
* FALSE otherwise
|
* FALSE otherwise
|
||||||
*/
|
*/
|
||||||
public function updatePackage($contentId, $contentMainId = NULL) {
|
public function updatePackage($contentId, $contentMainId = NULL, $options) {
|
||||||
$this->deletePackage($contentId);
|
$this->deletePackage($contentId);
|
||||||
return $this->savePackage($contentId, $contentMainId);
|
return $this->savePackage($contentId, $contentMainId, FALSE, $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1438,8 +1444,8 @@ class H5PStorage {
|
||||||
* The main id of the new content (used in frameworks that support revisioning)
|
* The main id of the new content (used in frameworks that support revisioning)
|
||||||
*/
|
*/
|
||||||
public function copyPackage($contentId, $copyFromId, $contentMainId = NULL) {
|
public function copyPackage($contentId, $copyFromId, $contentMainId = NULL) {
|
||||||
$source_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $copyFromId;
|
$source_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $copyFromId;
|
||||||
$destination_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId;
|
$destination_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId;
|
||||||
$this->h5pC->copyFileTree($source_path, $destination_path);
|
$this->h5pC->copyFileTree($source_path, $destination_path);
|
||||||
|
|
||||||
$this->h5pF->copyLibraryUsage($contentId, $copyFromId, $contentMainId);
|
$this->h5pF->copyLibraryUsage($contentId, $copyFromId, $contentMainId);
|
||||||
|
@ -1475,7 +1481,7 @@ Class H5PExport {
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function createExportFile($content) {
|
public function createExportFile($content) {
|
||||||
$h5pDir = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR;
|
$h5pDir = $this->h5pC->path . DIRECTORY_SEPARATOR;
|
||||||
$tempPath = $h5pDir . 'temp' . DIRECTORY_SEPARATOR . $content['id'];
|
$tempPath = $h5pDir . 'temp' . DIRECTORY_SEPARATOR . $content['id'];
|
||||||
$zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $content['slug'] . '-' . $content['id'] . '.h5p';
|
$zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $content['slug'] . '-' . $content['id'] . '.h5p';
|
||||||
|
|
||||||
|
@ -1495,8 +1501,6 @@ Class H5PExport {
|
||||||
// Build h5p.json
|
// Build h5p.json
|
||||||
$h5pJson = array (
|
$h5pJson = array (
|
||||||
'title' => $content['title'],
|
'title' => $content['title'],
|
||||||
// TODO - stop using 'und', this is not the preferred way.
|
|
||||||
// Either remove language from the json if not existing, or use "language": null
|
|
||||||
'language' => (isset($content['language']) && strlen(trim($content['language'])) !== 0) ? $content['language'] : 'und',
|
'language' => (isset($content['language']) && strlen(trim($content['language'])) !== 0) ? $content['language'] : 'und',
|
||||||
'mainLibrary' => $content['library']['name'],
|
'mainLibrary' => $content['library']['name'],
|
||||||
'embedTypes' => $embedTypes,
|
'embedTypes' => $embedTypes,
|
||||||
|
@ -1507,7 +1511,7 @@ Class H5PExport {
|
||||||
$library = $dependency['library'];
|
$library = $dependency['library'];
|
||||||
|
|
||||||
// Copy library to h5p
|
// Copy library to h5p
|
||||||
$source = isset($library['path']) ? $library['path'] : $h5pDir . 'libraries' . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE);
|
$source = $h5pDir . (isset($library['path']) ? $library['path'] : 'libraries' . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE));
|
||||||
$destination = $tempPath . DIRECTORY_SEPARATOR . $library['machineName'];
|
$destination = $tempPath . DIRECTORY_SEPARATOR . $library['machineName'];
|
||||||
$this->h5pC->copyFileTree($source, $destination);
|
$this->h5pC->copyFileTree($source, $destination);
|
||||||
|
|
||||||
|
@ -1527,28 +1531,52 @@ Class H5PExport {
|
||||||
$results = print_r(json_encode($h5pJson), true);
|
$results = print_r(json_encode($h5pJson), true);
|
||||||
file_put_contents($tempPath . DIRECTORY_SEPARATOR . 'h5p.json', $results);
|
file_put_contents($tempPath . DIRECTORY_SEPARATOR . 'h5p.json', $results);
|
||||||
|
|
||||||
|
// Get a complete file list from our tmp dir
|
||||||
|
$files = array();
|
||||||
|
self::populateFileList($tempPath, $files);
|
||||||
|
|
||||||
// Create new zip instance.
|
// Create new zip instance.
|
||||||
$zip = new ZipArchive();
|
$zip = new ZipArchive();
|
||||||
$zip->open($zipPath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE);
|
$zip->open($zipPath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE);
|
||||||
|
|
||||||
// Get all files and folders in $tempPath
|
// Add all the files from the tmp dir.
|
||||||
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($tempPath . DIRECTORY_SEPARATOR));
|
foreach ($files as $file) {
|
||||||
// Add files to zip
|
// Please note that the zip format has no concept of folders, we must
|
||||||
foreach ($iterator as $key => $value) {
|
// use forward slashes to separate our directories.
|
||||||
$test = '.';
|
$zip->addFile($file->absolutePath, $file->relativePath);
|
||||||
// Do not add the folders '.' and '..' to the zip. This will make zip invalid.
|
|
||||||
if (substr_compare($key, $test, -strlen($test), strlen($test)) !== 0) {
|
|
||||||
// Get files path in $tempPath
|
|
||||||
$filePath = explode($tempPath . DIRECTORY_SEPARATOR, $key);
|
|
||||||
// Add files to the zip with the intended file-structure
|
|
||||||
$zip->addFile($key, $filePath[1]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close zip and remove temp dir
|
// Close zip and remove temp dir
|
||||||
$zip->close();
|
$zip->close();
|
||||||
H5PCore::deleteFileTree($tempPath);
|
H5PCore::deleteFileTree($tempPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive function the will add the files of the given directory to the
|
||||||
|
* given files list. All files are objects with an absolute path and
|
||||||
|
* a relative path. The relative path is forward slashes only! Great for
|
||||||
|
* use in zip files and URLs.
|
||||||
|
*
|
||||||
|
* @param string $dir path
|
||||||
|
* @param array $files list
|
||||||
|
* @param string $relative prefix. Optional
|
||||||
|
*/
|
||||||
|
private static function populateFileList($dir, &$files, $relative = '') {
|
||||||
|
$strip = strlen($dir) + 1;
|
||||||
|
foreach (glob($dir . DIRECTORY_SEPARATOR . '*') as $file) {
|
||||||
|
$rel = $relative . substr($file, $strip);
|
||||||
|
if (is_dir($file)) {
|
||||||
|
self::populateFileList($file, $files, $rel . '/');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$files[] = (object) array(
|
||||||
|
'absolutePath' => $file,
|
||||||
|
'relativePath' => $rel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete .h5p file
|
* Delete .h5p file
|
||||||
*
|
*
|
||||||
|
@ -1556,7 +1584,7 @@ Class H5PExport {
|
||||||
* Identifier for the H5P
|
* Identifier for the H5P
|
||||||
*/
|
*/
|
||||||
public function deleteExport($contentId) {
|
public function deleteExport($contentId) {
|
||||||
$h5pDir = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR;
|
$h5pDir = $this->h5pC->path . DIRECTORY_SEPARATOR;
|
||||||
$zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $contentId . '.h5p';
|
$zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $contentId . '.h5p';
|
||||||
if (file_exists($zipPath)) {
|
if (file_exists($zipPath)) {
|
||||||
unlink($zipPath);
|
unlink($zipPath);
|
||||||
|
@ -1590,7 +1618,7 @@ class H5PCore {
|
||||||
|
|
||||||
public static $coreApi = array(
|
public static $coreApi = array(
|
||||||
'majorVersion' => 1,
|
'majorVersion' => 1,
|
||||||
'minorVersion' => 4
|
'minorVersion' => 5
|
||||||
);
|
);
|
||||||
public static $styles = array(
|
public static $styles = array(
|
||||||
'styles/h5p.css',
|
'styles/h5p.css',
|
||||||
|
@ -1615,6 +1643,22 @@ class H5PCore {
|
||||||
|
|
||||||
private $exportEnabled;
|
private $exportEnabled;
|
||||||
|
|
||||||
|
// Disable flags
|
||||||
|
const DISABLE_NONE = 0;
|
||||||
|
const DISABLE_FRAME = 1;
|
||||||
|
const DISABLE_DOWNLOAD = 2;
|
||||||
|
const DISABLE_EMBED = 4;
|
||||||
|
const DISABLE_COPYRIGHT = 8;
|
||||||
|
const DISABLE_ABOUT = 16;
|
||||||
|
|
||||||
|
// Map flags to string
|
||||||
|
public static $disable = array(
|
||||||
|
self::DISABLE_FRAME => 'frame',
|
||||||
|
self::DISABLE_DOWNLOAD => 'download',
|
||||||
|
self::DISABLE_EMBED => 'embed',
|
||||||
|
self::DISABLE_COPYRIGHT => 'copyright'
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for the H5PCore
|
* Constructor for the H5PCore
|
||||||
*
|
*
|
||||||
|
@ -1625,16 +1669,17 @@ class H5PCore {
|
||||||
* @param boolean $export enabled?
|
* @param boolean $export enabled?
|
||||||
* @param int $development_mode mode.
|
* @param int $development_mode mode.
|
||||||
*/
|
*/
|
||||||
public function __construct($H5PFramework, $path, $language = 'en', $export = FALSE, $development_mode = H5PDevelopment::MODE_NONE) {
|
public function __construct($H5PFramework, $path, $url, $language = 'en', $export = FALSE, $development_mode = H5PDevelopment::MODE_NONE) {
|
||||||
$this->h5pF = $H5PFramework;
|
$this->h5pF = $H5PFramework;
|
||||||
|
|
||||||
$this->h5pF = $H5PFramework;
|
$this->h5pF = $H5PFramework;
|
||||||
$this->path = $path;
|
$this->path = $path;
|
||||||
|
$this->url = $url;
|
||||||
$this->exportEnabled = $export;
|
$this->exportEnabled = $export;
|
||||||
$this->development_mode = $development_mode;
|
$this->development_mode = $development_mode;
|
||||||
|
|
||||||
if ($development_mode & H5PDevelopment::MODE_LIBRARY) {
|
if ($development_mode & H5PDevelopment::MODE_LIBRARY) {
|
||||||
$this->h5pD = new H5PDevelopment($this->h5pF, $path, $language);
|
$this->h5pD = new H5PDevelopment($this->h5pF, $path . '/', $language);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1652,6 +1697,9 @@ class H5PCore {
|
||||||
$content['id'] = $this->h5pF->insertContent($content, $contentMainId);
|
$content['id'] = $this->h5pF->insertContent($content, $contentMainId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some user data for content has to be reset when the content changes.
|
||||||
|
$this->h5pF->resetContentUserData($contentMainId ? $contentMainId : $content['id']);
|
||||||
|
|
||||||
return $content['id'];
|
return $content['id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1735,8 +1783,6 @@ class H5PCore {
|
||||||
// Recreate export file
|
// Recreate export file
|
||||||
$exporter = new H5PExport($this->h5pF, $this);
|
$exporter = new H5PExport($this->h5pF, $this);
|
||||||
$exporter->createExportFile($content);
|
$exporter->createExportFile($content);
|
||||||
|
|
||||||
// TODO: Should we rather create the file once first accessed, like imagecache?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache.
|
// Cache.
|
||||||
|
@ -1805,8 +1851,9 @@ class H5PCore {
|
||||||
* @param array $dependency
|
* @param array $dependency
|
||||||
* @param string $type
|
* @param string $type
|
||||||
* @param array $assets
|
* @param array $assets
|
||||||
|
* @param string $prefix Optional. Make paths relative to another dir.
|
||||||
*/
|
*/
|
||||||
private function getDependencyAssets($dependency, $type, &$assets) {
|
private function getDependencyAssets($dependency, $type, &$assets, $prefix = '') {
|
||||||
// Check if dependency has any files of this type
|
// Check if dependency has any files of this type
|
||||||
if (empty($dependency[$type]) || $dependency[$type][0] === '') {
|
if (empty($dependency[$type]) || $dependency[$type][0] === '') {
|
||||||
return;
|
return;
|
||||||
|
@ -1816,10 +1863,9 @@ class H5PCore {
|
||||||
if ($type === 'preloadedCss' && (isset($dependency['dropCss']) && $dependency['dropCss'] === '1')) {
|
if ($type === 'preloadedCss' && (isset($dependency['dropCss']) && $dependency['dropCss'] === '1')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($dependency[$type] as $file) {
|
foreach ($dependency[$type] as $file) {
|
||||||
$assets[] = (object) array(
|
$assets[] = (object) array(
|
||||||
'path' => $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file),
|
'path' => $prefix . '/' . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file),
|
||||||
'version' => $dependency['version']
|
'version' => $dependency['version']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1835,7 +1881,19 @@ class H5PCore {
|
||||||
$urls = array();
|
$urls = array();
|
||||||
|
|
||||||
foreach ($assets as $asset) {
|
foreach ($assets as $asset) {
|
||||||
$urls[] = $asset->path . $asset->version;
|
$url = $asset->path;
|
||||||
|
|
||||||
|
// Add URL prefix if not external
|
||||||
|
if (strpos($asset->path, '://') === FALSE) {
|
||||||
|
$url = $this->url . $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add version/cache buster if set
|
||||||
|
if (isset($asset->version)) {
|
||||||
|
$url .= $asset->version;
|
||||||
|
}
|
||||||
|
|
||||||
|
$urls[] = $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $urls;
|
return $urls;
|
||||||
|
@ -1845,23 +1903,23 @@ class H5PCore {
|
||||||
* Return file paths for all dependecies files.
|
* Return file paths for all dependecies files.
|
||||||
*
|
*
|
||||||
* @param array $dependencies
|
* @param array $dependencies
|
||||||
|
* @param string $prefix Optional. Make paths relative to another dir.
|
||||||
* @return array files.
|
* @return array files.
|
||||||
*/
|
*/
|
||||||
public function getDependenciesFiles($dependencies) {
|
public function getDependenciesFiles($dependencies, $prefix = '') {
|
||||||
$files = array(
|
$files = array(
|
||||||
'scripts' => array(),
|
'scripts' => array(),
|
||||||
'styles' => array()
|
'styles' => array()
|
||||||
);
|
);
|
||||||
foreach ($dependencies as $dependency) {
|
foreach ($dependencies as $dependency) {
|
||||||
if (isset($dependency['path']) === FALSE) {
|
if (isset($dependency['path']) === FALSE) {
|
||||||
$dependency['path'] = $this->path . '/libraries/' . H5PCore::libraryToString($dependency, TRUE);
|
$dependency['path'] = 'libraries/' . H5PCore::libraryToString($dependency, TRUE);
|
||||||
$dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']);
|
$dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']);
|
||||||
$dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']);
|
$dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$dependency['version'] = "?ver={$dependency['majorVersion']}.{$dependency['minorVersion']}.{$dependency['patchVersion']}";
|
$dependency['version'] = "?ver={$dependency['majorVersion']}.{$dependency['minorVersion']}.{$dependency['patchVersion']}";
|
||||||
$this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts']);
|
$this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts'], $prefix);
|
||||||
$this->getDependencyAssets($dependency, 'preloadedCss', $files['styles']);
|
$this->getDependencyAssets($dependency, 'preloadedCss', $files['styles'], $prefix);
|
||||||
}
|
}
|
||||||
return $files;
|
return $files;
|
||||||
}
|
}
|
||||||
|
@ -2271,8 +2329,6 @@ class H5PCore {
|
||||||
/**
|
/**
|
||||||
* Helper function for creating markup for the unsupported libraries list
|
* Helper function for creating markup for the unsupported libraries list
|
||||||
*
|
*
|
||||||
* TODO: Make help text translatable
|
|
||||||
*
|
|
||||||
* @return string Html
|
* @return string Html
|
||||||
* */
|
* */
|
||||||
public function createMarkupForUnsupportedLibraryList($libraries) {
|
public function createMarkupForUnsupportedLibraryList($libraries) {
|
||||||
|
@ -2309,11 +2365,62 @@ class H5PCore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if($platformInfo['uuid'] === '' && isset($json->uuid)) {
|
if($platformInfo['uuid'] === '' && isset($json->uuid)) {
|
||||||
$this->h5pF->setOption('h5p_site_uuid', $json->uuid);
|
$this->h5pF->setOption('site_uuid', $json->uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getGlobalDisable() {
|
||||||
|
$disable = self::DISABLE_NONE;
|
||||||
|
|
||||||
|
// Allow global settings to override and disable options
|
||||||
|
if (!$this->h5pF->getOption('frame', TRUE)) {
|
||||||
|
$disable |= self::DISABLE_FRAME;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!$this->h5pF->getOption('export', TRUE)) {
|
||||||
|
$disable |= self::DISABLE_DOWNLOAD;
|
||||||
|
}
|
||||||
|
if (!$this->h5pF->getOption('embed', TRUE)) {
|
||||||
|
$disable |= self::DISABLE_EMBED;
|
||||||
|
}
|
||||||
|
if (!$this->h5pF->getOption('copyright', TRUE)) {
|
||||||
|
$disable |= self::DISABLE_COPYRIGHT;
|
||||||
|
}
|
||||||
|
if (!$this->h5pF->getOption('icon', TRUE)) {
|
||||||
|
$disable |= self::DISABLE_ABOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine disable state from sources.
|
||||||
|
*
|
||||||
|
* @param array $sources
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function getDisable(&$sources) {
|
||||||
|
$disable = H5PCore::DISABLE_NONE;
|
||||||
|
if (!isset($sources['frame']) || !$sources['frame']) {
|
||||||
|
$disable |= H5PCore::DISABLE_FRAME;
|
||||||
|
}
|
||||||
|
if (!isset($sources['download']) || !$sources['download']) {
|
||||||
|
$disable |= H5PCore::DISABLE_DOWNLOAD;
|
||||||
|
}
|
||||||
|
if (!isset($sources['copyright']) || !$sources['copyright']) {
|
||||||
|
$disable |= H5PCore::DISABLE_COPYRIGHT;
|
||||||
|
}
|
||||||
|
if (!isset($sources['embed']) || !$sources['embed']) {
|
||||||
|
$disable |= H5PCore::DISABLE_EMBED;
|
||||||
|
}
|
||||||
|
if (!isset($sources['about']) || !$sources['about']) {
|
||||||
|
$disable |= H5PCore::DISABLE_ABOUT;
|
||||||
|
}
|
||||||
|
return $disable;
|
||||||
|
}
|
||||||
|
|
||||||
// Cache for getting library ids
|
// Cache for getting library ids
|
||||||
private $libraryIdMap = array();
|
private $libraryIdMap = array();
|
||||||
|
|
||||||
|
@ -2412,7 +2519,6 @@ class H5PContentValidator {
|
||||||
|
|
||||||
// Keep track of the libraries we load to avoid loading it multiple times.
|
// Keep track of the libraries we load to avoid loading it multiple times.
|
||||||
$this->libraries = array();
|
$this->libraries = array();
|
||||||
// TODO: Should this possible be done in core's loadLibrary? This might be done multiple places.
|
|
||||||
|
|
||||||
// Keep track of all dependencies for the given content.
|
// Keep track of all dependencies for the given content.
|
||||||
$this->dependencies = array();
|
$this->dependencies = array();
|
||||||
|
@ -2801,11 +2907,14 @@ class H5PContentValidator {
|
||||||
'type' => 'group',
|
'type' => 'group',
|
||||||
'fields' => $library['semantics'],
|
'fields' => $library['semantics'],
|
||||||
), FALSE);
|
), FALSE);
|
||||||
$validkeys = array('library', 'params');
|
$validkeys = array('library', 'params', 'subContentId');
|
||||||
if (isset($semantics->extraAttributes)) {
|
if (isset($semantics->extraAttributes)) {
|
||||||
$validkeys = array_merge($validkeys, $semantics->extraAttributes);
|
$validkeys = array_merge($validkeys, $semantics->extraAttributes);
|
||||||
}
|
}
|
||||||
$this->filterParams($value, $validkeys);
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
// Find all dependencies for this library
|
// Find all dependencies for this library
|
||||||
$depkey = 'preloaded-' . $library['machineName'];
|
$depkey = 'preloaded-' . $library['machineName'];
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
(function ($) {
|
||||||
|
$(document).ready(function () {
|
||||||
|
var $inputs = $('.h5p-action-bar-settings input');
|
||||||
|
var $frame = $inputs.filter('input[name="frame"], input[name="h5p_frame"]');
|
||||||
|
var $others = $inputs.filter(':not(input[name="frame"], input[name="h5p_frame"])');
|
||||||
|
|
||||||
|
var toggle = function () {
|
||||||
|
if ($frame.is(':checked')) {
|
||||||
|
$others.attr('disabled', false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$others.attr('disabled', true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$frame.change(toggle);
|
||||||
|
toggle();
|
||||||
|
});
|
||||||
|
})(jQuery);
|
|
@ -0,0 +1,282 @@
|
||||||
|
/*jshint -W083 */
|
||||||
|
var H5PUpgrades = H5PUpgrades || {};
|
||||||
|
|
||||||
|
H5P.ContentUpgradeProcess = (function (Version) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
* @namespace H5P
|
||||||
|
*/
|
||||||
|
function ContentUpgradeProcess(name, oldVersion, newVersion, params, id, loadLibrary, done) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Make params possible to work with
|
||||||
|
try {
|
||||||
|
params = JSON.parse(params);
|
||||||
|
if (!(params instanceof Object)) {
|
||||||
|
throw true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (event) {
|
||||||
|
return done({
|
||||||
|
type: 'errorParamsBroken',
|
||||||
|
id: id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.loadLibrary = loadLibrary;
|
||||||
|
self.upgrade(name, oldVersion, newVersion, params, function (err, result) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
done(null, JSON.stringify(params));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
ContentUpgradeProcess.prototype.upgrade = function (name, oldVersion, newVersion, params, done) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Load library details and upgrade routines
|
||||||
|
self.loadLibrary(name, newVersion, function (err, library) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run upgrade routines on params
|
||||||
|
self.processParams(library, oldVersion, newVersion, params, function (err, params) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any of the sub-libraries need upgrading
|
||||||
|
asyncSerial(library.semantics, function (index, field, next) {
|
||||||
|
self.processField(field, params[field.name], function (err, upgradedParams) {
|
||||||
|
if (upgradedParams) {
|
||||||
|
params[field.name] = upgradedParams;
|
||||||
|
}
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
}, function (err) {
|
||||||
|
done(err, params);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run upgrade hooks on params.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {Object} library
|
||||||
|
* @param {Version} oldVersion
|
||||||
|
* @param {Version} newVersion
|
||||||
|
* @param {Object} params
|
||||||
|
* @param {Function} next
|
||||||
|
*/
|
||||||
|
ContentUpgradeProcess.prototype.processParams = function (library, oldVersion, newVersion, params, next) {
|
||||||
|
if (H5PUpgrades[library.name] === undefined) {
|
||||||
|
if (library.upgradesScript) {
|
||||||
|
// Upgrades script should be loaded so the upgrades should be here.
|
||||||
|
return next({
|
||||||
|
type: 'scriptMissing',
|
||||||
|
library: library.name + ' ' + newVersion
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// No upgrades script. Move on
|
||||||
|
return next(null, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run upgrade hooks. Start by going through major versions
|
||||||
|
asyncSerial(H5PUpgrades[library.name], function (major, minors, nextMajor) {
|
||||||
|
if (major < oldVersion.major || major > newVersion.major) {
|
||||||
|
// Older than the current version or newer than the selected
|
||||||
|
nextMajor();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Go through the minor versions for this major version
|
||||||
|
asyncSerial(minors, function (minor, upgrade, nextMinor) {
|
||||||
|
if (minor <= oldVersion.minor || minor > newVersion.minor) {
|
||||||
|
// Older than or equal to the current version or newer than the selected
|
||||||
|
nextMinor();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We found an upgrade hook, run it
|
||||||
|
var unnecessaryWrapper = (upgrade.contentUpgrade !== undefined ? upgrade.contentUpgrade : upgrade);
|
||||||
|
|
||||||
|
try {
|
||||||
|
unnecessaryWrapper(params, function (err, upgradedParams) {
|
||||||
|
params = upgradedParams;
|
||||||
|
nextMinor(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
if (console && console.log) {
|
||||||
|
console.log("Error", err.stack);
|
||||||
|
console.log("Error", err.name);
|
||||||
|
console.log("Error", err.message);
|
||||||
|
}
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, nextMajor);
|
||||||
|
}
|
||||||
|
}, function (err) {
|
||||||
|
next(err, params);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process parameter fields to find and upgrade sub-libraries.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {Object} field
|
||||||
|
* @param {Object} params
|
||||||
|
* @param {Function} done
|
||||||
|
*/
|
||||||
|
ContentUpgradeProcess.prototype.processField = function (field, params, done) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (params === undefined) {
|
||||||
|
return done();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (field.type) {
|
||||||
|
case 'library':
|
||||||
|
if (params.library === undefined || params.params === undefined) {
|
||||||
|
return done();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
if (availableLib[0] === usedLib[0]) {
|
||||||
|
if (availableLib[1] === usedLib[1]) {
|
||||||
|
return done(); // Same version
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have different versions
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// A newer version is available, upgrade params
|
||||||
|
return self.upgrade(availableLib[0], usedVer, availableVer, params.params, function (err, upgraded) {
|
||||||
|
if (!err) {
|
||||||
|
params.library = availableLib[0] + ' ' + availableVer.major + '.' + availableVer.minor;
|
||||||
|
params.params = upgraded;
|
||||||
|
}
|
||||||
|
done(err, params);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'group':
|
||||||
|
if (field.fields.length === 1) {
|
||||||
|
// Single field to process, wrapper will be skipped
|
||||||
|
self.processField(field.fields[0], params, function (err, upgradedParams) {
|
||||||
|
if (upgradedParams) {
|
||||||
|
params = upgradedParams;
|
||||||
|
}
|
||||||
|
done(err, params);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Go through all fields in the group
|
||||||
|
asyncSerial(field.fields, function (index, subField, next) {
|
||||||
|
var paramsToProcess = params ? params[subField.name] : null;
|
||||||
|
self.processField(subField, paramsToProcess, function (err, upgradedParams) {
|
||||||
|
if (upgradedParams) {
|
||||||
|
params[subField.name] = upgradedParams;
|
||||||
|
}
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
}, function (err) {
|
||||||
|
done(err, params);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'list':
|
||||||
|
// Go trough all params in the list
|
||||||
|
asyncSerial(params, function (index, subParams, next) {
|
||||||
|
self.processField(field.field, subParams, function (err, upgradedParams) {
|
||||||
|
if (upgradedParams) {
|
||||||
|
params[index] = upgradedParams;
|
||||||
|
}
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
}, function (err) {
|
||||||
|
done(err, params);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helps process each property on the given object asynchronously in serial order.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Function} process
|
||||||
|
* @param {Function} finished
|
||||||
|
*/
|
||||||
|
var asyncSerial = function (obj, process, finished) {
|
||||||
|
var id, isArray = obj instanceof Array;
|
||||||
|
|
||||||
|
// Keep track of each property that belongs to this object.
|
||||||
|
if (!isArray) {
|
||||||
|
var ids = [];
|
||||||
|
for (id in obj) {
|
||||||
|
if (obj.hasOwnProperty(id)) {
|
||||||
|
ids.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = -1; // Keeps track of the current property
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private. Process the next property
|
||||||
|
*/
|
||||||
|
var next = function () {
|
||||||
|
id = isArray ? i : ids[i];
|
||||||
|
process(id, obj[id], check);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private. Check if we're done or have an error.
|
||||||
|
*
|
||||||
|
* @param {String} err
|
||||||
|
*/
|
||||||
|
var check = function (err) {
|
||||||
|
// We need to use a real async function in order for the stack to clear.
|
||||||
|
setTimeout(function () {
|
||||||
|
i++;
|
||||||
|
if (i === (isArray ? obj.length : ids.length) || (err !== undefined && err !== null)) {
|
||||||
|
finished(err);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
check(); // Start
|
||||||
|
};
|
||||||
|
|
||||||
|
return ContentUpgradeProcess;
|
||||||
|
})(H5P.Version);
|
|
@ -0,0 +1,62 @@
|
||||||
|
var H5P = H5P || {};
|
||||||
|
importScripts('h5p-version.js', 'h5p-content-upgrade-process.js');
|
||||||
|
|
||||||
|
var libraryLoadedCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register message handlers
|
||||||
|
*/
|
||||||
|
var messageHandlers = {
|
||||||
|
newJob: function (job) {
|
||||||
|
// Start new job
|
||||||
|
new H5P.ContentUpgradeProcess(job.name, new H5P.Version(job.oldVersion), new H5P.Version(job.newVersion), job.params, job.id, function loadLibrary(name, version, next) {
|
||||||
|
// TODO: Cache?
|
||||||
|
postMessage({
|
||||||
|
action: 'loadLibrary',
|
||||||
|
name: name,
|
||||||
|
version: version.toString()
|
||||||
|
});
|
||||||
|
libraryLoadedCallback = next;
|
||||||
|
}, function done(err, result) {
|
||||||
|
if (err) {
|
||||||
|
// Return error
|
||||||
|
postMessage({
|
||||||
|
action: 'error',
|
||||||
|
id: job.id,
|
||||||
|
err: err.message ? err.message : err
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return upgraded content
|
||||||
|
postMessage({
|
||||||
|
action: 'done',
|
||||||
|
id: job.id,
|
||||||
|
params: result
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
libraryLoaded: function (data) {
|
||||||
|
var library = data.library;
|
||||||
|
if (library.upgradesScript) {
|
||||||
|
try {
|
||||||
|
importScripts(library.upgradesScript);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
libraryLoadedCallback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
libraryLoadedCallback(null, data.library);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle messages from our master
|
||||||
|
*/
|
||||||
|
onmessage = function (event) {
|
||||||
|
if (event.data.action !== undefined && messageHandlers[event.data.action]) {
|
||||||
|
messageHandlers[event.data.action].call(this, event.data);
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,8 +1,7 @@
|
||||||
/*jshint -W083 */
|
/*jshint -W083 */
|
||||||
var H5PUpgrades = H5PUpgrades || {};
|
|
||||||
|
|
||||||
(function ($) {
|
(function ($, Version) {
|
||||||
var info, $container, librariesCache = {};
|
var info, $container, librariesCache = {}, scriptsCache = {};
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
@ -43,87 +42,6 @@ var H5PUpgrades = H5PUpgrades || {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Private. Helps process each property on the given object asynchronously in serial order.
|
|
||||||
*
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Function} process
|
|
||||||
* @param {Function} finished
|
|
||||||
*/
|
|
||||||
var asyncSerial = function (obj, process, finished) {
|
|
||||||
var id, isArray = obj instanceof Array;
|
|
||||||
|
|
||||||
// Keep track of each property that belongs to this object.
|
|
||||||
if (!isArray) {
|
|
||||||
var ids = [];
|
|
||||||
for (id in obj) {
|
|
||||||
if (obj.hasOwnProperty(id)) {
|
|
||||||
ids.push(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var i = -1; // Keeps track of the current property
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private. Process the next property
|
|
||||||
*/
|
|
||||||
var next = function () {
|
|
||||||
id = isArray ? i : ids[i];
|
|
||||||
process(id, obj[id], check);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private. Check if we're done or have an error.
|
|
||||||
*
|
|
||||||
* @param {String} err
|
|
||||||
*/
|
|
||||||
var check = function (err) {
|
|
||||||
// We need to use a real async function in order for the stack to clear.
|
|
||||||
setTimeout(function () {
|
|
||||||
i++;
|
|
||||||
if (i === (isArray ? obj.length : ids.length) || (err !== undefined && err !== null)) {
|
|
||||||
finished(err);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
check(); // Start
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make it easy to keep track of version details.
|
|
||||||
*
|
|
||||||
* @param {String} version
|
|
||||||
* @param {Number} libraryId
|
|
||||||
* @returns {_L1.Version}
|
|
||||||
*/
|
|
||||||
function Version(version, libraryId) {
|
|
||||||
if (libraryId !== undefined) {
|
|
||||||
version = info.versions[libraryId];
|
|
||||||
|
|
||||||
// Public
|
|
||||||
this.libraryId = libraryId;
|
|
||||||
}
|
|
||||||
var versionSplit = version.split('.', 3);
|
|
||||||
|
|
||||||
// Public
|
|
||||||
this.major = versionSplit[0];
|
|
||||||
this.minor = versionSplit[1];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Public. Custom string for this object.
|
|
||||||
*
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
this.toString = function () {
|
|
||||||
return version;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a throbber in the status field.
|
* Displays a throbber in the status field.
|
||||||
*
|
*
|
||||||
|
@ -154,18 +72,84 @@ var H5PUpgrades = H5PUpgrades || {};
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Get selected version
|
// Get selected version
|
||||||
self.version = new Version(null, libraryId);
|
self.version = new Version(info.versions[libraryId]);
|
||||||
|
self.version.libraryId = libraryId;
|
||||||
|
|
||||||
// Create throbber with loading text and progress
|
// Create throbber with loading text and progress
|
||||||
self.throbber = new Throbber(info.inProgress.replace('%ver', self.version));
|
self.throbber = new Throbber(info.inProgress.replace('%ver', self.version));
|
||||||
|
|
||||||
// Get the next batch
|
self.started = new Date().getTime();
|
||||||
self.nextBatch({
|
self.io = 0;
|
||||||
libraryId: libraryId,
|
|
||||||
token: info.token
|
// Track number of working
|
||||||
});
|
self.working = 0;
|
||||||
|
|
||||||
|
var start = function () {
|
||||||
|
// Get the next batch
|
||||||
|
self.nextBatch({
|
||||||
|
libraryId: libraryId,
|
||||||
|
token: info.token
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (window.Worker !== undefined) {
|
||||||
|
// Prepare our workers
|
||||||
|
self.initWorkers();
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// No workers, do the job ourselves
|
||||||
|
self.loadScript(info.scriptBaseUrl + '/h5p-content-upgrade-process.js' + info.buster, start);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize workers
|
||||||
|
*/
|
||||||
|
ContentUpgrade.prototype.initWorkers = function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Determine number of workers (defaults to 4)
|
||||||
|
var numWorkers = (window.navigator !== undefined && window.navigator.hardwareConcurrency ? window.navigator.hardwareConcurrency : 4);
|
||||||
|
self.workers = new Array(numWorkers);
|
||||||
|
|
||||||
|
// Register message handlers
|
||||||
|
var messageHandlers = {
|
||||||
|
done: function (result) {
|
||||||
|
self.workDone(result.id, result.params, this);
|
||||||
|
},
|
||||||
|
error: function (error) {
|
||||||
|
self.printError(error.err);
|
||||||
|
|
||||||
|
// Stop everything
|
||||||
|
self.terminate();
|
||||||
|
},
|
||||||
|
loadLibrary: function (details) {
|
||||||
|
var worker = this;
|
||||||
|
self.loadLibrary(details.name, new Version(details.version), function (err, library) {
|
||||||
|
if (err) {
|
||||||
|
// Reset worker?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.postMessage({
|
||||||
|
action: 'libraryLoaded',
|
||||||
|
library: library
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < numWorkers; i++) {
|
||||||
|
self.workers[i] = new Worker(info.scriptBaseUrl + '/h5p-content-upgrade-worker.js' + info.buster);
|
||||||
|
self.workers[i].onmessage = function (event) {
|
||||||
|
if (event.data.action !== undefined && messageHandlers[event.data.action]) {
|
||||||
|
messageHandlers[event.data.action].call(this, event.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the next batch and start processing it.
|
* Get the next batch and start processing it.
|
||||||
*
|
*
|
||||||
|
@ -174,12 +158,24 @@ var H5PUpgrades = H5PUpgrades || {};
|
||||||
ContentUpgrade.prototype.nextBatch = function (outData) {
|
ContentUpgrade.prototype.nextBatch = function (outData) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
// Track time spent on IO
|
||||||
|
var start = new Date().getTime();
|
||||||
$.post(info.infoUrl, outData, function (inData) {
|
$.post(info.infoUrl, outData, function (inData) {
|
||||||
|
self.io += new Date().getTime() - start;
|
||||||
if (!(inData instanceof Object)) {
|
if (!(inData instanceof Object)) {
|
||||||
// Print errors from backend
|
// Print errors from backend
|
||||||
return self.setStatus(inData);
|
return self.setStatus(inData);
|
||||||
}
|
}
|
||||||
if (inData.left === 0) {
|
if (inData.left === 0) {
|
||||||
|
var total = new Date().getTime() - self.started;
|
||||||
|
|
||||||
|
if (window.console && console.log) {
|
||||||
|
console.log('The upgrade process took ' + (total / 1000) + ' seconds. (' + (Math.round((self.io / (total / 100)) * 100) / 100) + ' % IO)' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminate workers
|
||||||
|
self.terminate();
|
||||||
|
|
||||||
// Nothing left to process
|
// Nothing left to process
|
||||||
return self.setStatus(info.done);
|
return self.setStatus(info.done);
|
||||||
}
|
}
|
||||||
|
@ -208,90 +204,125 @@ var H5PUpgrades = H5PUpgrades || {};
|
||||||
*/
|
*/
|
||||||
ContentUpgrade.prototype.processBatch = function (parameters) {
|
ContentUpgrade.prototype.processBatch = function (parameters) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var upgraded = {}; // Track upgraded params
|
|
||||||
|
|
||||||
var current = 0; // Track progress
|
// Track upgraded params
|
||||||
asyncSerial(parameters, function (id, params, next) {
|
self.upgraded = {};
|
||||||
|
|
||||||
try {
|
// Track current batch
|
||||||
// Make params possible to work with
|
self.parameters = parameters;
|
||||||
params = JSON.parse(params);
|
|
||||||
if (!(params instanceof Object)) {
|
// Create id mapping
|
||||||
throw true;
|
self.ids = [];
|
||||||
}
|
for (var id in parameters) {
|
||||||
|
if (parameters.hasOwnProperty(id)) {
|
||||||
|
self.ids.push(id);
|
||||||
}
|
}
|
||||||
catch (event) {
|
}
|
||||||
return next(info.errorContent.replace('%id', id) + ' ' + info.errorParamsBroken);
|
|
||||||
|
// Keep track of current content
|
||||||
|
self.current = -1;
|
||||||
|
|
||||||
|
if (self.workers !== undefined) {
|
||||||
|
// Assign each worker content to upgrade
|
||||||
|
for (var i = 0; i < self.workers.length; i++) {
|
||||||
|
self.assignWork(self.workers[i]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
// Upgrade this content.
|
self.assignWork();
|
||||||
self.upgrade(info.library.name, new Version(info.library.version), self.version, params, function (err, params) {
|
}
|
||||||
if (err) {
|
|
||||||
return next(info.errorContent.replace('%id', id) + ' ' + err);
|
|
||||||
}
|
|
||||||
|
|
||||||
upgraded[id] = JSON.stringify(params);
|
|
||||||
|
|
||||||
current++;
|
|
||||||
self.throbber.setProgress(Math.round((info.total - self.left + current) / (info.total / 100)) + ' %');
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
}, function (err) {
|
|
||||||
// Finished with all parameters that came in
|
|
||||||
if (err) {
|
|
||||||
return self.setStatus('<p>' + info.error + '<br/>' + err + '</p>');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save upgraded content and get next round of data to process
|
|
||||||
self.nextBatch({
|
|
||||||
libraryId: self.version.libraryId,
|
|
||||||
token: self.token,
|
|
||||||
params: JSON.stringify(upgraded)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upgade the given content.
|
|
||||||
*
|
*
|
||||||
* @param {String} name
|
|
||||||
* @param {Version} oldVersion
|
|
||||||
* @param {Version} newVersion
|
|
||||||
* @param {Object} params
|
|
||||||
* @param {Function} next
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
*/
|
||||||
ContentUpgrade.prototype.upgrade = function (name, oldVersion, newVersion, params, next) {
|
ContentUpgrade.prototype.assignWork = function (worker) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Load library details and upgrade routines
|
var id = self.ids[self.current + 1];
|
||||||
self.loadLibrary(name, newVersion, function (err, library) {
|
if (id === undefined) {
|
||||||
if (err) {
|
return false; // Out of work
|
||||||
return next(err);
|
}
|
||||||
}
|
self.current++;
|
||||||
|
self.working++;
|
||||||
|
|
||||||
// Run upgrade routines on params
|
if (worker) {
|
||||||
self.processParams(library, oldVersion, newVersion, params, function (err, params) {
|
worker.postMessage({
|
||||||
|
action: 'newJob',
|
||||||
|
id: id,
|
||||||
|
name: info.library.name,
|
||||||
|
oldVersion: info.library.version,
|
||||||
|
newVersion: self.version.toString(),
|
||||||
|
params: self.parameters[id]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
new H5P.ContentUpgradeProcess(info.library.name, new Version(info.library.version), self.version, self.parameters[id], id, function loadLibrary(name, version, next) {
|
||||||
|
self.loadLibrary(name, version, function (err, library) {
|
||||||
|
if (library.upgradesScript) {
|
||||||
|
self.loadScript(library.upgradesScript, function (err) {
|
||||||
|
if (err) {
|
||||||
|
err = info.errorScript.replace('%lib', name + ' ' + version);
|
||||||
|
}
|
||||||
|
next(err, library);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
next(null, library);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}, function done(err, result) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
self.printError(err);
|
||||||
|
return ;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any of the sub-libraries need upgrading
|
self.workDone(id, result);
|
||||||
asyncSerial(library.semantics, function (index, field, next) {
|
|
||||||
self.processField(field, params[field.name], function (err, upgradedParams) {
|
|
||||||
if (upgradedParams) {
|
|
||||||
params[field.name] = upgradedParams;
|
|
||||||
}
|
|
||||||
next(err);
|
|
||||||
});
|
|
||||||
}, function (err) {
|
|
||||||
next(err, params);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
ContentUpgrade.prototype.workDone = function (id, result, worker) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.working--;
|
||||||
|
self.upgraded[id] = result;
|
||||||
|
|
||||||
|
// Update progress message
|
||||||
|
self.throbber.setProgress(Math.round((info.total - self.left + self.current) / (info.total / 100)) + ' %');
|
||||||
|
|
||||||
|
// Assign next job
|
||||||
|
if (self.assignWork(worker) === false && self.working === 0) {
|
||||||
|
// All workers have finsihed.
|
||||||
|
self.nextBatch({
|
||||||
|
libraryId: self.version.libraryId,
|
||||||
|
token: self.token,
|
||||||
|
params: JSON.stringify(self.upgraded)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
ContentUpgrade.prototype.terminate = function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (self.workers) {
|
||||||
|
// Stop all workers
|
||||||
|
for (var i = 0; i < self.workers.length; i++) {
|
||||||
|
self.workers[i].terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var librariesLoadedCallbacks = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load library data needed for content upgrade.
|
* Load library data needed for content upgrade.
|
||||||
*
|
*
|
||||||
|
@ -303,32 +334,43 @@ var H5PUpgrades = H5PUpgrades || {};
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var key = name + '/' + version.major + '/' + version.minor;
|
var key = name + '/' + version.major + '/' + version.minor;
|
||||||
if (librariesCache[key] !== undefined) {
|
|
||||||
|
if (librariesCache[key] === true) {
|
||||||
|
// Library is being loaded, que callback
|
||||||
|
if (librariesLoadedCallbacks[key] === undefined) {
|
||||||
|
librariesLoadedCallbacks[key] = [next];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
librariesLoadedCallbacks[key].push(next);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (librariesCache[key] !== undefined) {
|
||||||
// Library has been loaded before. Return cache.
|
// Library has been loaded before. Return cache.
|
||||||
next(null, librariesCache[key]);
|
next(null, librariesCache[key]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track time spent loading
|
||||||
|
var start = new Date().getTime();
|
||||||
|
librariesCache[key] = true;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
cache: true,
|
cache: true,
|
||||||
url: info.libraryBaseUrl + '/' + key
|
url: info.libraryBaseUrl + '/' + key
|
||||||
}).fail(function () {
|
}).fail(function () {
|
||||||
|
self.io += new Date().getTime() - start;
|
||||||
next(info.errorData.replace('%lib', name + ' ' + version));
|
next(info.errorData.replace('%lib', name + ' ' + version));
|
||||||
}).done(function (library) {
|
}).done(function (library) {
|
||||||
|
self.io += new Date().getTime() - start;
|
||||||
librariesCache[key] = library;
|
librariesCache[key] = library;
|
||||||
|
next(null, library);
|
||||||
|
|
||||||
if (library.upgradesScript) {
|
if (librariesLoadedCallbacks[key] !== undefined) {
|
||||||
self.loadScript(library.upgradesScript, function (err) {
|
for (var i = 0; i < librariesLoadedCallbacks[key].length; i++) {
|
||||||
if (err) {
|
librariesLoadedCallbacks[key][i](null, library);
|
||||||
err = info.errorScript.replace('%lib', name + ' ' + version);
|
}
|
||||||
}
|
|
||||||
next(err, library);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
next(null, library);
|
|
||||||
}
|
}
|
||||||
|
delete librariesLoadedCallbacks[key];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -339,162 +381,43 @@ var H5PUpgrades = H5PUpgrades || {};
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
ContentUpgrade.prototype.loadScript = function (url, next) {
|
ContentUpgrade.prototype.loadScript = function (url, next) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (scriptsCache[url] !== undefined) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track time spent loading
|
||||||
|
var start = new Date().getTime();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
dataType: 'script',
|
dataType: 'script',
|
||||||
cache: true,
|
cache: true,
|
||||||
url: url
|
url: url
|
||||||
}).fail(function () {
|
}).fail(function () {
|
||||||
|
self.io += new Date().getTime() - start;
|
||||||
next(true);
|
next(true);
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
|
scriptsCache[url] = true;
|
||||||
|
self.io += new Date().getTime() - start;
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run upgrade hooks on params.
|
|
||||||
*
|
*
|
||||||
* @param {Object} library
|
|
||||||
* @param {Version} oldVersion
|
|
||||||
* @param {Version} newVersion
|
|
||||||
* @param {Object} params
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
*/
|
||||||
ContentUpgrade.prototype.processParams = function (library, oldVersion, newVersion, params, next) {
|
ContentUpgrade.prototype.printError = function (error) {
|
||||||
if (H5PUpgrades[library.name] === undefined) {
|
|
||||||
if (library.upgradesScript) {
|
|
||||||
// Upgrades script should be loaded so the upgrades should be here.
|
|
||||||
return next(info.errorScript.replace('%lib', library.name + ' ' + newVersion));
|
|
||||||
}
|
|
||||||
|
|
||||||
// No upgrades script. Move on
|
|
||||||
return next(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run upgrade hooks. Start by going through major versions
|
|
||||||
asyncSerial(H5PUpgrades[library.name], function (major, minors, nextMajor) {
|
|
||||||
if (major < oldVersion.major || major > newVersion.major) {
|
|
||||||
// Older than the current version or newer than the selected
|
|
||||||
nextMajor();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Go through the minor versions for this major version
|
|
||||||
asyncSerial(minors, function (minor, upgrade, nextMinor) {
|
|
||||||
if (minor <= oldVersion.minor || minor > newVersion.minor) {
|
|
||||||
// Older than or equal to the current version or newer than the selected
|
|
||||||
nextMinor();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// We found an upgrade hook, run it
|
|
||||||
var unnecessaryWrapper = (upgrade.contentUpgrade !== undefined ? upgrade.contentUpgrade : upgrade);
|
|
||||||
|
|
||||||
try {
|
|
||||||
unnecessaryWrapper(params, function (err, upgradedParams) {
|
|
||||||
params = upgradedParams;
|
|
||||||
nextMinor(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
next(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, nextMajor);
|
|
||||||
}
|
|
||||||
}, function (err) {
|
|
||||||
next(err, params);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process parameter fields to find and upgrade sub-libraries.
|
|
||||||
*
|
|
||||||
* @param {Object} field
|
|
||||||
* @param {Object} params
|
|
||||||
* @param {Function} next
|
|
||||||
*/
|
|
||||||
ContentUpgrade.prototype.processField = function (field, params, next) {
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (params === undefined) {
|
if (error.type === 'errorParamsBroken') {
|
||||||
return next();
|
error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken;
|
||||||
|
}
|
||||||
|
else if (error.type === 'scriptMissing') {
|
||||||
|
error = info.errorScript.replace('%lib', error.library);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (field.type) {
|
self.setStatus('<p>' + info.error + '<br/>' + error + '</p>');
|
||||||
case 'library':
|
|
||||||
if (params.library === undefined || params.params === undefined) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
if (availableLib[0] === usedLib[0]) {
|
|
||||||
if (availableLib[1] === usedLib[1]) {
|
|
||||||
return next(); // Same version
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have different versions
|
|
||||||
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 next(); // Larger or same version that's available
|
|
||||||
}
|
|
||||||
|
|
||||||
// A newer version is available, upgrade params
|
|
||||||
return self.upgrade(availableLib[0], usedVer, availableVer, params.params, function (err, upgraded) {
|
|
||||||
if (!err) {
|
|
||||||
params.library = availableLib[0] + ' ' + availableVer.major + '.' + availableVer.minor;
|
|
||||||
params.params = upgraded;
|
|
||||||
}
|
|
||||||
next(err, params);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'group':
|
|
||||||
if (field.fields.length === 1) {
|
|
||||||
// Single field to process, wrapper will be skipped
|
|
||||||
self.processField(field.fields[0], params, function (err, upgradedParams) {
|
|
||||||
if (upgradedParams) {
|
|
||||||
params = upgradedParams;
|
|
||||||
}
|
|
||||||
next(err, params);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Go through all fields in the group
|
|
||||||
asyncSerial(field.fields, function (index, subField, next) {
|
|
||||||
self.processField(subField, params[subField.name], function (err, upgradedParams) {
|
|
||||||
if (upgradedParams) {
|
|
||||||
params[subField.name] = upgradedParams;
|
|
||||||
}
|
|
||||||
next(err);
|
|
||||||
});
|
|
||||||
}, function (err) {
|
|
||||||
next(err, params);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'list':
|
|
||||||
// Go trough all params in the list
|
|
||||||
asyncSerial(params, function (index, subParams, next) {
|
|
||||||
self.processField(field.field, subParams, function (err, upgradedParams) {
|
|
||||||
if (upgradedParams) {
|
|
||||||
params[index] = upgradedParams;
|
|
||||||
}
|
|
||||||
next(err);
|
|
||||||
});
|
|
||||||
}, function (err) {
|
|
||||||
next(err, params);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
})(H5P.jQuery);
|
})(H5P.jQuery, H5P.Version);
|
||||||
|
|
|
@ -5,9 +5,59 @@ var H5P = H5P || {};
|
||||||
* The Event class for the EventDispatcher
|
* The Event class for the EventDispatcher
|
||||||
* @class
|
* @class
|
||||||
*/
|
*/
|
||||||
H5P.Event = function(type, data) {
|
H5P.Event = function(type, data, extras) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
var bubbles = false;
|
||||||
|
|
||||||
|
// Is this an external event?
|
||||||
|
var external = false;
|
||||||
|
|
||||||
|
// Is this event scheduled to be sent externally?
|
||||||
|
var scheduledForExternal = false;
|
||||||
|
|
||||||
|
if (extras === undefined) {
|
||||||
|
extras = {};
|
||||||
|
}
|
||||||
|
if (extras.bubbles === true) {
|
||||||
|
bubbles = true;
|
||||||
|
}
|
||||||
|
if (extras.external === true) {
|
||||||
|
external = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent this event from bubbling up to parent
|
||||||
|
*
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
this.preventBubbling = function() {
|
||||||
|
bubbles = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get bubbling status
|
||||||
|
*
|
||||||
|
* @returns {Boolean} - true if bubbling false otherwise
|
||||||
|
*/
|
||||||
|
this.getBubbles = function() {
|
||||||
|
return bubbles;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to schedule an event for externalDispatcher
|
||||||
|
*
|
||||||
|
* @returns {Boolean}
|
||||||
|
* - true if external and not already scheduled
|
||||||
|
* - false otherwise
|
||||||
|
*/
|
||||||
|
this.scheduleForExternal = function() {
|
||||||
|
if (external && !scheduledForExternal) {
|
||||||
|
scheduledForExternal = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
H5P.EventDispatcher = (function () {
|
H5P.EventDispatcher = (function () {
|
||||||
|
@ -131,22 +181,41 @@ H5P.EventDispatcher = (function () {
|
||||||
* Custom event data(used when event type as string is used as first
|
* Custom event data(used when event type as string is used as first
|
||||||
* argument
|
* argument
|
||||||
*/
|
*/
|
||||||
this.trigger = function (event, eventData) {
|
this.trigger = function (event, eventData, extras) {
|
||||||
if (event === undefined) {
|
if (event === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof event === 'string') {
|
if (typeof event === 'string') {
|
||||||
event = new H5P.Event(event, eventData);
|
event = new H5P.Event(event, eventData, extras);
|
||||||
}
|
}
|
||||||
else if (eventData !== undefined) {
|
else if (eventData !== undefined) {
|
||||||
event.data = eventData;
|
event.data = eventData;
|
||||||
}
|
}
|
||||||
if (triggers[event.type] === undefined) {
|
|
||||||
return;
|
// Check to see if this event should go externally after all triggering and bubbling is done
|
||||||
|
var scheduledForExternal = event.scheduleForExternal();
|
||||||
|
|
||||||
|
if (triggers[event.type] !== undefined) {
|
||||||
|
// Call all listeners
|
||||||
|
for (var i = 0; i < triggers[event.type].length; i++) {
|
||||||
|
triggers[event.type][i].listener.call(triggers[event.type][i].thisArg, event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Call all listeners
|
|
||||||
for (var i = 0; i < triggers[event.type].length; i++) {
|
if (triggers['*'] !== undefined) {
|
||||||
triggers[event.type][i].listener.call(triggers[event.type][i].thisArg, event);
|
// Call all * listeners
|
||||||
|
for (var i = 0; i < triggers['*'].length; i++) {
|
||||||
|
triggers['*'][i].listener.call(triggers['*'][i].thisArg, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bubble
|
||||||
|
if (event.getBubbles() && self.parent instanceof H5P.EventDispatcher && typeof self.parent.trigger === 'function') {
|
||||||
|
self.parent.trigger(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scheduledForExternal) {
|
||||||
|
H5P.externalDispatcher.trigger(event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
// H5P iframe Resizer
|
// H5P iframe Resizer
|
||||||
(function () {
|
(function () {
|
||||||
if (!window.postMessage || !window.addEventListener) {
|
if (!window.postMessage || !window.addEventListener || window.h5pResizerInitialized) {
|
||||||
return; // Not supported
|
return; // Not supported
|
||||||
}
|
}
|
||||||
|
window.h5pResizerInitialized = true;
|
||||||
|
|
||||||
// Map actions to handlers
|
// Map actions to handlers
|
||||||
var actionHandlers = {};
|
var actionHandlers = {};
|
||||||
|
@ -47,6 +48,11 @@
|
||||||
actionHandlers.prepareResize = function (iframe, data, respond) {
|
actionHandlers.prepareResize = function (iframe, data, respond) {
|
||||||
responseData = {};
|
responseData = {};
|
||||||
|
|
||||||
|
// Create spaceholder and insert after iframe.
|
||||||
|
var spaceholder = document.createElement('div');
|
||||||
|
spaceholder.style.height = (iframe.clientHeight - 1) + 'px';
|
||||||
|
iframe.parentNode.insertBefore(spaceholder, iframe.nextSibling);
|
||||||
|
|
||||||
// Reset iframe height, in case content has shrinked.
|
// Reset iframe height, in case content has shrinked.
|
||||||
iframe.style.height = '1px';
|
iframe.style.height = '1px';
|
||||||
|
|
||||||
|
@ -64,6 +70,7 @@
|
||||||
actionHandlers.resize = function (iframe, data, respond) {
|
actionHandlers.resize = function (iframe, data, respond) {
|
||||||
// Resize iframe so all content is visible.
|
// Resize iframe so all content is visible.
|
||||||
iframe.style.height = data.height + 'px';
|
iframe.style.height = data.height + 'px';
|
||||||
|
iframe.parentNode.removeChild(iframe.nextSibling);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
H5P.Version = (function () {
|
||||||
|
/**
|
||||||
|
* Make it easy to keep track of version details.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @namespace H5P
|
||||||
|
* @param {String} version
|
||||||
|
*/
|
||||||
|
function Version(version) {
|
||||||
|
var versionSplit = version.split('.', 3);
|
||||||
|
|
||||||
|
// Public
|
||||||
|
this.major = versionSplit[0];
|
||||||
|
this.minor = versionSplit[1];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public. Custom string for this object.
|
||||||
|
*
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
this.toString = function () {
|
||||||
|
return version;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Version;
|
||||||
|
})();
|
|
@ -6,7 +6,7 @@ var H5P = H5P || {};
|
||||||
* @class
|
* @class
|
||||||
*/
|
*/
|
||||||
H5P.XAPIEvent = function() {
|
H5P.XAPIEvent = function() {
|
||||||
H5P.Event.call(this, 'xAPI', {'statement': {}});
|
H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true, external: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype);
|
H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype);
|
||||||
|
@ -44,8 +44,8 @@ H5P.XAPIEvent.prototype.setVerb = function(verb) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
else if (verb.id !== undefined) {
|
||||||
H5P.error('illegal verb');
|
this.data.statement.verb = verb;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -79,17 +79,50 @@ H5P.XAPIEvent.prototype.getVerb = function(full) {
|
||||||
H5P.XAPIEvent.prototype.setObject = function(instance) {
|
H5P.XAPIEvent.prototype.setObject = function(instance) {
|
||||||
if (instance.contentId) {
|
if (instance.contentId) {
|
||||||
this.data.statement.object = {
|
this.data.statement.object = {
|
||||||
'id': H5PIntegration.contents['cid-' + instance.contentId].url,
|
'id': this.getContentXAPIId(instance),
|
||||||
'objectType': 'Activity',
|
'objectType': 'Activity',
|
||||||
'extensions': {
|
'definition': {
|
||||||
'http://h5p.org/x-api/h5p-local-content-id': instance.contentId
|
'extensions': {
|
||||||
|
'http://h5p.org/x-api/h5p-local-content-id': instance.contentId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (instance.subContentId) {
|
||||||
|
this.data.statement.object.definition.extensions['http://h5p.org/x-api/h5p-subContentId'] = instance.subContentId;
|
||||||
|
// Don't set titles on main content, title should come from publishing platform
|
||||||
|
if (typeof instance.getH5PTitle === 'function') {
|
||||||
|
this.data.statement.object.definition.name = {
|
||||||
|
"en-US": instance.getH5PTitle()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + instance.contentId].title) {
|
||||||
|
this.data.statement.object.definition.name = {
|
||||||
|
"en-US": H5P.createTitle(H5PIntegration.contents['cid-' + instance.contentId].title)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
};
|
||||||
// Not triggered by an H5P content type...
|
|
||||||
this.data.statement.object = {
|
/**
|
||||||
'objectType': 'Activity'
|
* Helperfunction to set the context part of the statement.
|
||||||
|
*
|
||||||
|
* @param {object} instance - the H5P 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": [
|
||||||
|
{
|
||||||
|
"id": this.getContentXAPIId(instance.parent),
|
||||||
|
"objectType": "Activity"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -111,16 +144,13 @@ H5P.XAPIEvent.prototype.setActor = function() {
|
||||||
uuid = localStorage.H5PUserUUID;
|
uuid = localStorage.H5PUserUUID;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(char) {
|
uuid = H5P.createUUID();
|
||||||
var random = Math.random()*16|0, newChar = char === 'x' ? random : (random&0x3|0x8);
|
|
||||||
return newChar.toString(16);
|
|
||||||
});
|
|
||||||
localStorage.H5PUserUUID = uuid;
|
localStorage.H5PUserUUID = uuid;
|
||||||
}
|
}
|
||||||
this.data.statement.actor = {
|
this.data.statement.actor = {
|
||||||
'account': {
|
'account': {
|
||||||
'name': uuid,
|
'name': uuid,
|
||||||
'homePage': window.location.origin + H5PIntegration.basePath
|
'homePage': H5PIntegration.siteUrl
|
||||||
},
|
},
|
||||||
'objectType': 'Agent'
|
'objectType': 'Agent'
|
||||||
};
|
};
|
||||||
|
@ -145,6 +175,17 @@ H5P.XAPIEvent.prototype.getScore = function() {
|
||||||
return this.getVerifiedStatementValue(['result', 'score', 'raw']);
|
return this.getVerifiedStatementValue(['result', 'score', 'raw']);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) {
|
||||||
|
var xAPIId;
|
||||||
|
if (instance.contentId && H5PIntegration && H5PIntegration.contents) {
|
||||||
|
xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url;
|
||||||
|
if (instance.subContentId) {
|
||||||
|
xAPIId += '?subContentId=' + instance.subContentId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return xAPIId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Figure out if a property exists in the statement and return it
|
* Figure out if a property exists in the statement and return it
|
||||||
*
|
*
|
||||||
|
|
|
@ -3,10 +3,6 @@ var H5P = H5P || {};
|
||||||
// Create object where external code may register and listen for H5P Events
|
// Create object where external code may register and listen for H5P Events
|
||||||
H5P.externalDispatcher = new H5P.EventDispatcher();
|
H5P.externalDispatcher = new H5P.EventDispatcher();
|
||||||
|
|
||||||
if (H5P.isFramed && H5P.externalEmbed === false) {
|
|
||||||
H5P.externalDispatcher.on('xAPI', window.top.H5P.externalDispatcher.trigger);
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventDispatcher extensions
|
// EventDispatcher extensions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,20 +35,37 @@ H5P.EventDispatcher.prototype.createXAPIEventTemplate = function(verb, extra) {
|
||||||
event.data.statement[i] = extra[i];
|
event.data.statement[i] = extra[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!('object' in event)) {
|
if (!('object' in event.data.statement)) {
|
||||||
event.setObject(this);
|
event.setObject(this);
|
||||||
}
|
}
|
||||||
|
if (!('context' in event.data.statement)) {
|
||||||
|
event.setContext(this);
|
||||||
|
}
|
||||||
return event;
|
return event;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to create xAPI completed events
|
* Helper function to create xAPI completed events
|
||||||
*
|
*
|
||||||
|
* DEPRECATED - USE triggerXAPIScored instead
|
||||||
|
*
|
||||||
* @param {int} score - will be set as the 'raw' value of the score object
|
* @param {int} score - will be set as the 'raw' value of the score object
|
||||||
* @param {int} maxScore - will be set as the "max" value of the score object
|
* @param {int} maxScore - will be set as the "max" value of the score object
|
||||||
*/
|
*/
|
||||||
H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) {
|
H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) {
|
||||||
var event = this.createXAPIEventTemplate('completed');
|
this.triggerXAPIScored(score, maxScore, 'completed');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to create scored xAPI events
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {int} score - will be set as the 'raw' value of the score object
|
||||||
|
* @param {int} maxScore - will be set as the "max" value of the score object
|
||||||
|
* @param {string} verb - short form of adl verb
|
||||||
|
*/
|
||||||
|
H5P.EventDispatcher.prototype.triggerXAPIScored = function(score, maxScore, verb) {
|
||||||
|
var event = this.createXAPIEventTemplate(verb);
|
||||||
event.setScoredResult(score, maxScore);
|
event.setScoredResult(score, maxScore);
|
||||||
this.trigger(event);
|
this.trigger(event);
|
||||||
};
|
};
|
||||||
|
@ -63,13 +76,10 @@ H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) {
|
||||||
* @param {function} event - xAPI event
|
* @param {function} event - xAPI event
|
||||||
*/
|
*/
|
||||||
H5P.xAPICompletedListener = function(event) {
|
H5P.xAPICompletedListener = function(event) {
|
||||||
var statement = event.data.statement;
|
if (event.getVerb() === 'completed' && !event.getVerifiedStatementValue(['context', 'contextActivities', 'parent'])) {
|
||||||
if ('verb' in statement) {
|
var score = event.getScore();
|
||||||
if (statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') {
|
var maxScore = event.getMaxScore();
|
||||||
var score = statement.result.score.raw;
|
var contentId = event.getVerifiedStatementValue(['object', 'definition', 'extensions', 'http://h5p.org/x-api/h5p-local-content-id']);
|
||||||
var maxScore = statement.result.score.max;
|
H5P.setFinished(contentId, score, maxScore);
|
||||||
var contentId = statement.object.extensions['http://h5p.org/x-api/h5p-local-content-id'];
|
|
||||||
H5P.setFinished(contentId, score, maxScore);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
446
js/h5p.js
446
js/h5p.js
|
@ -30,7 +30,29 @@ else if (document.documentElement.msRequestFullscreen) {
|
||||||
H5P.fullScreenBrowserPrefix = 'ms';
|
H5P.fullScreenBrowserPrefix = 'ms';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep track of when the H5Ps where started
|
/** @const {Number} */
|
||||||
|
H5P.DISABLE_NONE = 0;
|
||||||
|
|
||||||
|
/** @const {Number} */
|
||||||
|
H5P.DISABLE_FRAME = 1;
|
||||||
|
|
||||||
|
/** @const {Number} */
|
||||||
|
H5P.DISABLE_DOWNLOAD = 2;
|
||||||
|
|
||||||
|
/** @const {Number} */
|
||||||
|
H5P.DISABLE_EMBED = 4;
|
||||||
|
|
||||||
|
/** @const {Number} */
|
||||||
|
H5P.DISABLE_COPYRIGHT = 8;
|
||||||
|
|
||||||
|
/** @const {Number} */
|
||||||
|
H5P.DISABLE_ABOUT = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep track of when the H5Ps where started.
|
||||||
|
*
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
H5P.opened = {};
|
H5P.opened = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +67,11 @@ H5P.init = function (target) {
|
||||||
|
|
||||||
// Determine if we can use full screen
|
// Determine if we can use full screen
|
||||||
if (H5P.canHasFullScreen === undefined) {
|
if (H5P.canHasFullScreen === undefined) {
|
||||||
H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? (document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled) : true;
|
// Restricts fullscreen when embedded.
|
||||||
|
// (embedded doesn't support semi-fullscreen solution)
|
||||||
|
H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? ((document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled) ? true : false) : true;
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// H5Ps added in normal DIV.
|
// H5Ps added in normal DIV.
|
||||||
|
@ -62,31 +88,56 @@ H5P.init = function (target) {
|
||||||
params: JSON.parse(contentData.jsonContent)
|
params: JSON.parse(contentData.jsonContent)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
H5P.getUserData(contentId, 'state', function (err, previousState) {
|
||||||
|
if (previousState) {
|
||||||
|
library.userDatas = {
|
||||||
|
state: previousState
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (previousState === null && H5PIntegration.saveFreq) {
|
||||||
|
// Content has been reset. Display dialog.
|
||||||
|
delete contentData.contentUserData;
|
||||||
|
var dialog = new H5P.Dialog('content-user-data-reset', 'Data Reset', '<p>' + H5P.t('contentChanged') + '</p><p>' + H5P.t('startingOver') + '</p><div class="h5p-dialog-ok-button" tabIndex="0" role="button">OK</div>', $container);
|
||||||
|
H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) {
|
||||||
|
$dialog.find('.h5p-dialog-ok-button').click(function () {
|
||||||
|
dialog.close();
|
||||||
|
}).keypress(function (event) {
|
||||||
|
if (event.which === 32) {
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
dialog.open();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Create new instance.
|
// Create new instance.
|
||||||
var instance = H5P.newRunnable(library, contentId, $container, true);
|
var instance = H5P.newRunnable(library, contentId, $container, true);
|
||||||
|
|
||||||
// Check if we should add and display a fullscreen button for this H5P.
|
// Check if we should add and display a fullscreen button for this H5P.
|
||||||
if (contentData.fullScreen == 1) {
|
if (contentData.fullScreen == 1 && H5P.canHasFullScreen) {
|
||||||
H5P.jQuery('<div class="h5p-content-controls"><div role="button" tabindex="1" class="h5p-enable-fullscreen" title="' + H5P.t('fullscreen') + '"></div></div>').prependTo($container).children().click(function () {
|
H5P.jQuery('<div class="h5p-content-controls"><div role="button" tabindex="1" class="h5p-enable-fullscreen" title="' + H5P.t('fullscreen') + '"></div></div>').prependTo($container).children().click(function () {
|
||||||
H5P.fullScreen($container, instance);
|
H5P.fullScreen($container, instance);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create action bar
|
||||||
var $actions = H5P.jQuery('<ul class="h5p-actions"></ul>');
|
var $actions = H5P.jQuery('<ul class="h5p-actions"></ul>');
|
||||||
if (contentData.exportUrl !== '') {
|
|
||||||
// Display export button
|
if (!(contentData.disable & H5P.DISABLE_DOWNLOAD)) {
|
||||||
|
// Add export button
|
||||||
H5P.jQuery('<li class="h5p-button h5p-export" role="button" tabindex="1" title="' + H5P.t('downloadDescription') + '">' + H5P.t('download') + '</li>').appendTo($actions).click(function () {
|
H5P.jQuery('<li class="h5p-button h5p-export" role="button" tabindex="1" title="' + H5P.t('downloadDescription') + '">' + H5P.t('download') + '</li>').appendTo($actions).click(function () {
|
||||||
window.location.href = contentData.exportUrl;
|
window.location.href = contentData.exportUrl;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (!(contentData.disable & H5P.DISABLE_COPYRIGHT) && instance.getCopyrights !== undefined) {
|
||||||
// Display copyrights button
|
// Add copyrights button
|
||||||
H5P.jQuery('<li class="h5p-button h5p-copyrights" role="button" tabindex="1" title="' + H5P.t('copyrightsDescription') + '">' + H5P.t('copyrights') + '</li>').appendTo($actions).click(function () {
|
H5P.jQuery('<li class="h5p-button h5p-copyrights" role="button" tabindex="1" title="' + H5P.t('copyrightsDescription') + '">' + H5P.t('copyrights') + '</li>').appendTo($actions).click(function () {
|
||||||
H5P.openCopyrightsDialog($actions, instance, library.params, contentId);
|
H5P.openCopyrightsDialog($actions, instance, library.params, contentId);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
if (contentData.embedCode !== undefined) {
|
if (!(contentData.disable & H5P.DISABLE_EMBED)) {
|
||||||
// Display embed button
|
// Add embed button
|
||||||
H5P.jQuery('<li class="h5p-button h5p-embed" role="button" tabindex="1" title="' + H5P.t('embedDescription') + '">' + H5P.t('embed') + '</li>').appendTo($actions).click(function () {
|
H5P.jQuery('<li class="h5p-button h5p-embed" role="button" tabindex="1" title="' + H5P.t('embedDescription') + '">' + H5P.t('embed') + '</li>').appendTo($actions).click(function () {
|
||||||
H5P.openEmbedDialog($actions, contentData.embedCode, contentData.resizeCode, {
|
H5P.openEmbedDialog($actions, contentData.embedCode, contentData.resizeCode, {
|
||||||
width: $container.width(),
|
width: $container.width(),
|
||||||
|
@ -94,10 +145,19 @@ H5P.init = function (target) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (contentData.showH5PIconInActionBar) {
|
|
||||||
|
if (!(contentData.disable & H5P.DISABLE_ABOUT)) {
|
||||||
|
// Add about H5P button icon
|
||||||
H5P.jQuery('<li><a class="h5p-link" href="http://h5p.org" target="_blank" title="' + H5P.t('h5pDescription') + '"></a></li>').appendTo($actions);
|
H5P.jQuery('<li><a class="h5p-link" href="http://h5p.org" target="_blank" title="' + H5P.t('h5pDescription') + '"></a></li>').appendTo($actions);
|
||||||
}
|
}
|
||||||
$actions.insertAfter($container);
|
|
||||||
|
// Insert action bar if it has any content
|
||||||
|
if ($actions.children().length) {
|
||||||
|
$actions.insertAfter($container);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$element.addClass('h5p-no-frame');
|
||||||
|
}
|
||||||
|
|
||||||
// Keep track of when we started
|
// Keep track of when we started
|
||||||
H5P.opened[contentId] = new Date();
|
H5P.opened[contentId] = new Date();
|
||||||
|
@ -111,7 +171,37 @@ H5P.init = function (target) {
|
||||||
|
|
||||||
// Listen for xAPI events.
|
// Listen for xAPI events.
|
||||||
H5P.on(instance, 'xAPI', H5P.xAPICompletedListener);
|
H5P.on(instance, 'xAPI', H5P.xAPICompletedListener);
|
||||||
H5P.on(instance, 'xAPI', H5P.externalDispatcher.trigger);
|
|
||||||
|
// Auto save current state if supported
|
||||||
|
if (H5PIntegration.saveFreq !== false && (
|
||||||
|
instance.getCurrentState instanceof Function ||
|
||||||
|
typeof instance.getCurrentState === 'function')) {
|
||||||
|
|
||||||
|
var saveTimer, save = function () {
|
||||||
|
var state = instance.getCurrentState();
|
||||||
|
if (state !== undefined) {
|
||||||
|
H5P.setUserData(contentId, 'state', state, {deleteOnChange: true});
|
||||||
|
}
|
||||||
|
if (H5PIntegration.saveFreq) {
|
||||||
|
// Continue autosave
|
||||||
|
saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (H5PIntegration.saveFreq) {
|
||||||
|
// Start autosave
|
||||||
|
saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// xAPI events will schedule a save in three seconds.
|
||||||
|
H5P.on(instance, 'xAPI', function (event) {
|
||||||
|
var verb = event.getVerb();
|
||||||
|
if (verb === 'completed' || verb === 'progressed') {
|
||||||
|
clearTimeout(saveTimer);
|
||||||
|
saveTimer = setTimeout(save, 3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (H5P.isFramed) {
|
if (H5P.isFramed) {
|
||||||
var resizeDelay;
|
var resizeDelay;
|
||||||
|
@ -234,20 +324,10 @@ H5P.init = function (target) {
|
||||||
* @returns {string} HTML
|
* @returns {string} HTML
|
||||||
*/
|
*/
|
||||||
H5P.getHeadTags = function (contentId) {
|
H5P.getHeadTags = function (contentId) {
|
||||||
var basePath = window.location.protocol + '//' + window.location.host + H5PIntegration.basePath;
|
|
||||||
|
|
||||||
var createUrl = function (path) {
|
|
||||||
if (path.substring(0,7) !== 'http://' && path.substring(0,8) !== 'https://') {
|
|
||||||
// Not external, add base path.
|
|
||||||
path = basePath + path;
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
};
|
|
||||||
|
|
||||||
var createStyleTags = function (styles) {
|
var createStyleTags = function (styles) {
|
||||||
var tags = '';
|
var tags = '';
|
||||||
for (var i = 0; i < styles.length; i++) {
|
for (var i = 0; i < styles.length; i++) {
|
||||||
tags += '<link rel="stylesheet" href="' + createUrl(styles[i]) + '">';
|
tags += '<link rel="stylesheet" href="' + styles[i] + '">';
|
||||||
}
|
}
|
||||||
return tags;
|
return tags;
|
||||||
};
|
};
|
||||||
|
@ -255,7 +335,7 @@ H5P.getHeadTags = function (contentId) {
|
||||||
var createScriptTags = function (scripts) {
|
var createScriptTags = function (scripts) {
|
||||||
var tags = '';
|
var tags = '';
|
||||||
for (var i = 0; i < scripts.length; i++) {
|
for (var i = 0; i < scripts.length; i++) {
|
||||||
tags += '<script src="' + createUrl(scripts[i]) + '"></script>';
|
tags += '<script src="' + scripts[i] + '"></script>';
|
||||||
}
|
}
|
||||||
return tags;
|
return tags;
|
||||||
};
|
};
|
||||||
|
@ -516,6 +596,19 @@ H5P.getPath = function (path, contentId) {
|
||||||
return prefix + '/' + path;
|
return prefix + '/' + path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* THIS FUNCTION IS DEPRECATED, USE getPath INSTEAD
|
||||||
|
* Will be remove march 2016.
|
||||||
|
*
|
||||||
|
* Find the path to the content files folder based on the id of the content
|
||||||
|
*
|
||||||
|
* @param contentId
|
||||||
|
* Id of the content requesting a path
|
||||||
|
*/
|
||||||
|
H5P.getContentPath = function (contentId) {
|
||||||
|
return H5PIntegration.url + '/content/' + contentId;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get library class constructor from H5P by classname.
|
* Get library class constructor from H5P by classname.
|
||||||
* Note that this class will only work for resolve "H5P.NameWithoutDot".
|
* Note that this class will only work for resolve "H5P.NameWithoutDot".
|
||||||
|
@ -541,13 +634,14 @@ H5P.classFromName = function (name) {
|
||||||
* @param {Number} contentId
|
* @param {Number} contentId
|
||||||
* @param {jQuery} $attachTo An optional element to attach the instance to.
|
* @param {jQuery} $attachTo An optional element to attach the instance to.
|
||||||
* @param {Boolean} skipResize Optionally skip triggering of the resize event after attaching.
|
* @param {Boolean} skipResize Optionally skip triggering of the resize event after attaching.
|
||||||
* @param {Object} The parent of this H5P
|
* @param {Object} extras - extra params for the H5P content constructor
|
||||||
* @return {Object} Instance.
|
* @return {Object} Instance.
|
||||||
*/
|
*/
|
||||||
H5P.newRunnable = function (library, contentId, $attachTo, skipResize) {
|
H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) {
|
||||||
var nameSplit, versionSplit;
|
var nameSplit, versionSplit, machineName;
|
||||||
try {
|
try {
|
||||||
nameSplit = library.library.split(' ', 2);
|
nameSplit = library.library.split(' ', 2);
|
||||||
|
machineName = nameSplit[0];
|
||||||
versionSplit = nameSplit[1].split('.', 2);
|
versionSplit = nameSplit[1].split('.', 2);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
|
@ -575,7 +669,27 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) {
|
||||||
return H5P.error('Unable to find constructor for: ' + library.library);
|
return H5P.error('Unable to find constructor for: ' + library.library);
|
||||||
}
|
}
|
||||||
|
|
||||||
var instance = new constructor(library.params, contentId);
|
if (extras === undefined) {
|
||||||
|
extras = {};
|
||||||
|
}
|
||||||
|
if (library.subContentId) {
|
||||||
|
extras.subContentId = library.subContentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (library.userDatas && library.userDatas.state && H5PIntegration.saveFreq) {
|
||||||
|
extras.previousState = library.userDatas.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
var instance;
|
||||||
|
// Some old library versions have their own custom third parameter.
|
||||||
|
// Make sure we don't send them the extras.
|
||||||
|
// (they will interpret it as something else)
|
||||||
|
if (H5P.jQuery.inArray(library.library, ['H5P.CoursePresentation 1.0', 'H5P.CoursePresentation 1.1', 'H5P.CoursePresentation 1.2', 'H5P.CoursePresentation 1.3']) > -1) {
|
||||||
|
instance = new constructor(library.params, contentId);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
instance = new constructor(library.params, contentId, extras);
|
||||||
|
}
|
||||||
|
|
||||||
if (instance.$ === undefined) {
|
if (instance.$ === undefined) {
|
||||||
instance.$ = H5P.jQuery(instance);
|
instance.$ = H5P.jQuery(instance);
|
||||||
|
@ -584,9 +698,20 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) {
|
||||||
if (instance.contentId === undefined) {
|
if (instance.contentId === undefined) {
|
||||||
instance.contentId = contentId;
|
instance.contentId = contentId;
|
||||||
}
|
}
|
||||||
|
if (instance.subContentId === undefined && library.subContentId) {
|
||||||
|
instance.subContentId = library.subContentId;
|
||||||
|
}
|
||||||
|
if (instance.parent === undefined && extras && extras.parent) {
|
||||||
|
instance.parent = extras.parent;
|
||||||
|
}
|
||||||
|
|
||||||
if ($attachTo !== undefined) {
|
if ($attachTo !== undefined) {
|
||||||
instance.attach($attachTo);
|
instance.attach($attachTo);
|
||||||
|
H5P.trigger(instance, 'domChanged', {
|
||||||
|
'$target': $attachTo,
|
||||||
|
'library': machineName,
|
||||||
|
'key': 'newLibrary'
|
||||||
|
}, {'bubbles': true, 'external': true});
|
||||||
|
|
||||||
if (skipResize === undefined || !skipResize) {
|
if (skipResize === undefined || !skipResize) {
|
||||||
// Resize content.
|
// Resize content.
|
||||||
|
@ -1274,7 +1399,7 @@ H5P.cssLoaded = function (path) {
|
||||||
* @returns {array} The passed array is returned for chaining.
|
* @returns {array} The passed array is returned for chaining.
|
||||||
*/
|
*/
|
||||||
H5P.shuffleArray = function (array) {
|
H5P.shuffleArray = function (array) {
|
||||||
if (! array instanceof Array) {
|
if (!(array instanceof Array)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1356,10 +1481,10 @@ if (String.prototype.trim === undefined) {
|
||||||
* @param {string} eventType
|
* @param {string} eventType
|
||||||
* The event type
|
* The event type
|
||||||
*/
|
*/
|
||||||
H5P.trigger = function(instance, eventType) {
|
H5P.trigger = function(instance, eventType, data, extras) {
|
||||||
// Try new event system first
|
// Try new event system first
|
||||||
if (instance.trigger !== undefined) {
|
if (instance.trigger !== undefined) {
|
||||||
instance.trigger(eventType);
|
instance.trigger(eventType, data, extras);
|
||||||
}
|
}
|
||||||
// Try deprecated event system
|
// Try deprecated event system
|
||||||
else if (instance.$ !== undefined && instance.$.trigger !== undefined) {
|
else if (instance.$ !== undefined && instance.$.trigger !== undefined) {
|
||||||
|
@ -1391,10 +1516,251 @@ H5P.on = function(instance, eventType, handler) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create UUID
|
||||||
|
*
|
||||||
|
* @returns {String} UUID
|
||||||
|
*/
|
||||||
|
H5P.createUUID = function() {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
H5P.jQuery(document).ready(function () {
|
H5P.createTitle = function(rawTitle, maxLength) {
|
||||||
if (!H5P.preventInit) {
|
if (!rawTitle) {
|
||||||
// Start script need to be an external resource to load in correct order for IE9.
|
return '';
|
||||||
H5P.init(document.body);
|
|
||||||
}
|
}
|
||||||
});
|
if (maxLength === undefined) {
|
||||||
|
maxLength = 60;
|
||||||
|
}
|
||||||
|
var title = H5P.jQuery('<div></div>')
|
||||||
|
.text(
|
||||||
|
// Strip tags
|
||||||
|
rawTitle.replace(/(<([^>]+)>)/ig,"")
|
||||||
|
// Escape
|
||||||
|
).text();
|
||||||
|
if (title.length > maxLength) {
|
||||||
|
title = title.substr(0, maxLength - 3) + '...';
|
||||||
|
}
|
||||||
|
return title;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrap in privates
|
||||||
|
(function ($) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates ajax requests for inserting, updateing and deleteing
|
||||||
|
* content user data.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {number} contentId What content to store the data for.
|
||||||
|
* @param {string} dataType Identifies the set of data for this content.
|
||||||
|
* @param {string} subContentId Identifies sub content
|
||||||
|
* @param {function} [done] Callback when ajax is done.
|
||||||
|
* @param {object} [data] To be stored for future use.
|
||||||
|
* @param {boolean} [preload=false] Data is loaded when content is loaded.
|
||||||
|
* @param {boolean} [invalidate=false] Data is invalidated when content changes.
|
||||||
|
* @param {boolean} [async=true]
|
||||||
|
*/
|
||||||
|
function contentUserDataAjax(contentId, dataType, subContentId, done, data, preload, invalidate, async) {
|
||||||
|
var options = {
|
||||||
|
url: H5PIntegration.ajax.contentUserData.replace(':contentId', contentId).replace(':dataType', dataType).replace(':subContentId', subContentId ? subContentId : 0),
|
||||||
|
dataType: 'json',
|
||||||
|
async: async === undefined ? true : async
|
||||||
|
};
|
||||||
|
if (data !== undefined) {
|
||||||
|
options.type = 'POST';
|
||||||
|
options.data = {
|
||||||
|
data: (data === null ? 0 : data),
|
||||||
|
preload: (preload ? 1 : 0),
|
||||||
|
invalidate: (invalidate ? 1 : 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
options.type = 'GET';
|
||||||
|
}
|
||||||
|
if (done !== undefined) {
|
||||||
|
options.error = function (xhr, error) {
|
||||||
|
done(error);
|
||||||
|
};
|
||||||
|
options.success = function (response) {
|
||||||
|
if (!response.success) {
|
||||||
|
done(response.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data === false || response.data === undefined) {
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
done(undefined, response.data);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user data for given content.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {number} contentId What content to get data for.
|
||||||
|
* @param {string} dataId Identifies the set of data for this content.
|
||||||
|
* @param {function} done Callback with error and data parameters.
|
||||||
|
* @param {string} [subContentId] Identifies which data belongs to sub content.
|
||||||
|
*/
|
||||||
|
H5P.getUserData = function (contentId, dataId, done, subContentId) {
|
||||||
|
if (!subContentId) {
|
||||||
|
subContentId = 0; // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = H5PIntegration.contents['cid-' + contentId];
|
||||||
|
var preloadedData = content.contentUserData;
|
||||||
|
if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId]) {
|
||||||
|
if (preloadedData[subContentId][dataId] === 'RESET') {
|
||||||
|
done(undefined, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
done(undefined, JSON.parse(preloadedData[subContentId][dataId]));
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
contentUserDataAjax(contentId, dataId, subContentId, function (err, data) {
|
||||||
|
if (err || data === undefined) {
|
||||||
|
done(err, data);
|
||||||
|
return; // Error or no data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache in preloaded
|
||||||
|
if (content.contentUserData === undefined) {
|
||||||
|
content.contentUserData = preloadedData = {};
|
||||||
|
}
|
||||||
|
if (preloadedData[subContentId] === undefined) {
|
||||||
|
preloadedData[subContentId] = {};
|
||||||
|
}
|
||||||
|
preloadedData[subContentId][dataId] = data;
|
||||||
|
|
||||||
|
// Done. Try to decode JSON
|
||||||
|
try {
|
||||||
|
done(undefined, JSON.parse(data));
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
done(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set user data for given content.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {number} contentId What content to get data for.
|
||||||
|
* @param {string} dataId Identifies the set of data for this content.
|
||||||
|
* @param {object} data The data that is to be stored.
|
||||||
|
* @param {object} extras - object holding the following properties:
|
||||||
|
* - {string} [subContentId] Identifies which data belongs to sub content.
|
||||||
|
* - {boolean} [preloaded=true] If the data should be loaded when content is loaded.
|
||||||
|
* - {boolean} [deleteOnChange=false] If the data should be invalidated when the content changes.
|
||||||
|
* - {function} [errorCallback] Callback with error as parameters.
|
||||||
|
* - {boolean} [async=true]
|
||||||
|
*/
|
||||||
|
H5P.setUserData = function (contentId, dataId, data, extras) {
|
||||||
|
var options = H5P.jQuery.extend(true, {}, {
|
||||||
|
subContentId: 0,
|
||||||
|
preloaded: true,
|
||||||
|
deleteOnChange: false,
|
||||||
|
async: true
|
||||||
|
}, extras);
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
if (options.errorCallback) {
|
||||||
|
options.errorCallback(err);
|
||||||
|
}
|
||||||
|
return; // Failed to serialize.
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = H5PIntegration.contents['cid-' + contentId];
|
||||||
|
if (!content.contentUserData) {
|
||||||
|
content.contentUserData = {};
|
||||||
|
}
|
||||||
|
var preloadedData = content.contentUserData;
|
||||||
|
if (preloadedData[options.subContentId] === undefined) {
|
||||||
|
preloadedData[options.subContentId] = {};
|
||||||
|
}
|
||||||
|
if (data === preloadedData[options.subContentId][dataId]) {
|
||||||
|
return; // No need to save this twice.
|
||||||
|
}
|
||||||
|
|
||||||
|
preloadedData[options.subContentId][dataId] = data;
|
||||||
|
contentUserDataAjax(contentId, dataId, options.subContentId, function (error, data) {
|
||||||
|
if (options.errorCallback && error) {
|
||||||
|
options.errorCallback(error);
|
||||||
|
}
|
||||||
|
}, data, options.preloaded, options.deleteOnChange, options.async);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete user data for given content.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {number} contentId What content to remove data for.
|
||||||
|
* @param {string} dataId Identifies the set of data for this content.
|
||||||
|
* @param {string} [subContentId] Identifies which data belongs to sub content.
|
||||||
|
*/
|
||||||
|
H5P.deleteUserData = function (contentId, dataId, subContentId) {
|
||||||
|
if (!subContentId) {
|
||||||
|
subContentId = 0; // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from preloaded/cache
|
||||||
|
var preloadedData = H5PIntegration.contents['cid-' + contentId].contentUserData;
|
||||||
|
if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId]) {
|
||||||
|
delete preloadedData[subContentId][dataId];
|
||||||
|
}
|
||||||
|
|
||||||
|
contentUserDataAjax(contentId, dataId, subContentId, undefined, null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Init H5P when page is fully loadded
|
||||||
|
$(document).ready(function () {
|
||||||
|
if (!H5P.preventInit) {
|
||||||
|
// Note that this start script has to be an external resource for it to
|
||||||
|
// load in correct order in IE9.
|
||||||
|
H5P.init(document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (H5PIntegration.saveFreq !== false) {
|
||||||
|
// Store the current state of the H5P when leaving the page.
|
||||||
|
H5P.$window.on('beforeunload', function () {
|
||||||
|
for (var i = 0; i < H5P.instances.length; i++) {
|
||||||
|
var instance = H5P.instances[i];
|
||||||
|
if (instance.getCurrentState instanceof Function ||
|
||||||
|
typeof instance.getCurrentState === 'function') {
|
||||||
|
var state = instance.getCurrentState();
|
||||||
|
if (state !== undefined) {
|
||||||
|
// Async is not used to prevent the request from being cancelled.
|
||||||
|
H5P.setUserData(instance.contentId, 'state', state, {deleteOnChange: true, async: false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relay events to top window.
|
||||||
|
if (H5P.isFramed && H5P.externalEmbed === false) {
|
||||||
|
H5P.externalDispatcher.on('*', window.top.H5P.externalDispatcher.trigger);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})(H5P.jQuery);
|
||||||
|
|
|
@ -36,7 +36,9 @@ html.h5p-iframe .h5p-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.h5p-fullscreen .h5p-content, .h5p-semi-fullscreen .h5p-content {
|
.h5p-content.h5p-no-frame,
|
||||||
|
.h5p-fullscreen .h5p-content,
|
||||||
|
.h5p-semi-fullscreen .h5p-content {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
.h5p-container {
|
.h5p-container {
|
||||||
|
@ -390,3 +392,18 @@ div.h5p-fullscreen {
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
}
|
}
|
||||||
|
.h5p-dialog-ok-button {
|
||||||
|
cursor: default;
|
||||||
|
float: right;
|
||||||
|
outline: none;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
padding: 0.25em 0.75em 0.125em;
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
.h5p-dialog-ok-button:hover,
|
||||||
|
.h5p-dialog-ok-button:focus {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
.h5p-dialog-ok-button:active {
|
||||||
|
background: #eeffee;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue