commit
ae512b2b03
|
@ -1,13 +1,12 @@
|
|||
This folder contains the h5p general library. The files within this folder are not specific to any framework.
|
||||
|
||||
Any interaction with LMS, CMS or other frameworks is done through interfaces. Plattforms needs to implement
|
||||
the following interfaces in order for the h5p libraries to work:
|
||||
|
||||
- TODO: Fill in here
|
||||
|
||||
In addition frameworks need to do the following:
|
||||
the H5PFrameworkInterface(in h5p.classes.php) and also do the following:
|
||||
|
||||
- Provide a form for uploading h5p packages.
|
||||
- Place the uploaded h5p packages in a temporary directory
|
||||
+++
|
||||
|
||||
See existing implementations for details. For instance the Drupal h5p module located on drupal.org/project/h5p
|
||||
|
||||
We will make available documentations and tutorials for creating platform integrations in the future
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<!doctype html>
|
||||
<html lang="<?php print $lang; ?>" class="h5p-iframe">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><?php print $content['title']; ?></title>
|
||||
<?php for ($i = 0, $s = count($scripts); $i < $s; $i++): ?>
|
||||
<script src="<?php print $scripts[$i]; ?>"></script>
|
||||
<?php endfor; ?>
|
||||
<?php for ($i = 0, $s = count($styles); $i < $s; $i++): ?>
|
||||
<link rel="stylesheet" href="<?php print $styles[$i]; ?>">
|
||||
<?php endfor; ?>
|
||||
</head>
|
||||
<body>
|
||||
<div class="h5p-content" data-content-id="<?php print $content['id']; ?>"></div>
|
||||
<script>
|
||||
H5PIntegration = <?php print json_encode($integration); ?>;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
BIN
fonts/h5p.eot
BIN
fonts/h5p.eot
Binary file not shown.
|
@ -1,31 +1,13 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>
|
||||
<json>
|
||||
{
|
||||
"fontFamily": "h5p-core-fonts",
|
||||
"majorVersion": 1,
|
||||
"minorVersion": 0,
|
||||
"fontURL": "http://h5p.org",
|
||||
"license": "MIT license",
|
||||
"licenseURL": "http://opensource.org/licenses/MIT",
|
||||
"designer": "Magnus Vik Magnussen",
|
||||
"designerURL": "",
|
||||
"version": "Version 1.0",
|
||||
"fontId": "h5p-core-fonts",
|
||||
"psName": "h5p-core-fonts",
|
||||
"subFamily": "Regular",
|
||||
"fullName": "h5p-core-fonts",
|
||||
"description": "Generated by IcoMoon"
|
||||
}
|
||||
</json>
|
||||
</metadata>
|
||||
<metadata>Generated by IcoMoon</metadata>
|
||||
<defs>
|
||||
<font id="h5p-core-fonts" horiz-adv-x="1024">
|
||||
<font id="h5p" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " d="" horiz-adv-x="512" />
|
||||
<glyph unicode="" d="M454.299 416.588l-116.917 116.917-84.781-84.707 201.696-201.697 317.097 317.097-84.781 84.706z" />
|
||||
<glyph unicode="" d="M660.48 703.59c-140.288 0-253.952-113.664-253.952-253.952 0-122.47 86.63-224.666 202.138-248.627v206.234h-86.835c-11.264 0-14.541 7.168-7.373 16.179l133.12 164.659c7.168 9.011 18.842 9.011 26.010 0l133.12-164.659c7.373-8.602 3.686-16.179-7.373-16.179h-86.835v-206.234c115.507 23.962 202.138 126.157 202.138 248.627-0.205 140.288-113.869 253.952-254.157 253.952z" horiz-adv-x="1321" />
|
||||
<glyph unicode="" d="M662.118 701.952c-140.288 0-253.952-113.664-253.952-253.952s113.664-253.952 253.952-253.952 253.952 113.664 253.952 253.952-113.664 253.952-253.952 253.952zM621.773 652.8h83.763v-65.946h-83.763v65.946zM748.749 273.92h-173.67v50.995h49.562v159.13h-49.562v50.995h133.53v-210.125h40.346v-50.995z" horiz-adv-x="1321" />
|
||||
<glyph unicode="" d="M925.491 236.646l-114.688 114.688c27.238 37.888 43.213 84.378 43.213 134.554 0 127.59-103.834 231.424-231.424 231.424s-231.424-103.834-231.424-231.424c0-127.59 103.834-231.424 231.424-231.424 50.176 0 96.666 15.974 134.554 43.213l114.688-114.688c5.325-5.325 13.926-5.325 19.251 0l34.406 34.406c5.325 5.12 5.325 13.926 0 19.251zM622.797 318.566c-92.365 0-167.117 74.752-167.117 167.117s74.752 167.117 167.117 167.117c92.365 0 167.117-74.752 167.117-167.117s-74.752-167.117-167.117-167.117z" horiz-adv-x="1321" />
|
||||
|
@ -37,6 +19,6 @@
|
|||
<glyph unicode="" d="M660.48 701.952c-140.288 0-253.952-113.664-253.952-253.952s113.664-253.952 253.952-253.952 253.952 113.664 253.952 253.952-113.664 253.952-253.952 253.952zM796.058 371.2c6.963-6.963 6.963-18.022 0-24.986l-33.997-33.997c-6.963-6.963-18.022-6.963-24.986 0l-76.8 76.8-76.595-76.595c-6.963-6.963-18.022-6.963-24.986 0l-33.997 33.997c-6.963 6.963-6.963 18.022 0 24.986l76.8 76.8-76.8 76.8c-6.963 6.963-6.963 18.022 0 24.986l33.997 33.997c6.963 6.963 18.022 6.963 24.986 0l76.8-76.8 76.8 76.8c6.963 6.963 18.022 6.963 24.986 0l33.997-33.997c6.963-6.963 6.963-18.022 0-24.986l-77.005-77.005 76.8-76.8z" horiz-adv-x="1321" />
|
||||
<glyph unicode="" d="M324.468 566.591c-4.186-4.186-2.79-8.372 3.255-8.835l111.615-11.86c6.046-0.697 10.464 3.721 9.766 9.766l-11.86 111.615c-0.697 6.046-4.65 7.441-8.835 3.255l-103.942-103.942zM399.112 634.259l-64.644 64.876c-4.186 4.186-11.161 4.186-15.581 0l-23.254-23.254c-4.186-4.186-4.186-11.161 0-15.581l64.876-64.876zM896.497 670.533c-4.186 4.186-8.372 2.79-8.835-3.255l-11.86-111.615c-0.697-6.046 3.721-10.464 9.766-9.766l111.615 11.86c6.046 0.697 7.441 4.65 3.255 8.835l-103.942 103.942zM964.165 595.657l64.876 64.876c4.186 4.186 4.186 11.161 0 15.581l-23.254 23.254c-4.186 4.186-11.161 4.186-15.581 0l-64.876-64.876zM1000.44 320.34c4.186 4.186 2.79 8.372-3.255 8.835l-111.615 11.86c-6.046 0.697-10.464-3.721-9.766-9.766l11.86-111.615c0.697-6.046 4.65-7.441 8.835-3.255l103.942 103.942zM925.564 252.441l64.876-64.876c4.186-4.186 11.161-4.186 15.581 0l23.254 23.254c4.186 4.186 4.186 11.161 0 15.581l-64.876 64.876zM428.41 216.398c4.186-4.186 8.372-2.79 8.835 3.255l11.86 111.615c0.697 6.046-3.721 10.464-9.766 9.766l-111.615-11.86c-6.046-0.697-7.441-4.65-3.255-8.835l103.942-103.942zM360.51 291.273l-64.876-64.876c-4.186-4.186-4.186-11.161 0-15.581l23.254-23.254c4.186-4.186 11.161-4.186 15.581 0l64.876 64.876zM477.939 572.404v-248.809h365.076v248.809h-365.076zM797.905 368.707h-274.854v158.355h274.621v-158.355z" horiz-adv-x="1321" />
|
||||
<glyph unicode="" d="M599.553 337.314c4.419-3.023 8.138-9.998 8.138-15.581v-58.599c0-5.348-3.721-7.441-8.138-4.419l-220.906 148.123c-4.419 3.023-8.138 9.998-8.138 15.348v50.228c0 5.348 3.721 12.323 8.138 15.581l220.906 149.517c4.419 3.023 8.138 1.162 8.138-4.419v-58.599c0-5.348-3.721-12.556-8.138-15.581l-152.773-106.731c-4.419-3.023-4.419-8.138 0-11.161l152.773-103.71zM874.175 440.559c4.419 3.023 4.65 8.138 0 11.161l-152.773 106.731c-4.419 3.023-8.138 10.232-8.138 15.581v58.599c0 5.348 3.721 7.441 8.138 4.419l220.906-149.517c4.419-3.023 8.138-9.998 8.138-15.581v-50.228c0-5.348-3.721-12.323-8.138-15.348l-220.906-148.123c-4.419-3.023-8.138-1.162-8.138 4.419v58.599c0 5.348 3.721 12.323 8.138 15.581l152.773 103.71z" horiz-adv-x="1321" />
|
||||
<glyph unicode="" d="M502.17 522.957c-12.902 0-16.998-8.192-8.806-18.227l152.166-188.006c8.192-10.035 21.504-10.035 29.696 0l152.166 188.006c8.192 10.035 4.301 18.227-8.806 18.227h-316.416zM719.462 512.512v139.878c0 12.902-10.65 23.552-23.552 23.552h-70.656c-12.902 0-23.552-10.65-23.552-23.552v-139.878zM798.106 375.91c-8.602 0-20.070-5.53-25.6-12.288l-75.162-92.979c-5.325-6.758-15.36-16.589-22.118-21.914 0 0-4.506-3.686-14.746-3.686s-14.746 3.686-14.746 3.686c-6.758 5.325-16.589 15.36-22.118 21.914l-75.162 92.979c-5.325 6.758-16.998 12.288-25.6 12.288h-130.253c-8.602 0-15.77-6.963-15.77-15.77v-141.722c0-8.602 6.963-15.77 15.77-15.77h535.962c8.602 0 15.77 6.963 15.77 15.77v141.722c0 8.602-6.963 15.77-15.77 15.77h-130.458zM448.102 261.018c-15.565 0-28.262 12.698-28.262 28.262 0 15.565 12.698 28.262 28.262 28.262s28.262-12.698 28.262-28.262c-0.205-15.565-12.698-28.262-28.262-28.262z" horiz-adv-x="1321" />
|
||||
<glyph unicode="" d="M502.17 522.957c-12.902 0-16.998-8.192-8.806-18.227l152.166-188.006c8.192-10.035 21.504-10.035 29.696 0l152.166 188.006c8.192 10.035 4.301 18.227-8.806 18.227h-316.416zM719.462 512.512v139.878c0 12.902-10.65 23.552-23.552 23.552h-70.656c-12.902 0-23.552-10.65-23.552-23.552v-139.878zM798.106 375.91c-8.602 0-20.070-5.53-25.6-12.288l-75.162-92.979c-5.325-6.758-15.36-16.589-22.118-21.914 0 0-4.506-3.686-14.746-3.686s-14.746 3.686-14.746 3.686c-6.758 5.325-16.589 15.36-22.118 21.914l-75.162 92.979c-5.325 6.758-16.998 12.288-25.6 12.288h-130.253c-8.602 0-15.77-6.963-15.77-15.77v-141.722c0-8.602 6.963-15.77 15.77-15.77h535.962c8.602 0 15.77 6.963 15.77 15.77v141.722c0 8.602-6.963 15.77-15.77 15.77h-130.458zM448.102 261.018c-15.565 0-28.262 12.698-28.262 28.262s12.698 28.262 28.262 28.262 28.262-12.698 28.262-28.262c-0.205-15.565-12.698-28.262-28.262-28.262z" horiz-adv-x="1321" />
|
||||
<glyph unicode="" d="M742.605 448l107.11 107.11c9.626 9.626 9.626 25.19 0 34.816l-47.309 47.309c-9.626 9.626-25.19 9.626-34.816 0l-107.11-107.11-107.11 107.11c-9.626 9.626-25.19 9.626-34.816 0l-47.309-47.309c-9.626-9.626-9.626-25.19 0-34.816l107.11-107.11-107.11-107.11c-9.626-9.626-9.626-25.19 0-34.816l47.309-47.309c9.626-9.626 25.19-9.626 34.816 0l107.11 107.11 107.11-107.11c9.626-9.626 25.19-9.626 34.816 0l47.309 47.309c9.626 9.626 9.626 25.19 0 34.816l-107.11 107.11z" horiz-adv-x="1321" />
|
||||
</font></defs></svg>
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
BIN
fonts/h5p.ttf
BIN
fonts/h5p.ttf
Binary file not shown.
|
@ -9,7 +9,7 @@ class H5PDevelopment {
|
|||
const MODE_CONTENT = 1;
|
||||
const MODE_LIBRARY = 2;
|
||||
|
||||
private $h5pF, $libraries, $language;
|
||||
private $h5pF, $libraries, $language, $filesPath;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
|
@ -23,6 +23,7 @@ class H5PDevelopment {
|
|||
public function __construct($H5PFramework, $filesPath, $language, $libraries = NULL) {
|
||||
$this->h5pF = $H5PFramework;
|
||||
$this->language = $language;
|
||||
$this->filesPath = $filesPath;
|
||||
if ($libraries !== NULL) {
|
||||
$this->libraries = $libraries;
|
||||
}
|
||||
|
@ -86,13 +87,14 @@ class H5PDevelopment {
|
|||
$library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']);
|
||||
$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;
|
||||
}
|
||||
|
||||
// TODO: Should we remove libraries without files? Not really needed, but must be cleaned up some time, right?
|
||||
|
||||
// Go trough libraries and insert dependencies. Missing deps. will just be ignored and not available. (I guess?!)
|
||||
$this->h5pF->lockDependencyStorage();
|
||||
foreach ($this->libraries as $library) {
|
||||
$this->h5pF->deleteLibraryDependencies($library['libraryId']);
|
||||
// This isn't optimal, but without it we would get duplicate warnings.
|
||||
|
@ -104,6 +106,7 @@ class H5PDevelopment {
|
|||
}
|
||||
}
|
||||
}
|
||||
$this->h5pF->unlockDependencyStorage();
|
||||
// TODO: Deps must be inserted into h5p_nodes_libraries as well... ? But only if they are used?!
|
||||
}
|
||||
|
||||
|
@ -137,12 +140,10 @@ class H5PDevelopment {
|
|||
*/
|
||||
public function getSemantics($name, $majorVersion, $minorVersion) {
|
||||
$library = H5PDevelopment::libraryToString($name, $majorVersion, $minorVersion);
|
||||
|
||||
if (isset($this->libraries[$library]) === FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $this->getFileContents($this->libraries[$library]['path'] . '/semantics.json');
|
||||
return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/semantics.json');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -160,7 +161,7 @@ class H5PDevelopment {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
return $this->getFileContents($this->libraries[$library]['path'] . '/language/' . $language . '.json');
|
||||
return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/language/' . $language . '.json');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
515
h5p.classes.php
515
h5p.classes.php
|
@ -15,6 +15,7 @@ interface H5PFrameworkInterface {
|
|||
*/
|
||||
public function getPlatformInfo();
|
||||
|
||||
|
||||
/**
|
||||
* Fetches a file from a remote server using HTTP GET
|
||||
*
|
||||
|
@ -73,12 +74,6 @@ interface H5PFrameworkInterface {
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
@ -252,6 +247,13 @@ interface H5PFrameworkInterface {
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
@ -408,6 +410,17 @@ interface H5PFrameworkInterface {
|
|||
*/
|
||||
public function deleteLibraryDependencies($libraryId);
|
||||
|
||||
/**
|
||||
* Start an atomic operation against the dependency storage
|
||||
*/
|
||||
public function lockDependencyStorage();
|
||||
|
||||
/**
|
||||
* Stops an atomic operation against the dependency storage
|
||||
*/
|
||||
public function unlockDependencyStorage();
|
||||
|
||||
|
||||
/**
|
||||
* Delete a library from database and file system
|
||||
*
|
||||
|
@ -759,15 +772,14 @@ class H5PValidator {
|
|||
// When upgrading, we opnly add allready installed libraries,
|
||||
// and new dependent libraries
|
||||
$upgrades = array();
|
||||
foreach ($libraries as &$library) {
|
||||
foreach ($libraries as $libString => &$library) {
|
||||
// Is this library already installed?
|
||||
if ($this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']) !== FALSE) {
|
||||
$upgrades[H5PCore::libraryToString($library)] = $library;
|
||||
if ($this->h5pC->getLibraryId($library, $libString) !== FALSE) {
|
||||
$upgrades[$libString] = $library;
|
||||
}
|
||||
}
|
||||
while ($missingLibraries = $this->getMissingLibraries($upgrades)) {
|
||||
foreach ($missingLibraries as $missing) {
|
||||
$libString = H5PCore::libraryToString($missing);
|
||||
foreach ($missingLibraries as $libString => $missing) {
|
||||
$library = $libraries[$libString];
|
||||
if ($library) {
|
||||
$upgrades[$libString] = $library;
|
||||
|
@ -787,15 +799,15 @@ class H5PValidator {
|
|||
}
|
||||
|
||||
$missingLibraries = $this->getMissingLibraries($libraries);
|
||||
foreach ($missingLibraries as $missing) {
|
||||
if ($this->h5pF->getLibraryId($missing['machineName'], $missing['majorVersion'], $missing['minorVersion'])) {
|
||||
unset($missingLibraries[H5PCore::libraryToString($missing)]);
|
||||
foreach ($missingLibraries as $libString => $missing) {
|
||||
if ($this->h5pC->getLibraryId($missing, $libString)) {
|
||||
unset($missingLibraries[$libString]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($missingLibraries)) {
|
||||
foreach ($missingLibraries as $library) {
|
||||
$this->h5pF->setErrorMessage($this->h5pF->t('Missing required library @library', array('@library' => H5PCore::libraryToString($library))));
|
||||
foreach ($missingLibraries as $libString => $library) {
|
||||
$this->h5pF->setErrorMessage($this->h5pF->t('Missing required library @library', array('@library' => $libString)));
|
||||
}
|
||||
if (!$this->h5pF->mayUpdateLibraries()) {
|
||||
$this->h5pF->setInfoMessage($this->h5pF->t("Note that the libraries may exist in the file you uploaded, but you're not allowed to upload new libraries. Contact the site administrator about this."));
|
||||
|
@ -926,8 +938,9 @@ class H5PValidator {
|
|||
private function getMissingDependencies($dependencies, $libraries) {
|
||||
$missing = array();
|
||||
foreach ($dependencies as $dependency) {
|
||||
if (!isset($libraries[H5PCore::libraryToString($dependency)])) {
|
||||
$missing[H5PCore::libraryToString($dependency)] = $dependency;
|
||||
$libString = H5PCore::libraryToString($dependency);
|
||||
if (!isset($libraries[$libString])) {
|
||||
$missing[$libString] = $dependency;
|
||||
}
|
||||
}
|
||||
return $missing;
|
||||
|
@ -1227,74 +1240,15 @@ class H5PStorage {
|
|||
* TRUE if one or more libraries were updated
|
||||
* FALSE otherwise
|
||||
*/
|
||||
public function savePackage($content = NULL, $contentMainId = NULL, $skipContent = FALSE, $upgradeOnly = FALSE) {
|
||||
// Save the libraries we processed during validation
|
||||
$library_saved = FALSE;
|
||||
$upgradedLibsCount = 0;
|
||||
$mayUpdateLibraries = $this->h5pF->mayUpdateLibraries();
|
||||
|
||||
foreach ($this->h5pC->librariesJsonData as &$library) {
|
||||
$libraryId = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']);
|
||||
$library['saveDependencies'] = TRUE;
|
||||
|
||||
if (!$libraryId) {
|
||||
$new = TRUE;
|
||||
}
|
||||
elseif ($this->h5pF->isPatchedLibrary($library)) {
|
||||
$new = FALSE;
|
||||
$library['libraryId'] = $libraryId;
|
||||
}
|
||||
else {
|
||||
$library['libraryId'] = $libraryId;
|
||||
// We already have the same or a newer version of this library
|
||||
$library['saveDependencies'] = FALSE;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$mayUpdateLibraries) {
|
||||
// This shouldn't happen, but just to be safe...
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->h5pF->saveLibraryData($library, $new);
|
||||
|
||||
$libraries_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'libraries';
|
||||
if (!is_dir($libraries_path)) {
|
||||
mkdir($libraries_path, 0777, true);
|
||||
}
|
||||
$destination_path = $libraries_path . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE);
|
||||
H5PCore::deleteFileTree($destination_path);
|
||||
rename($library['uploadDirectory'], $destination_path);
|
||||
|
||||
$library_saved = TRUE;
|
||||
}
|
||||
|
||||
foreach ($this->h5pC->librariesJsonData as &$library) {
|
||||
if ($library['saveDependencies']) {
|
||||
$this->h5pF->deleteLibraryDependencies($library['libraryId']);
|
||||
if (isset($library['preloadedDependencies'])) {
|
||||
$this->h5pF->saveLibraryDependencies($library['libraryId'], $library['preloadedDependencies'], 'preloaded');
|
||||
}
|
||||
if (isset($library['dynamicDependencies'])) {
|
||||
$this->h5pF->saveLibraryDependencies($library['libraryId'], $library['dynamicDependencies'], 'dynamic');
|
||||
}
|
||||
if (isset($library['editorDependencies'])) {
|
||||
$this->h5pF->saveLibraryDependencies($library['libraryId'], $library['editorDependencies'], 'editor');
|
||||
}
|
||||
|
||||
// Make sure libraries dependencies, parameter filtering and export files gets regenerated for all content who uses this library.
|
||||
$this->h5pF->clearFilteredParameters($library['libraryId']);
|
||||
|
||||
$upgradedLibsCount++;
|
||||
}
|
||||
public function savePackage($content = NULL, $contentMainId = NULL, $skipContent = FALSE, $options = array()) {
|
||||
if ($this->h5pF->mayUpdateLibraries()) {
|
||||
// Save the libraries we processed during validation
|
||||
$this->saveLibraries();
|
||||
}
|
||||
|
||||
if (!$skipContent) {
|
||||
$current_path = $this->h5pF->getUploadedH5pFolderPath() . DIRECTORY_SEPARATOR . 'content';
|
||||
|
||||
// Find out which libraries are used by this package/content
|
||||
$librariesInUse = array();
|
||||
$this->h5pC->findLibraryDependencies($librariesInUse, $this->h5pC->mainJsonData);
|
||||
$basePath = $this->h5pF->getUploadedH5pFolderPath();
|
||||
$current_path = $basePath . DIRECTORY_SEPARATOR . 'content';
|
||||
|
||||
// Save content
|
||||
if ($content === NULL) {
|
||||
|
@ -1303,34 +1257,141 @@ class H5PStorage {
|
|||
if (!is_array($content)) {
|
||||
$content = array('id' => $content);
|
||||
}
|
||||
$content['library'] = $librariesInUse['preloaded-' . $this->h5pC->mainJsonData['mainLibrary']]['library'];
|
||||
|
||||
// Find main library version
|
||||
foreach ($this->h5pC->mainJsonData['preloadedDependencies'] as $dep) {
|
||||
if ($dep['machineName'] === $this->h5pC->mainJsonData['mainLibrary']) {
|
||||
$dep['libraryId'] = $this->h5pC->getLibraryId($dep);
|
||||
$content['library'] = $dep;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$content['params'] = file_get_contents($current_path . DIRECTORY_SEPARATOR . 'content.json');
|
||||
|
||||
if (isset($options['disable'])) {
|
||||
$content['disable'] = $options['disable'];
|
||||
}
|
||||
$contentId = $this->h5pC->saveContent($content, $contentMainId);
|
||||
$this->contentId = $contentId;
|
||||
|
||||
$contents_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content';
|
||||
$contents_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content';
|
||||
if (!is_dir($contents_path)) {
|
||||
mkdir($contents_path, 0777, true);
|
||||
}
|
||||
|
||||
// Move the content folder
|
||||
$destination_path = $contents_path . DIRECTORY_SEPARATOR . $contentId;
|
||||
@rename($current_path, $destination_path);
|
||||
$this->h5pC->copyFileTree($current_path, $destination_path);
|
||||
|
||||
// Save the content library dependencies
|
||||
$this->h5pF->saveLibraryUsage($contentId, $librariesInUse);
|
||||
H5PCore::deleteFileTree($this->h5pF->getUploadedH5pFolderPath());
|
||||
// Remove temp content folder
|
||||
H5PCore::deleteFileTree($basePath);
|
||||
}
|
||||
|
||||
// Update supported library list if neccessary:
|
||||
$this->h5pC->validateLibrarySupport(TRUE);
|
||||
}
|
||||
|
||||
if ($upgradeOnly) {
|
||||
// TODO - support translation
|
||||
$this->h5pF->setInfoMessage($this->h5pF->t('@num libraries were upgraded!', array('@num' => $upgradedLibsCount)));
|
||||
/**
|
||||
* Helps savePackage.
|
||||
*
|
||||
* @return int Number of libraries saved
|
||||
*/
|
||||
private function saveLibraries() {
|
||||
// Keep track of the number of libraries that have been saved
|
||||
$newOnes = 0;
|
||||
$oldOnes = 0;
|
||||
|
||||
// Find libraries directory and make sure it exists
|
||||
$libraries_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'libraries';
|
||||
if (!is_dir($libraries_path)) {
|
||||
mkdir($libraries_path, 0777, true);
|
||||
}
|
||||
|
||||
return $library_saved;
|
||||
// Go through libraries that came with this package
|
||||
foreach ($this->h5pC->librariesJsonData as $libString => &$library) {
|
||||
// Find local library identifier
|
||||
$libraryId = $this->h5pC->getLibraryId($library, $libString);
|
||||
|
||||
// Assume new library
|
||||
$new = TRUE;
|
||||
if ($libraryId) {
|
||||
// Found old library
|
||||
$library['libraryId'] = $libraryId;
|
||||
|
||||
if ($this->h5pF->isPatchedLibrary($library)) {
|
||||
// This is a newer version than ours. Upgrade!
|
||||
$new = FALSE;
|
||||
}
|
||||
else {
|
||||
$library['saveDependencies'] = FALSE;
|
||||
// This is an older version, no need to save.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate that the dependencies of this library should be saved.
|
||||
$library['saveDependencies'] = TRUE;
|
||||
|
||||
// Save library meta data
|
||||
$this->h5pF->saveLibraryData($library, $new);
|
||||
|
||||
// Make sure destination dir is free
|
||||
$destination_path = $libraries_path . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE);
|
||||
H5PCore::deleteFileTree($destination_path);
|
||||
|
||||
// Move library folder
|
||||
$this->h5pC->copyFileTree($library['uploadDirectory'], $destination_path);
|
||||
H5PCore::deleteFileTree($library['uploadDirectory']);
|
||||
|
||||
if ($new) {
|
||||
$newOnes++;
|
||||
}
|
||||
else {
|
||||
$oldOnes++;
|
||||
}
|
||||
}
|
||||
|
||||
// Go through the libraries again to save dependencies.
|
||||
foreach ($this->h5pC->librariesJsonData as &$library) {
|
||||
if (!$library['saveDependencies']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Should the table be locked for this operation?
|
||||
|
||||
// Remove any old dependencies
|
||||
$this->h5pF->deleteLibraryDependencies($library['libraryId']);
|
||||
|
||||
// Insert the different new ones
|
||||
if (isset($library['preloadedDependencies'])) {
|
||||
$this->h5pF->saveLibraryDependencies($library['libraryId'], $library['preloadedDependencies'], 'preloaded');
|
||||
}
|
||||
if (isset($library['dynamicDependencies'])) {
|
||||
$this->h5pF->saveLibraryDependencies($library['libraryId'], $library['dynamicDependencies'], 'dynamic');
|
||||
}
|
||||
if (isset($library['editorDependencies'])) {
|
||||
$this->h5pF->saveLibraryDependencies($library['libraryId'], $library['editorDependencies'], 'editor');
|
||||
}
|
||||
|
||||
// Make sure libraries dependencies, parameter filtering and export files gets regenerated for all content who uses this library.
|
||||
$this->h5pF->clearFilteredParameters($library['libraryId']);
|
||||
}
|
||||
|
||||
// Tell the user what we've done.
|
||||
if ($newOnes && $oldOnes) {
|
||||
$message = $this->h5pF->t('Added %new new H5P libraries and updated %old old.', array('%new' => $newOnes, '%old' => $oldOnes));
|
||||
}
|
||||
elseif ($newOnes) {
|
||||
$message = $this->h5pF->t('Added %new new H5P libraries.', array('%new' => $newOnes));
|
||||
}
|
||||
elseif ($oldOnes) {
|
||||
$message = $this->h5pF->t('Updated %old H5P libraries.', array('%old' => $oldOnes));
|
||||
}
|
||||
|
||||
if (isset($message)) {
|
||||
$this->h5pF->setInfoMessage($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1340,8 +1401,9 @@ class H5PStorage {
|
|||
* The content id
|
||||
*/
|
||||
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);
|
||||
// TODO: Delete export?
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1355,9 +1417,9 @@ class H5PStorage {
|
|||
* TRUE if one or more libraries were updated
|
||||
* FALSE otherwise
|
||||
*/
|
||||
public function updatePackage($contentId, $contentMainId = NULL) {
|
||||
public function updatePackage($contentId, $contentMainId = NULL, $options) {
|
||||
$this->deletePackage($contentId);
|
||||
return $this->savePackage($contentId, $contentMainId);
|
||||
return $this->savePackage($contentId, $contentMainId, FALSE, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1374,8 +1436,8 @@ class H5PStorage {
|
|||
* The main id of the new content (used in frameworks that support revisioning)
|
||||
*/
|
||||
public function copyPackage($contentId, $copyFromId, $contentMainId = NULL) {
|
||||
$source_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $copyFromId;
|
||||
$destination_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId;
|
||||
$source_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $copyFromId;
|
||||
$destination_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId;
|
||||
$this->h5pC->copyFileTree($source_path, $destination_path);
|
||||
|
||||
$this->h5pF->copyLibraryUsage($contentId, $copyFromId, $contentMainId);
|
||||
|
@ -1411,7 +1473,7 @@ Class H5PExport {
|
|||
* @return string
|
||||
*/
|
||||
public function createExportFile($content) {
|
||||
$h5pDir = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR;
|
||||
$h5pDir = $this->h5pC->path . DIRECTORY_SEPARATOR;
|
||||
$tempPath = $h5pDir . 'temp' . DIRECTORY_SEPARATOR . $content['id'];
|
||||
$zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $content['id'] . '.h5p';
|
||||
|
||||
|
@ -1431,8 +1493,6 @@ Class H5PExport {
|
|||
// Build h5p.json
|
||||
$h5pJson = array (
|
||||
'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',
|
||||
'mainLibrary' => $content['library']['name'],
|
||||
'embedTypes' => $embedTypes,
|
||||
|
@ -1443,7 +1503,7 @@ Class H5PExport {
|
|||
$library = $dependency['library'];
|
||||
|
||||
// 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'];
|
||||
$this->h5pC->copyFileTree($source, $destination);
|
||||
|
||||
|
@ -1463,28 +1523,52 @@ Class H5PExport {
|
|||
$results = print_r(json_encode($h5pJson), true);
|
||||
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.
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($zipPath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE);
|
||||
|
||||
// Get all files and folders in $tempPath
|
||||
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($tempPath . DIRECTORY_SEPARATOR));
|
||||
// Add files to zip
|
||||
foreach ($iterator as $key => $value) {
|
||||
$test = '.';
|
||||
// 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]);
|
||||
}
|
||||
// Add all the files from the tmp dir.
|
||||
foreach ($files as $file) {
|
||||
// Please note that the zip format has no concept of folders, we must
|
||||
// use forward slashes to separate our directories.
|
||||
$zip->addFile($file->absolutePath, $file->relativePath);
|
||||
}
|
||||
|
||||
// Close zip and remove temp dir
|
||||
$zip->close();
|
||||
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
|
||||
*
|
||||
|
@ -1492,7 +1576,7 @@ Class H5PExport {
|
|||
* Identifier for the H5P
|
||||
*/
|
||||
public function deleteExport($contentId) {
|
||||
$h5pDir = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR;
|
||||
$h5pDir = $this->h5pC->path . DIRECTORY_SEPARATOR;
|
||||
$zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $contentId . '.h5p';
|
||||
if (file_exists($zipPath)) {
|
||||
unlink($zipPath);
|
||||
|
@ -1526,7 +1610,7 @@ class H5PCore {
|
|||
|
||||
public static $coreApi = array(
|
||||
'majorVersion' => 1,
|
||||
'minorVersion' => 3
|
||||
'minorVersion' => 5
|
||||
);
|
||||
public static $styles = array(
|
||||
'styles/h5p.css',
|
||||
|
@ -1534,6 +1618,9 @@ class H5PCore {
|
|||
public static $scripts = array(
|
||||
'js/jquery.js',
|
||||
'js/h5p.js',
|
||||
'js/h5p-event-dispatcher.js',
|
||||
'js/h5p-x-api-event.js',
|
||||
'js/h5p-x-api.js',
|
||||
);
|
||||
public static $adminScripts = array(
|
||||
'js/jquery.js',
|
||||
|
@ -1548,6 +1635,22 @@ class H5PCore {
|
|||
|
||||
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
|
||||
*
|
||||
|
@ -1558,16 +1661,17 @@ class H5PCore {
|
|||
* @param boolean $export enabled?
|
||||
* @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->path = $path;
|
||||
$this->url = $url;
|
||||
$this->exportEnabled = $export;
|
||||
$this->development_mode = $development_mode;
|
||||
|
||||
if ($development_mode & H5PDevelopment::MODE_LIBRARY) {
|
||||
$this->h5pD = new H5PDevelopment($this->h5pF, $path, $language);
|
||||
$this->h5pD = new H5PDevelopment($this->h5pF, $path . '/', $language);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1585,6 +1689,9 @@ class H5PCore {
|
|||
$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'];
|
||||
}
|
||||
|
||||
|
@ -1658,8 +1765,6 @@ class H5PCore {
|
|||
// Recreate export file
|
||||
$exporter = new H5PExport($this->h5pF, $this);
|
||||
$exporter->createExportFile($content);
|
||||
|
||||
// TODO: Should we rather create the file once first accessed, like imagecache?
|
||||
}
|
||||
|
||||
// Cache.
|
||||
|
@ -1698,8 +1803,9 @@ class H5PCore {
|
|||
* @param array $dependency
|
||||
* @param string $type
|
||||
* @param array $assets
|
||||
* @param string $prefix Optional. Make paths relative to another dir.
|
||||
*/
|
||||
private function getDependencyAssets($dependency, $type, &$assets) {
|
||||
private function getDependencyAssets($dependency, $type, &$assets, $prefix = '') {
|
||||
// Check if dependency has any files of this type
|
||||
if (empty($dependency[$type]) || $dependency[$type][0] === '') {
|
||||
return;
|
||||
|
@ -1709,10 +1815,9 @@ class H5PCore {
|
|||
if ($type === 'preloadedCss' && (isset($dependency['dropCss']) && $dependency['dropCss'] === '1')) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($dependency[$type] as $file) {
|
||||
$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']
|
||||
);
|
||||
}
|
||||
|
@ -1728,7 +1833,19 @@ class H5PCore {
|
|||
$urls = array();
|
||||
|
||||
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;
|
||||
|
@ -1738,23 +1855,23 @@ class H5PCore {
|
|||
* Return file paths for all dependecies files.
|
||||
*
|
||||
* @param array $dependencies
|
||||
* @param string $prefix Optional. Make paths relative to another dir.
|
||||
* @return array files.
|
||||
*/
|
||||
public function getDependenciesFiles($dependencies) {
|
||||
public function getDependenciesFiles($dependencies, $prefix = '') {
|
||||
$files = array(
|
||||
'scripts' => array(),
|
||||
'styles' => array()
|
||||
);
|
||||
foreach ($dependencies as $dependency) {
|
||||
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['preloadedCss'] = explode(',', $dependency['preloadedCss']);
|
||||
}
|
||||
|
||||
$dependency['version'] = "?ver={$dependency['majorVersion']}.{$dependency['minorVersion']}.{$dependency['patchVersion']}";
|
||||
$this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts']);
|
||||
$this->getDependencyAssets($dependency, 'preloadedCss', $files['styles']);
|
||||
$this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts'], $prefix);
|
||||
$this->getDependencyAssets($dependency, 'preloadedCss', $files['styles'], $prefix);
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
@ -1825,9 +1942,12 @@ class H5PCore {
|
|||
*
|
||||
* @param array $librariesUsed Flat list of all dependencies.
|
||||
* @param array $library To find all dependencies for.
|
||||
* @param bool $editor Used interally to force all preloaded sub dependencies of an editor dependecy to be editor dependencies.
|
||||
* @param int $nextWeight An integer determining the order of the libraries
|
||||
* when they are loaded
|
||||
* @param bool $editor Used interally to force all preloaded sub dependencies
|
||||
* of an editor dependecy to be editor dependencies.
|
||||
*/
|
||||
public function findLibraryDependencies(&$dependencies, $library, $editor = FALSE) {
|
||||
public function findLibraryDependencies(&$dependencies, $library, $nextWeight = 1, $editor = FALSE) {
|
||||
foreach (array('dynamic', 'preloaded', 'editor') as $type) {
|
||||
$property = $type . 'Dependencies';
|
||||
if (!isset($library[$property])) {
|
||||
|
@ -1851,7 +1971,8 @@ class H5PCore {
|
|||
'library' => $dependencyLibrary,
|
||||
'type' => $type
|
||||
);
|
||||
$this->findLibraryDependencies($dependencies, $dependencyLibrary, $type === 'editor');
|
||||
$nextWeight = $this->findLibraryDependencies($dependencies, $dependencyLibrary, $nextWeight, $type === 'editor');
|
||||
$dependencies[$dependencyKey]['weight'] = $nextWeight++;
|
||||
}
|
||||
else {
|
||||
// This site is missing a dependency!
|
||||
|
@ -1859,6 +1980,7 @@ class H5PCore {
|
|||
}
|
||||
}
|
||||
}
|
||||
return $nextWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1924,7 +2046,7 @@ class H5PCore {
|
|||
|
||||
@mkdir($destination);
|
||||
while (false !== ($file = readdir($dir))) {
|
||||
if (($file != '.') && ($file != '..')) {
|
||||
if (($file != '.') && ($file != '..') && $file != '.git' && $file != '.gitignore') {
|
||||
if (is_dir($source . DIRECTORY_SEPARATOR . $file)) {
|
||||
$this->copyFileTree($source . DIRECTORY_SEPARATOR . $file, $destination . DIRECTORY_SEPARATOR . $file);
|
||||
}
|
||||
|
@ -2159,8 +2281,6 @@ class H5PCore {
|
|||
/**
|
||||
* Helper function for creating markup for the unsupported libraries list
|
||||
*
|
||||
* TODO: Make help text translatable
|
||||
*
|
||||
* @return string Html
|
||||
* */
|
||||
public function createMarkupForUnsupportedLibraryList($libraries) {
|
||||
|
@ -2197,10 +2317,83 @@ class H5PCore {
|
|||
}
|
||||
}
|
||||
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
|
||||
private $libraryIdMap = array();
|
||||
|
||||
/**
|
||||
* Small helper for getting the library's ID.
|
||||
*
|
||||
* @param array $library
|
||||
* @param string [$libString]
|
||||
* @return int Identifier, or FALSE if non-existent
|
||||
*/
|
||||
public function getLibraryId($library, $libString = NULL) {
|
||||
if (!$libString) {
|
||||
$libString = self::libraryToString($library);
|
||||
}
|
||||
|
||||
if (!isset($libraryIdMap[$libString])) {
|
||||
$libraryIdMap[$libString] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']);
|
||||
}
|
||||
|
||||
return $libraryIdMap[$libString];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2209,8 +2402,7 @@ class H5PCore {
|
|||
class H5PContentValidator {
|
||||
public $h5pF;
|
||||
public $h5pC;
|
||||
private $typeMap;
|
||||
private $libraries, $dependencies;
|
||||
private $typeMap, $libraries, $dependencies, $nextWeight;
|
||||
|
||||
/**
|
||||
* Constructor for the H5PContentValidator
|
||||
|
@ -2236,10 +2428,10 @@ class H5PContentValidator {
|
|||
'select' => 'validateSelect',
|
||||
'library' => 'validateLibrary',
|
||||
);
|
||||
$this->nextWeight = 1;
|
||||
|
||||
// Keep track of the libraries we load to avoid loading it multiple times.
|
||||
$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.
|
||||
$this->dependencies = array();
|
||||
|
@ -2562,6 +2754,9 @@ class H5PContentValidator {
|
|||
$found = FALSE;
|
||||
foreach ($semantics->fields as $field) {
|
||||
if ($field->name == $key) {
|
||||
if (isset($semantics->optional) && $semantics->optional) {
|
||||
$field->optional = TRUE;
|
||||
}
|
||||
$function = $this->typeMap[$field->type];
|
||||
$found = TRUE;
|
||||
break;
|
||||
|
@ -2581,16 +2776,18 @@ class H5PContentValidator {
|
|||
else {
|
||||
// If validator is not found, something exists in content that does
|
||||
// not have a corresponding semantics field. Remove it.
|
||||
$this->h5pF->setErrorMessage($this->h5pF->t('H5P internal error: no validator exists for @key', array('@key' => $key)));
|
||||
// $this->h5pF->setErrorMessage($this->h5pF->t('H5P internal error: no validator exists for @key', array('@key' => $key)));
|
||||
unset($group->$key);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($semantics->fields as $field) {
|
||||
if (!(isset($field->optional) && $field->optional)) {
|
||||
// Check if field is in group.
|
||||
if (! property_exists($group, $field->name)) {
|
||||
$this->h5pF->setErrorMessage($this->h5pF->t('No value given for mandatory field ' . $field->name));
|
||||
if (!(isset($semantics->optional) && $semantics->optional)) {
|
||||
foreach ($semantics->fields as $field) {
|
||||
if (!(isset($field->optional) && $field->optional)) {
|
||||
// Check if field is in group.
|
||||
if (! property_exists($group, $field->name)) {
|
||||
//$this->h5pF->setErrorMessage($this->h5pF->t('No value given for mandatory field ' . $field->name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2614,17 +2811,6 @@ class H5PContentValidator {
|
|||
$library = $this->h5pC->loadLibrary($libspec['machineName'], $libspec['majorVersion'], $libspec['minorVersion']);
|
||||
$library['semantics'] = $this->h5pC->loadLibrarySemantics($libspec['machineName'], $libspec['majorVersion'], $libspec['minorVersion']);
|
||||
$this->libraries[$value->library] = $library;
|
||||
|
||||
// Find all dependencies for this library
|
||||
$depkey = 'preloaded-' . $libspec['machineName'];
|
||||
if (!isset($this->dependencies[$depkey])) {
|
||||
$this->dependencies[$depkey] = array(
|
||||
'library' => $library,
|
||||
'type' => 'preloaded'
|
||||
);
|
||||
|
||||
$this->h5pC->findLibraryDependencies($this->dependencies, $library);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$library = $this->libraries[$value->library];
|
||||
|
@ -2634,11 +2820,26 @@ class H5PContentValidator {
|
|||
'type' => 'group',
|
||||
'fields' => $library['semantics'],
|
||||
), FALSE);
|
||||
$validkeys = array('library', 'params');
|
||||
$validkeys = array('library', 'params', 'subContentId');
|
||||
if (isset($semantics->extraAttributes)) {
|
||||
$validkeys = array_merge($validkeys, $semantics->extraAttributes);
|
||||
}
|
||||
$this->filterParams($value, $validkeys);
|
||||
if (isset($value->subContentId) && ! preg_match('/^\{?[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}?$/', $value->subContentId)) {
|
||||
unset($value->subContentId);
|
||||
}
|
||||
|
||||
// Find all dependencies for this library
|
||||
$depkey = 'preloaded-' . $library['machineName'];
|
||||
if (!isset($this->dependencies[$depkey])) {
|
||||
$this->dependencies[$depkey] = array(
|
||||
'library' => $library,
|
||||
'type' => 'preloaded'
|
||||
);
|
||||
|
||||
$this->nextWeight = $this->h5pC->findLibraryDependencies($this->dependencies, $library, $this->nextWeight);
|
||||
$this->dependencies[$depkey]['weight'] = $this->nextWeight++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,13 +1,12 @@
|
|||
/*jshint -W083 */
|
||||
var H5PUpgrades = H5PUpgrades || {};
|
||||
|
||||
(function ($) {
|
||||
var info, $container, librariesCache = {};
|
||||
(function ($, Version) {
|
||||
var info, $container, librariesCache = {}, scriptsCache = {};
|
||||
|
||||
// Initialize
|
||||
$(document).ready(function () {
|
||||
// Get library info
|
||||
info = H5PIntegration.getLibraryInfo();
|
||||
info = H5PAdminIntegration.libraryInfo;
|
||||
|
||||
// Get and reset container
|
||||
$container = $('#h5p-admin-container').html('<p>' + info.message + '</p>');
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -154,18 +72,84 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
var self = this;
|
||||
|
||||
// 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
|
||||
self.throbber = new Throbber(info.inProgress.replace('%ver', self.version));
|
||||
|
||||
// Get the next batch
|
||||
self.nextBatch({
|
||||
libraryId: libraryId,
|
||||
token: info.token
|
||||
});
|
||||
self.started = new Date().getTime();
|
||||
self.io = 0;
|
||||
|
||||
// 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.
|
||||
*
|
||||
|
@ -174,12 +158,24 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
ContentUpgrade.prototype.nextBatch = function (outData) {
|
||||
var self = this;
|
||||
|
||||
// Track time spent on IO
|
||||
var start = new Date().getTime();
|
||||
$.post(info.infoUrl, outData, function (inData) {
|
||||
self.io += new Date().getTime() - start;
|
||||
if (!(inData instanceof Object)) {
|
||||
// Print errors from backend
|
||||
return self.setStatus(inData);
|
||||
}
|
||||
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
|
||||
return self.setStatus(info.done);
|
||||
}
|
||||
|
@ -208,90 +204,125 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
*/
|
||||
ContentUpgrade.prototype.processBatch = function (parameters) {
|
||||
var self = this;
|
||||
var upgraded = {}; // Track upgraded params
|
||||
|
||||
var current = 0; // Track progress
|
||||
asyncSerial(parameters, function (id, params, next) {
|
||||
// Track upgraded params
|
||||
self.upgraded = {};
|
||||
|
||||
try {
|
||||
// Make params possible to work with
|
||||
params = JSON.parse(params);
|
||||
if (!(params instanceof Object)) {
|
||||
throw true;
|
||||
}
|
||||
// Track current batch
|
||||
self.parameters = parameters;
|
||||
|
||||
// Create id mapping
|
||||
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.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)
|
||||
});
|
||||
});
|
||||
self.assignWork();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// Load library details and upgrade routines
|
||||
self.loadLibrary(name, newVersion, function (err, library) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
var id = self.ids[self.current + 1];
|
||||
if (id === undefined) {
|
||||
return false; // Out of work
|
||||
}
|
||||
self.current++;
|
||||
self.working++;
|
||||
|
||||
// Run upgrade routines on params
|
||||
self.processParams(library, oldVersion, newVersion, params, function (err, params) {
|
||||
if (worker) {
|
||||
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) {
|
||||
return next(err);
|
||||
self.printError(err);
|
||||
return ;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
next(err, params);
|
||||
});
|
||||
self.workDone(id, result);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -303,32 +334,43 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
var self = this;
|
||||
|
||||
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.
|
||||
next(null, librariesCache[key]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Track time spent loading
|
||||
var start = new Date().getTime();
|
||||
librariesCache[key] = true;
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
cache: true,
|
||||
url: info.libraryBaseUrl + '/' + key
|
||||
}).fail(function () {
|
||||
self.io += new Date().getTime() - start;
|
||||
next(info.errorData.replace('%lib', name + ' ' + version));
|
||||
}).done(function (library) {
|
||||
self.io += new Date().getTime() - start;
|
||||
librariesCache[key] = library;
|
||||
next(null, 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);
|
||||
if (librariesLoadedCallbacks[key] !== undefined) {
|
||||
for (var i = 0; i < librariesLoadedCallbacks[key].length; i++) {
|
||||
librariesLoadedCallbacks[key][i](null, library);
|
||||
}
|
||||
}
|
||||
delete librariesLoadedCallbacks[key];
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -339,162 +381,43 @@ var H5PUpgrades = H5PUpgrades || {};
|
|||
* @param {Function} 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({
|
||||
dataType: 'script',
|
||||
cache: true,
|
||||
url: url
|
||||
}).fail(function () {
|
||||
self.io += new Date().getTime() - start;
|
||||
next(true);
|
||||
}).done(function () {
|
||||
scriptsCache[url] = true;
|
||||
self.io += new Date().getTime() - start;
|
||||
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) {
|
||||
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) {
|
||||
ContentUpgrade.prototype.printError = function (error) {
|
||||
var self = this;
|
||||
|
||||
if (params === undefined) {
|
||||
return next();
|
||||
if (error.type === 'errorParamsBroken') {
|
||||
error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken;
|
||||
}
|
||||
else if (error.type === 'scriptMissing') {
|
||||
error = info.errorScript.replace('%lib', error.library);
|
||||
}
|
||||
|
||||
switch (field.type) {
|
||||
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();
|
||||
}
|
||||
self.setStatus('<p>' + info.error + '<br/>' + error + '</p>');
|
||||
};
|
||||
|
||||
})(H5P.jQuery);
|
||||
})(H5P.jQuery, H5P.Version);
|
||||
|
|
|
@ -32,8 +32,9 @@ var H5PDataView = (function ($) {
|
|||
* search in column 2.
|
||||
* @param {Function} loaded
|
||||
* Callback for when data has been loaded.
|
||||
* @param {Object} order
|
||||
*/
|
||||
function H5PDataView(container, source, headers, l10n, classes, filters, loaded) {
|
||||
function H5PDataView(container, source, headers, l10n, classes, filters, loaded, order) {
|
||||
var self = this;
|
||||
|
||||
self.$container = $(container).addClass('h5p-data-view').html('');
|
||||
|
@ -44,6 +45,7 @@ var H5PDataView = (function ($) {
|
|||
self.classes = (classes === undefined ? {} : classes);
|
||||
self.filters = (filters === undefined ? [] : filters);
|
||||
self.loaded = loaded;
|
||||
self.order = order;
|
||||
|
||||
self.limit = 20;
|
||||
self.offset = 0;
|
||||
|
@ -68,8 +70,8 @@ var H5PDataView = (function ($) {
|
|||
url += (url.indexOf('?') === -1 ? '?' : '&') + 'offset=' + self.offset + '&limit=' + self.limit;
|
||||
|
||||
// Add sorting
|
||||
if (self.sortBy !== undefined && self.sortDir !== undefined) {
|
||||
url += '&sortBy=' + self.sortBy + '&sortDir=' + self.sortDir;
|
||||
if (self.order !== undefined) {
|
||||
url += '&sortBy=' + self.order.by + '&sortDir=' + self.order.dir;
|
||||
}
|
||||
|
||||
// Add filters
|
||||
|
@ -144,12 +146,11 @@ var H5PDataView = (function ($) {
|
|||
|
||||
// Create new table
|
||||
self.table = new H5PUtils.Table(self.classes, self.headers);
|
||||
self.table.setHeaders(self.headers, function (col, dir) {
|
||||
// Sorting column or direction has changed callback.
|
||||
self.sortBy = col;
|
||||
self.sortDir = dir;
|
||||
self.table.setHeaders(self.headers, function (order) {
|
||||
// Sorting column or direction has changed.
|
||||
self.order = order;
|
||||
self.loadData();
|
||||
});
|
||||
}, self.order);
|
||||
self.table.appendTo(self.$container);
|
||||
}
|
||||
|
||||
|
|
251
js/h5p-embed.js
251
js/h5p-embed.js
|
@ -1,69 +1,27 @@
|
|||
/*jshint multistr: true */
|
||||
|
||||
/**
|
||||
*
|
||||
* Converts old script tag embed to iframe
|
||||
*/
|
||||
var H5P = H5P || (function () {
|
||||
var H5POldEmbed = H5POldEmbed || (function () {
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var contentId = 0;
|
||||
var contents = {};
|
||||
var resizer = false;
|
||||
|
||||
/**
|
||||
* Wraps multiple content between a prefix and a suffix.
|
||||
* Loads the resizing script
|
||||
*/
|
||||
var wrap = function (prefix, content, suffix) {
|
||||
var result = '';
|
||||
for (var i = 0; i < content.length; i++) {
|
||||
result += prefix + content[i] + suffix;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
var loadContent = function (id, script) {
|
||||
var url = script.getAttribute('data-h5p');
|
||||
var data, callback = 'H5P' + id;
|
||||
|
||||
// Prevent duplicate loading.
|
||||
script.removeAttribute('data-h5p');
|
||||
var loadResizer = function (url) {
|
||||
var data, callback = 'H5POldEmbed';
|
||||
resizer = true;
|
||||
|
||||
// Callback for when content data is loaded.
|
||||
window[callback] = function (content) {
|
||||
contents[id] = content;
|
||||
|
||||
var iframe = document.createElement('iframe');
|
||||
var parent = script.parentNode;
|
||||
parent.insertBefore(iframe, script);
|
||||
|
||||
iframe.id = 'h5p-iframe-' + id;
|
||||
iframe.style.display = 'block';
|
||||
iframe.style.width = '100%';
|
||||
iframe.style.height = '1px';
|
||||
iframe.style.border = 'none';
|
||||
iframe.style.zIndex = 101;
|
||||
iframe.style.top = 0;
|
||||
iframe.style.left = 0;
|
||||
iframe.className = 'h5p-iframe';
|
||||
iframe.setAttribute('frameBorder', '0');
|
||||
iframe.contentDocument.open();
|
||||
iframe.contentDocument.write('\
|
||||
<!doctype html><html class="h5p-iframe">\
|
||||
<head>\
|
||||
<script>\
|
||||
var H5PIntegration = window.parent.H5P.getIntegration(' + id + ');\
|
||||
</script>\
|
||||
' + wrap('<link rel="stylesheet" href="', content.styles, '">') + '\
|
||||
' + wrap('<script src="', content.scripts, '"></script>') + '\
|
||||
</head><body>\
|
||||
<div class="h5p-content" data-class="' + content.library + '" data-content-id="' + id + '"/>\
|
||||
</body></html>');
|
||||
iframe.contentDocument.close();
|
||||
iframe.contentDocument.documentElement.style.overflow = 'hidden';
|
||||
// Add resizing script to head
|
||||
var resizer = document.createElement('script');
|
||||
resizer.src = content;
|
||||
head.appendChild(resizer);
|
||||
|
||||
// Clean up
|
||||
parent.removeChild(script);
|
||||
head.removeChild(data);
|
||||
delete window[callback];
|
||||
};
|
||||
|
@ -74,183 +32,44 @@ var H5P = H5P || (function () {
|
|||
head.appendChild(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaced script tag with iframe
|
||||
*/
|
||||
var addIframe = function (script) {
|
||||
// Add iframe
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.src = script.getAttribute('data-h5p');
|
||||
iframe.frameBorder = false;
|
||||
iframe.allowFullscreen = true;
|
||||
var parent = script.parentNode;
|
||||
parent.insertBefore(iframe, script);
|
||||
parent.removeChild(script);
|
||||
};
|
||||
|
||||
/**
|
||||
* Go throught all script tags with the data-h5p attribute and load content.
|
||||
*/
|
||||
function H5P() {
|
||||
function H5POldEmbed() {
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
var h5ps = []; // Use seperate array since scripts grow in size.
|
||||
for (var i = 0; i < scripts.length; i++) {
|
||||
var script = scripts[i];
|
||||
if (script.hasAttribute('data-h5p')) {
|
||||
if (script.src.indexOf('/h5p-resizer.js') !== -1) {
|
||||
resizer = true;
|
||||
}
|
||||
else if (script.hasAttribute('data-h5p')) {
|
||||
h5ps.push(script);
|
||||
}
|
||||
}
|
||||
for (i = 0; i < h5ps.length; i++) {
|
||||
loadContent(contentId, h5ps[i]);
|
||||
contentId++;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return integration object
|
||||
*/
|
||||
H5P.getIntegration = function (id) {
|
||||
var content = contents[id];
|
||||
return {
|
||||
getJsonContent: function () {
|
||||
return content.params;
|
||||
},
|
||||
getContentPath: function () {
|
||||
return content.path + 'content/' + content.id + '/';
|
||||
},
|
||||
getFullscreen: function () {
|
||||
return content.fullscreen;
|
||||
},
|
||||
getLibraryPath: function (library) {
|
||||
return content.path + 'libraries/' + library;
|
||||
},
|
||||
getContentData: function () {
|
||||
return {
|
||||
library: content.library,
|
||||
jsonContent: content.params,
|
||||
fullScreen: content.fullscreen,
|
||||
exportUrl: content.exportUrl,
|
||||
embedCode: content.embedCode
|
||||
};
|
||||
},
|
||||
i18n: content.i18n,
|
||||
showH5PIconInActionBar: function () {
|
||||
// Always show H5P-icon when embedding
|
||||
return true;
|
||||
if (!resizer) {
|
||||
loadResizer(h5ps[i].getAttribute('data-h5p'));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Detect if we support fullscreen, and what prefix to use.
|
||||
var fullScreenBrowserPrefix, safariBrowser;
|
||||
if (document.documentElement.requestFullScreen) {
|
||||
fullScreenBrowserPrefix = '';
|
||||
}
|
||||
else if (document.documentElement.webkitRequestFullScreen &&
|
||||
navigator.userAgent.indexOf('Android') === -1 // Skip Android
|
||||
) {
|
||||
safariBrowser = navigator.userAgent.match(/Version\/(\d)/);
|
||||
safariBrowser = (safariBrowser === null ? 0 : parseInt(safariBrowser[1]));
|
||||
|
||||
// Do not allow fullscreen for safari < 7.
|
||||
if (safariBrowser === 0 || safariBrowser > 6) {
|
||||
fullScreenBrowserPrefix = 'webkit';
|
||||
addIframe(h5ps[i]);
|
||||
}
|
||||
}
|
||||
else if (document.documentElement.mozRequestFullScreen) {
|
||||
fullScreenBrowserPrefix = 'moz';
|
||||
}
|
||||
else if (document.documentElement.msRequestFullscreen) {
|
||||
fullScreenBrowserPrefix = 'ms';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter fullscreen mode.
|
||||
*/
|
||||
H5P.fullScreen = function ($element, instance, exitCallback, body) {
|
||||
var iframe = document.getElementById('h5p-iframe-' + $element.parent().data('content-id'));
|
||||
var $classes = $element.add(body);
|
||||
var $body = $classes.eq(1);
|
||||
|
||||
/**
|
||||
* Prepare for resize by setting the correct styles.
|
||||
*
|
||||
* @param {String} classes CSS
|
||||
*/
|
||||
var before = function (classes) {
|
||||
$classes.addClass(classes);
|
||||
iframe.style.height = '100%';
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets called when fullscreen mode has been entered.
|
||||
* Resizes and sets focus on content.
|
||||
*/
|
||||
var entered = function () {
|
||||
// Do not rely on window resize events.
|
||||
instance.$.trigger('resize');
|
||||
instance.$.trigger('focus');
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets called when fullscreen mode has been exited.
|
||||
* Resizes and sets focus on content.
|
||||
*
|
||||
* @param {String} classes CSS
|
||||
*/
|
||||
var done = function (classes) {
|
||||
H5P.isFullscreen = false;
|
||||
$classes.removeClass(classes);
|
||||
|
||||
// Do not rely on window resize events.
|
||||
instance.$.trigger('resize');
|
||||
instance.$.trigger('focus');
|
||||
|
||||
if (exitCallback !== undefined) {
|
||||
exitCallback();
|
||||
}
|
||||
};
|
||||
|
||||
H5P.isFullscreen = true;
|
||||
if (fullScreenBrowserPrefix === undefined) {
|
||||
// Create semi fullscreen.
|
||||
|
||||
before('h5p-semi-fullscreen');
|
||||
iframe.style.position = 'fixed';
|
||||
|
||||
var $disable = $element.prepend('<a href="#" class="h5p-disable-fullscreen" title="Disable fullscreen"></a>').children(':first');
|
||||
var keyup, disableSemiFullscreen = function () {
|
||||
$disable.remove();
|
||||
$body.unbind('keyup', keyup);
|
||||
iframe.style.position = 'static';
|
||||
done('h5p-semi-fullscreen');
|
||||
return false;
|
||||
};
|
||||
keyup = function (event) {
|
||||
if (event.keyCode === 27) {
|
||||
disableSemiFullscreen();
|
||||
}
|
||||
};
|
||||
$disable.click(disableSemiFullscreen);
|
||||
$body.keyup(keyup); // TODO: Does not work with iframe's $!
|
||||
entered();
|
||||
}
|
||||
else {
|
||||
// Create real fullscreen.
|
||||
|
||||
before('h5p-fullscreen');
|
||||
var first, eventName = (fullScreenBrowserPrefix === 'ms' ? 'MSFullscreenChange' : fullScreenBrowserPrefix + 'fullscreenchange');
|
||||
document.addEventListener(eventName, function () {
|
||||
if (first === undefined) {
|
||||
// We are entering fullscreen mode
|
||||
first = false;
|
||||
entered();
|
||||
return;
|
||||
}
|
||||
|
||||
// We are exiting fullscreen
|
||||
done('h5p-fullscreen');
|
||||
document.removeEventListener(eventName, arguments.callee, false);
|
||||
});
|
||||
|
||||
if (fullScreenBrowserPrefix === '') {
|
||||
iframe.requestFullScreen();
|
||||
}
|
||||
else {
|
||||
var method = (fullScreenBrowserPrefix === 'ms' ? 'msRequestFullscreen' : fullScreenBrowserPrefix + 'RequestFullScreen');
|
||||
var params = (fullScreenBrowserPrefix === 'webkit' && safariBrowser === 0 ? Element.ALLOW_KEYBOARD_INPUT : undefined);
|
||||
iframe[method](params);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return H5P;
|
||||
return H5POldEmbed;
|
||||
})();
|
||||
|
||||
new H5P();
|
||||
new H5POldEmbed();
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
/** @namespace H5P */
|
||||
var H5P = H5P || {};
|
||||
|
||||
/**
|
||||
* The Event class for the EventDispatcher
|
||||
* @class
|
||||
*/
|
||||
H5P.Event = function(type, data, extras) {
|
||||
this.type = type;
|
||||
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 () {
|
||||
|
||||
/**
|
||||
* The base of the event system.
|
||||
* Inherit this class if you want your H5P to dispatch events.
|
||||
* @class
|
||||
*/
|
||||
function EventDispatcher() {
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* Keep track of listeners for each event.
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
var triggers = {};
|
||||
|
||||
/**
|
||||
* Add new event listener.
|
||||
*
|
||||
* @public
|
||||
* @throws {TypeError} listener - Must be a function
|
||||
* @param {String} type - Event type
|
||||
* @param {Function} listener - Event listener
|
||||
* @param {Function} thisArg - Optionally specify the this value when calling listener.
|
||||
*/
|
||||
this.on = function (type, listener, thisArg) {
|
||||
if (thisArg === undefined) {
|
||||
thisArg = self;
|
||||
}
|
||||
if (typeof listener !== 'function') {
|
||||
throw TypeError('listener must be a function');
|
||||
}
|
||||
|
||||
// Trigger event before adding to avoid recursion
|
||||
self.trigger('newListener', {'type': type, 'listener': listener});
|
||||
|
||||
if (!triggers[type]) {
|
||||
// First
|
||||
triggers[type] = [{'listener': listener, 'thisArg': thisArg}];
|
||||
}
|
||||
else {
|
||||
// Append
|
||||
triggers[type].push({'listener': listener, 'thisArg': thisArg});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add new event listener that will be fired only once.
|
||||
*
|
||||
* @public
|
||||
* @throws {TypeError} listener - must be a function
|
||||
* @param {String} type - Event type
|
||||
* @param {Function} listener - Event listener
|
||||
* @param {Function} thisArg - Optionally specify the this value when calling listener.
|
||||
*/
|
||||
this.once = function (type, listener, thisArg) {
|
||||
if (thisArg === undefined) {
|
||||
thisArg = self;
|
||||
}
|
||||
if (!(listener instanceof Function)) {
|
||||
throw TypeError('listener must be a function');
|
||||
}
|
||||
|
||||
var once = function (event) {
|
||||
self.off(event, once);
|
||||
listener.apply(thisArg, event);
|
||||
};
|
||||
|
||||
self.on(type, once, thisArg);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove event listener.
|
||||
* If no listener is specified, all listeners will be removed.
|
||||
*
|
||||
* @public
|
||||
* @throws {TypeError} listener - must be a function
|
||||
* @param {String} type - Event type
|
||||
* @param {Function} listener - Event listener
|
||||
*/
|
||||
this.off = function (type, listener) {
|
||||
if (listener !== undefined && !(listener instanceof Function)) {
|
||||
throw TypeError('listener must be a function');
|
||||
}
|
||||
|
||||
if (triggers[type] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (listener === undefined) {
|
||||
// Remove all listeners
|
||||
delete triggers[type];
|
||||
self.trigger('removeListener', type);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find specific listener
|
||||
for (var i = 0; i < triggers[type].length; i++) {
|
||||
if (triggers[type][i].listener === listener) {
|
||||
triggers[type].unshift(i, 1);
|
||||
self.trigger('removeListener', type, {'listener': listener});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up empty arrays
|
||||
if (!triggers[type].length) {
|
||||
delete triggers[type];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatch event.
|
||||
*
|
||||
* @public
|
||||
* @param {String|Function} event - Event object or event type as string
|
||||
* @param {mixed} eventData
|
||||
* Custom event data(used when event type as string is used as first
|
||||
* argument
|
||||
*/
|
||||
this.trigger = function (event, eventData, extras) {
|
||||
if (event === undefined) {
|
||||
return;
|
||||
}
|
||||
if (typeof event === 'string') {
|
||||
event = new H5P.Event(event, eventData, extras);
|
||||
}
|
||||
else if (eventData !== undefined) {
|
||||
event.data = eventData;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
if (triggers['*'] !== undefined) {
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return EventDispatcher;
|
||||
})();
|
|
@ -7,8 +7,8 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
|
|||
* Initializing
|
||||
*/
|
||||
H5PLibraryDetails.init = function () {
|
||||
H5PLibraryDetails.$adminContainer = H5PIntegration.getAdminContainer();
|
||||
H5PLibraryDetails.library = H5PIntegration.getLibraryInfo();
|
||||
H5PLibraryDetails.$adminContainer = H5P.jQuery(H5PAdminIntegration.containerSelector);
|
||||
H5PLibraryDetails.library = H5PAdminIntegration.libraryInfo;
|
||||
|
||||
// currentContent holds the current list if data (relevant for filtering)
|
||||
H5PLibraryDetails.currentContent = H5PLibraryDetails.library.content;
|
||||
|
|
|
@ -7,15 +7,15 @@ var H5PLibraryList = H5PLibraryList || {};
|
|||
* Initializing
|
||||
*/
|
||||
H5PLibraryList.init = function () {
|
||||
var $adminContainer = H5PIntegration.getAdminContainer();
|
||||
var $adminContainer = H5P.jQuery(H5PAdminIntegration.containerSelector).html('');
|
||||
|
||||
var libraryList = H5PIntegration.getLibraryList();
|
||||
var libraryList = H5PAdminIntegration.libraryList;
|
||||
if (libraryList.notCached) {
|
||||
$adminContainer.append(H5PUtils.getRebuildCache(libraryList.notCached));
|
||||
}
|
||||
|
||||
// Create library list
|
||||
$adminContainer.append(H5PLibraryList.createLibraryList(H5PIntegration.getLibraryList()));
|
||||
$adminContainer.append(H5PLibraryList.createLibraryList(H5PAdminIntegration.libraryList));
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -24,7 +24,7 @@ var H5PLibraryList = H5PLibraryList || {};
|
|||
* @param {object} libraries List of libraries and headers
|
||||
*/
|
||||
H5PLibraryList.createLibraryList = function (libraries) {
|
||||
var t = H5PIntegration.i18n.H5P;
|
||||
var t = H5PAdminIntegration.l10n;
|
||||
if(libraries.listData === undefined || libraries.listData.length === 0) {
|
||||
return $('<div>' + t.NA + '</div>');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
// H5P iframe Resizer
|
||||
(function () {
|
||||
if (!window.postMessage || !window.addEventListener || window.h5pResizerInitialized) {
|
||||
return; // Not supported
|
||||
}
|
||||
window.h5pResizerInitialized = true;
|
||||
|
||||
// Map actions to handlers
|
||||
var actionHandlers = {};
|
||||
|
||||
/**
|
||||
* Prepare iframe resize.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} iframe Element
|
||||
* @param {Object} data Payload
|
||||
* @param {Function} respond Send a response to the iframe
|
||||
*/
|
||||
actionHandlers.hello = function (iframe, data, respond) {
|
||||
// Make iframe responsive
|
||||
iframe.style.width = '100%';
|
||||
|
||||
// Tell iframe that it needs to resize when our window resizes
|
||||
var resize = function (event) {
|
||||
if (iframe.contentWindow) {
|
||||
// Limit resize calls to avoid flickering
|
||||
respond('resize');
|
||||
}
|
||||
else {
|
||||
// Frame is gone, unregister.
|
||||
window.removeEventListener('resize', resize);
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', resize, false);
|
||||
|
||||
// Respond to let the iframe know we can resize it
|
||||
respond('hello');
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare iframe resize.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} iframe Element
|
||||
* @param {Object} data Payload
|
||||
* @param {Function} respond Send a response to the iframe
|
||||
*/
|
||||
actionHandlers.prepareResize = function (iframe, data, respond) {
|
||||
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.
|
||||
iframe.style.height = '1px';
|
||||
|
||||
respond('resizePrepared');
|
||||
};
|
||||
|
||||
/**
|
||||
* Resize parent and iframe to desired height.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} iframe Element
|
||||
* @param {Object} data Payload
|
||||
* @param {Function} respond Send a response to the iframe
|
||||
*/
|
||||
actionHandlers.resize = function (iframe, data, respond) {
|
||||
// Resize iframe so all content is visible.
|
||||
iframe.style.height = data.height + 'px';
|
||||
iframe.parentNode.removeChild(iframe.nextSibling);
|
||||
};
|
||||
|
||||
/**
|
||||
* Keyup event handler. Exits full screen on escape.
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
var escape = function (event) {
|
||||
if (event.keyCode === 27) {
|
||||
exitFullScreen();
|
||||
}
|
||||
};
|
||||
|
||||
// Listen for messages from iframes
|
||||
window.addEventListener('message', function receiveMessage(event) {
|
||||
if (event.data.context !== 'h5p') {
|
||||
return; // Only handle h5p requests.
|
||||
}
|
||||
|
||||
// Find out who sent the message
|
||||
var iframe, iframes = document.getElementsByTagName('iframe');
|
||||
for (var i = 0; i < iframes.length; i++) {
|
||||
if (iframes[i].contentWindow === event.source) {
|
||||
iframe = iframes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!iframe) {
|
||||
return; // Cannot find sender
|
||||
}
|
||||
|
||||
// Find action handler handler
|
||||
if (actionHandlers[event.data.action]) {
|
||||
actionHandlers[event.data.action](iframe, event.data, function respond(action, data) {
|
||||
if (data === undefined) {
|
||||
data = {};
|
||||
}
|
||||
data.action = action;
|
||||
data.context = 'h5p';
|
||||
event.source.postMessage(data, event.origin);
|
||||
});
|
||||
}
|
||||
}, false);
|
||||
|
||||
// Let h5p iframes know we're ready!
|
||||
var iframes = document.getElementsByTagName('iframe');
|
||||
var ready = {
|
||||
context: 'h5p',
|
||||
action: 'ready'
|
||||
};
|
||||
for (var i = 0; i < iframes.length; i++) {
|
||||
if (iframes[i].src.indexOf('h5p') !== -1) {
|
||||
iframes[i].contentWindow.postMessage(ready, '*');
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
|
@ -7,7 +7,7 @@ var H5PUtils = H5PUtils || {};
|
|||
* @param {array} headers List of headers
|
||||
*/
|
||||
H5PUtils.createTable = function (headers) {
|
||||
var $table = $('<table class="h5p-admin-table' + (H5PIntegration.extraTableClasses !== undefined ? ' ' + H5PIntegration.extraTableClasses : '') + '"></table>');
|
||||
var $table = $('<table class="h5p-admin-table' + (H5PAdminIntegration.extraTableClasses !== undefined ? ' ' + H5PAdminIntegration.extraTableClasses : '') + '"></table>');
|
||||
|
||||
if(headers) {
|
||||
var $thead = $('<thead></thead>');
|
||||
|
@ -182,18 +182,30 @@ var H5PUtils = H5PUtils || {};
|
|||
if (sortByCol !== undefined && col.sortable === true) {
|
||||
// Make sortable
|
||||
options.role = 'button';
|
||||
options.tabIndex = 1;
|
||||
options.tabIndex = 0;
|
||||
|
||||
// This is the first sortable column, use as default sort
|
||||
if (sortCol === undefined) {
|
||||
sortCol = id;
|
||||
sortDir = 0;
|
||||
}
|
||||
|
||||
// This is the sort column
|
||||
if (sortCol === id) {
|
||||
options['class'] = 'h5p-sort';
|
||||
if (sortDir === 1) {
|
||||
options['class'] += ' h5p-reverse';
|
||||
}
|
||||
}
|
||||
|
||||
options.on.click = function () {
|
||||
sort($th, id);
|
||||
};
|
||||
options.on.keypress = function (event) {
|
||||
if ((event.charCode || event.keyCode) === 32) { // Space
|
||||
sort($th, id);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,7 +244,10 @@ var H5PUtils = H5PUtils || {};
|
|||
sortDir = 0;
|
||||
}
|
||||
|
||||
sortByCol(sortCol, sortDir);
|
||||
sortByCol({
|
||||
by: sortCol,
|
||||
dir: sortDir
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -244,11 +259,17 @@ var H5PUtils = H5PUtils || {};
|
|||
* "text" and "sortable". E.g.
|
||||
* [{text: 'Col 1', sortable: true}, 'Col 2', 'Col 3']
|
||||
* @param {Function} sort Callback which is runned when sorting changes
|
||||
* @param {Object} [order]
|
||||
*/
|
||||
this.setHeaders = function (cols, sort) {
|
||||
this.setHeaders = function (cols, sort, order) {
|
||||
numCols = cols.length;
|
||||
sortByCol = sort;
|
||||
|
||||
if (order) {
|
||||
sortCol = order.by;
|
||||
sortDir = order.dir;
|
||||
}
|
||||
|
||||
// Create new head
|
||||
var $newThead = $('<thead/>');
|
||||
var $tr = $('<tr/>').appendTo($newThead);
|
||||
|
|
|
@ -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;
|
||||
})();
|
|
@ -0,0 +1,239 @@
|
|||
var H5P = H5P || {};
|
||||
|
||||
/**
|
||||
* Constructor for xAPI events
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
H5P.XAPIEvent = function() {
|
||||
H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true, external: true});
|
||||
};
|
||||
|
||||
H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype);
|
||||
H5P.XAPIEvent.prototype.constructor = H5P.XAPIEvent;
|
||||
|
||||
/**
|
||||
* Helperfunction to set scored result statements
|
||||
*
|
||||
* @param {int} score
|
||||
* @param {int} maxScore
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.setScoredResult = function(score, maxScore) {
|
||||
this.data.statement.result = {
|
||||
'score': {
|
||||
'min': 0,
|
||||
'max': maxScore,
|
||||
'raw': score
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Helperfunction to set a verb.
|
||||
*
|
||||
* @param {string} verb
|
||||
* Verb in short form, one of the verbs defined at
|
||||
* http://adlnet.gov/expapi/verbs/
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.setVerb = function(verb) {
|
||||
if (H5P.jQuery.inArray(verb, H5P.XAPIEvent.allowedXAPIVerbs) !== -1) {
|
||||
this.data.statement.verb = {
|
||||
'id': 'http://adlnet.gov/expapi/verbs/' + verb,
|
||||
'display': {
|
||||
'en-US': verb
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (verb.id !== undefined) {
|
||||
this.data.statement.verb = verb;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helperfunction to get the statements verb id
|
||||
*
|
||||
* @param {boolean} full
|
||||
* if true the full verb id prefixed by http://adlnet.gov/expapi/verbs/ will be returned
|
||||
* @returns {string} - Verb or null if no verb with an id has been defined
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.getVerb = function(full) {
|
||||
var statement = this.data.statement;
|
||||
if ('verb' in statement) {
|
||||
if (full === true) {
|
||||
return statement.verb;
|
||||
}
|
||||
return statement.verb.id.slice(31);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helperfunction to set the object part of the statement.
|
||||
*
|
||||
* The id is found automatically (the url to the content)
|
||||
*
|
||||
* @param {object} instance - the H5P instance
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.setObject = function(instance) {
|
||||
if (instance.contentId) {
|
||||
this.data.statement.object = {
|
||||
'id': this.getContentXAPIId(instance),
|
||||
'objectType': 'Activity',
|
||||
'definition': {
|
||||
'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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to set the actor, email and name will be added automatically
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.setActor = function() {
|
||||
if (H5PIntegration.user !== undefined) {
|
||||
this.data.statement.actor = {
|
||||
'name': H5PIntegration.user.name,
|
||||
'mbox': 'mailto:' + H5PIntegration.user.mail,
|
||||
'objectType': 'Agent'
|
||||
};
|
||||
}
|
||||
else {
|
||||
var uuid;
|
||||
if (localStorage.H5PUserUUID) {
|
||||
uuid = localStorage.H5PUserUUID;
|
||||
}
|
||||
else {
|
||||
uuid = H5P.createUUID();
|
||||
localStorage.H5PUserUUID = uuid;
|
||||
}
|
||||
this.data.statement.actor = {
|
||||
'account': {
|
||||
'name': uuid,
|
||||
'homePage': H5PIntegration.siteUrl
|
||||
},
|
||||
'objectType': 'Agent'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the max value of the result - score part of the statement
|
||||
*
|
||||
* @returns {int} the max score, or null if not defined
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.getMaxScore = function() {
|
||||
return this.getVerifiedStatementValue(['result', 'score', 'max']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the raw value of the result - score part of the statement
|
||||
*
|
||||
* @returns {int} the max score, or null if not defined
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.getScore = function() {
|
||||
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
|
||||
*
|
||||
* @param {array} keys
|
||||
* List describing the property we're looking for. For instance
|
||||
* ['result', 'score', 'raw'] for result.score.raw
|
||||
* @returns the value of the property if it is set, null otherwise
|
||||
*/
|
||||
H5P.XAPIEvent.prototype.getVerifiedStatementValue = function(keys) {
|
||||
var val = this.data.statement;
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (val[keys[i]] === undefined) {
|
||||
return null;
|
||||
}
|
||||
val = val[keys[i]];
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
/**
|
||||
* List of verbs defined at http://adlnet.gov/expapi/verbs/
|
||||
*
|
||||
* @type Array
|
||||
*/
|
||||
H5P.XAPIEvent.allowedXAPIVerbs = [
|
||||
'answered',
|
||||
'asked',
|
||||
'attempted',
|
||||
'attended',
|
||||
'commented',
|
||||
'completed',
|
||||
'exited',
|
||||
'experienced',
|
||||
'failed',
|
||||
'imported',
|
||||
'initialized',
|
||||
'interacted',
|
||||
'launched',
|
||||
'mastered',
|
||||
'passed',
|
||||
'preferred',
|
||||
'progressed',
|
||||
'registered',
|
||||
'responded',
|
||||
'resumed',
|
||||
'scored',
|
||||
'shared',
|
||||
'suspended',
|
||||
'terminated',
|
||||
'voided'
|
||||
];
|
|
@ -0,0 +1,85 @@
|
|||
var H5P = H5P || {};
|
||||
|
||||
// Create object where external code may register and listen for H5P Events
|
||||
H5P.externalDispatcher = new H5P.EventDispatcher();
|
||||
|
||||
// EventDispatcher extensions
|
||||
|
||||
/**
|
||||
* Helper function for triggering xAPI added to the EventDispatcher
|
||||
*
|
||||
* @param {string} verb - the short id of the verb we want to trigger
|
||||
* @param {oject} extra - extra properties for the xAPI statement
|
||||
*/
|
||||
H5P.EventDispatcher.prototype.triggerXAPI = function(verb, extra) {
|
||||
this.trigger(this.createXAPIEventTemplate(verb, extra));
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to create event templates added to the EventDispatcher
|
||||
*
|
||||
* Will in the future be used to add representations of the questions to the
|
||||
* statements.
|
||||
*
|
||||
* @param {string} verb - verb id in short form
|
||||
* @param {object} extra - Extra values to be added to the statement
|
||||
* @returns {Function} - XAPIEvent object
|
||||
*/
|
||||
H5P.EventDispatcher.prototype.createXAPIEventTemplate = function(verb, extra) {
|
||||
var event = new H5P.XAPIEvent();
|
||||
|
||||
event.setActor();
|
||||
event.setVerb(verb);
|
||||
if (extra !== undefined) {
|
||||
for (var i in extra) {
|
||||
event.data.statement[i] = extra[i];
|
||||
}
|
||||
}
|
||||
if (!('object' in event.data.statement)) {
|
||||
event.setObject(this);
|
||||
}
|
||||
if (!('context' in event.data.statement)) {
|
||||
event.setContext(this);
|
||||
}
|
||||
return event;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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} maxScore - will be set as the "max" value of the score object
|
||||
*/
|
||||
H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) {
|
||||
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);
|
||||
this.trigger(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal H5P function listening for xAPI completed events and stores scores
|
||||
*
|
||||
* @param {function} event - xAPI event
|
||||
*/
|
||||
H5P.xAPICompletedListener = function(event) {
|
||||
if (event.getVerb() === 'completed' && !event.getVerifiedStatementValue(['context', 'contextActivities', 'parent'])) {
|
||||
var score = event.getScore();
|
||||
var maxScore = event.getMaxScore();
|
||||
var contentId = event.getVerifiedStatementValue(['object', 'definition', 'extensions', 'http://h5p.org/x-api/h5p-local-content-id']);
|
||||
H5P.setFinished(contentId, score, maxScore);
|
||||
}
|
||||
};
|
152
styles/h5p.css
152
styles/h5p.css
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue