Merge branch 'release' into stable

semantics-font drupal-7.x-1.5
Svein-Tore Griff With 2015-04-22 14:01:56 +02:00
commit ae512b2b03
23 changed files with 2648 additions and 866 deletions

View File

@ -1,13 +1,12 @@
This folder contains the h5p general library. The files within this folder are not specific to any framework. 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 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: the H5PFrameworkInterface(in h5p.classes.php) and also do the following:
- TODO: Fill in here
In addition frameworks need to do the following:
- Provide a form for uploading h5p packages. - Provide a form for uploading h5p packages.
- Place the uploaded h5p packages in a temporary directory - 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 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

19
embed.php Normal file
View File

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

Binary file not shown.

View File

@ -1,31 +1,13 @@
<?xml version="1.0" standalone="no"?> <?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" > <!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"> <svg xmlns="http://www.w3.org/2000/svg">
<metadata> <metadata>Generated by IcoMoon</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>
<defs> <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" /> <font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" /> <missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" d="" horiz-adv-x="512" /> <glyph unicode="&#x20;" d="" horiz-adv-x="512" />
<glyph unicode="&#xe600;" 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="&#xe888;" 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="&#xe888;" 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="&#xe889;" 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="&#xe889;" 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="&#xe88a;" 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" /> <glyph unicode="&#xe88a;" 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="&#xe890;" 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="&#xe890;" 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="&#xe891;" 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="&#xe891;" 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="&#xe892;" 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="&#xe892;" 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="&#xe893;" 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="&#xe893;" 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="&#xe894;" 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" /> <glyph unicode="&#xe894;" 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> </font></defs></svg>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

View File

@ -9,7 +9,7 @@ class H5PDevelopment {
const MODE_CONTENT = 1; const MODE_CONTENT = 1;
const MODE_LIBRARY = 2; const MODE_LIBRARY = 2;
private $h5pF, $libraries, $language; private $h5pF, $libraries, $language, $filesPath;
/** /**
* Constructor. * Constructor.
@ -23,6 +23,7 @@ class H5PDevelopment {
public function __construct($H5PFramework, $filesPath, $language, $libraries = NULL) { public function __construct($H5PFramework, $filesPath, $language, $libraries = NULL) {
$this->h5pF = $H5PFramework; $this->h5pF = $H5PFramework;
$this->language = $language; $this->language = $language;
$this->filesPath = $filesPath;
if ($libraries !== NULL) { if ($libraries !== NULL) {
$this->libraries = $libraries; $this->libraries = $libraries;
} }
@ -86,13 +87,14 @@ class H5PDevelopment {
$library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']); $library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']);
$this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE); $this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE);
$library['path'] = $libraryPath; $library['path'] = 'development/' . $contents[$i];
$this->libraries[H5PDevelopment::libraryToString($library['machineName'], $library['majorVersion'], $library['minorVersion'])] = $library; $this->libraries[H5PDevelopment::libraryToString($library['machineName'], $library['majorVersion'], $library['minorVersion'])] = $library;
} }
// TODO: Should we remove libraries without files? Not really needed, but must be cleaned up some time, right? // 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?!) // 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) { foreach ($this->libraries as $library) {
$this->h5pF->deleteLibraryDependencies($library['libraryId']); $this->h5pF->deleteLibraryDependencies($library['libraryId']);
// This isn't optimal, but without it we would get duplicate warnings. // 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?! // 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) { public function getSemantics($name, $majorVersion, $minorVersion) {
$library = H5PDevelopment::libraryToString($name, $majorVersion, $minorVersion); $library = H5PDevelopment::libraryToString($name, $majorVersion, $minorVersion);
if (isset($this->libraries[$library]) === FALSE) { if (isset($this->libraries[$library]) === FALSE) {
return NULL; return NULL;
} }
return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/semantics.json');
return $this->getFileContents($this->libraries[$library]['path'] . '/semantics.json');
} }
/** /**
@ -160,7 +161,7 @@ class H5PDevelopment {
return NULL; return NULL;
} }
return $this->getFileContents($this->libraries[$library]['path'] . '/language/' . $language . '.json'); return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/language/' . $language . '.json');
} }
/** /**

View File

@ -15,6 +15,7 @@ interface H5PFrameworkInterface {
*/ */
public function getPlatformInfo(); public function getPlatformInfo();
/** /**
* Fetches a file from a remote server using HTTP GET * Fetches a file from a remote server using HTTP GET
* *
@ -73,12 +74,6 @@ interface H5PFrameworkInterface {
*/ */
public function getUploadedH5pFolderPath(); public function getUploadedH5pFolderPath();
/**
* @return string
* Path to the folder where all h5p files are stored
*/
public function getH5pPath();
/** /**
* Get the path to the last uploaded h5p file * Get the path to the last uploaded h5p file
* *
@ -252,6 +247,13 @@ interface H5PFrameworkInterface {
*/ */
public function updateContent($content, $contentMainId = NULL); public function updateContent($content, $contentMainId = NULL);
/**
* Resets marked user data for the given content.
*
* @param int $contentId
*/
public function resetContentUserData($contentId);
/** /**
* Save what libraries a library is dependending on * Save what libraries a library is dependending on
* *
@ -408,6 +410,17 @@ interface H5PFrameworkInterface {
*/ */
public function deleteLibraryDependencies($libraryId); 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 * Delete a library from database and file system
* *
@ -759,15 +772,14 @@ class H5PValidator {
// When upgrading, we opnly add allready installed libraries, // When upgrading, we opnly add allready installed libraries,
// and new dependent libraries // and new dependent libraries
$upgrades = array(); $upgrades = array();
foreach ($libraries as &$library) { foreach ($libraries as $libString => &$library) {
// Is this library already installed? // Is this library already installed?
if ($this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']) !== FALSE) { if ($this->h5pC->getLibraryId($library, $libString) !== FALSE) {
$upgrades[H5PCore::libraryToString($library)] = $library; $upgrades[$libString] = $library;
} }
} }
while ($missingLibraries = $this->getMissingLibraries($upgrades)) { while ($missingLibraries = $this->getMissingLibraries($upgrades)) {
foreach ($missingLibraries as $missing) { foreach ($missingLibraries as $libString => $missing) {
$libString = H5PCore::libraryToString($missing);
$library = $libraries[$libString]; $library = $libraries[$libString];
if ($library) { if ($library) {
$upgrades[$libString] = $library; $upgrades[$libString] = $library;
@ -787,15 +799,15 @@ class H5PValidator {
} }
$missingLibraries = $this->getMissingLibraries($libraries); $missingLibraries = $this->getMissingLibraries($libraries);
foreach ($missingLibraries as $missing) { foreach ($missingLibraries as $libString => $missing) {
if ($this->h5pF->getLibraryId($missing['machineName'], $missing['majorVersion'], $missing['minorVersion'])) { if ($this->h5pC->getLibraryId($missing, $libString)) {
unset($missingLibraries[H5PCore::libraryToString($missing)]); unset($missingLibraries[$libString]);
} }
} }
if (!empty($missingLibraries)) { if (!empty($missingLibraries)) {
foreach ($missingLibraries as $library) { foreach ($missingLibraries as $libString => $library) {
$this->h5pF->setErrorMessage($this->h5pF->t('Missing required library @library', array('@library' => H5PCore::libraryToString($library)))); $this->h5pF->setErrorMessage($this->h5pF->t('Missing required library @library', array('@library' => $libString)));
} }
if (!$this->h5pF->mayUpdateLibraries()) { 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.")); $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) { private function getMissingDependencies($dependencies, $libraries) {
$missing = array(); $missing = array();
foreach ($dependencies as $dependency) { foreach ($dependencies as $dependency) {
if (!isset($libraries[H5PCore::libraryToString($dependency)])) { $libString = H5PCore::libraryToString($dependency);
$missing[H5PCore::libraryToString($dependency)] = $dependency; if (!isset($libraries[$libString])) {
$missing[$libString] = $dependency;
} }
} }
return $missing; return $missing;
@ -1227,51 +1240,130 @@ class H5PStorage {
* TRUE if one or more libraries were updated * TRUE if one or more libraries were updated
* FALSE otherwise * FALSE otherwise
*/ */
public function savePackage($content = NULL, $contentMainId = NULL, $skipContent = FALSE, $upgradeOnly = FALSE) { public function savePackage($content = NULL, $contentMainId = NULL, $skipContent = FALSE, $options = array()) {
if ($this->h5pF->mayUpdateLibraries()) {
// Save the libraries we processed during validation // Save the libraries we processed during validation
$library_saved = FALSE; $this->saveLibraries();
$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) { if (!$skipContent) {
// This shouldn't happen, but just to be safe... $basePath = $this->h5pF->getUploadedH5pFolderPath();
continue; $current_path = $basePath . DIRECTORY_SEPARATOR . 'content';
// Save content
if ($content === NULL) {
$content = array();
}
if (!is_array($content)) {
$content = array('id' => $content);
} }
$this->h5pF->saveLibraryData($library, $new); // 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;
}
}
$libraries_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'libraries'; $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->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;
$this->h5pC->copyFileTree($current_path, $destination_path);
// Remove temp content folder
H5PCore::deleteFileTree($basePath);
}
// Update supported library list if neccessary:
$this->h5pC->validateLibrarySupport(TRUE);
}
/**
* 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)) { if (!is_dir($libraries_path)) {
mkdir($libraries_path, 0777, true); 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; // 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) { foreach ($this->h5pC->librariesJsonData as &$library) {
if ($library['saveDependencies']) { if (!$library['saveDependencies']) {
continue;
}
// TODO: Should the table be locked for this operation?
// Remove any old dependencies
$this->h5pF->deleteLibraryDependencies($library['libraryId']); $this->h5pF->deleteLibraryDependencies($library['libraryId']);
// Insert the different new ones
if (isset($library['preloadedDependencies'])) { if (isset($library['preloadedDependencies'])) {
$this->h5pF->saveLibraryDependencies($library['libraryId'], $library['preloadedDependencies'], 'preloaded'); $this->h5pF->saveLibraryDependencies($library['libraryId'], $library['preloadedDependencies'], 'preloaded');
} }
@ -1284,53 +1376,22 @@ class H5PStorage {
// Make sure libraries dependencies, parameter filtering and export files gets regenerated for all content who uses this library. // Make sure libraries dependencies, parameter filtering and export files gets regenerated for all content who uses this library.
$this->h5pF->clearFilteredParameters($library['libraryId']); $this->h5pF->clearFilteredParameters($library['libraryId']);
$upgradedLibsCount++;
}
} }
if (!$skipContent) { // Tell the user what we've done.
$current_path = $this->h5pF->getUploadedH5pFolderPath() . DIRECTORY_SEPARATOR . 'content'; if ($newOnes && $oldOnes) {
$message = $this->h5pF->t('Added %new new H5P libraries and updated %old old.', array('%new' => $newOnes, '%old' => $oldOnes));
// Find out which libraries are used by this package/content
$librariesInUse = array();
$this->h5pC->findLibraryDependencies($librariesInUse, $this->h5pC->mainJsonData);
// Save content
if ($content === NULL) {
$content = array();
} }
if (!is_array($content)) { elseif ($newOnes) {
$content = array('id' => $content); $message = $this->h5pF->t('Added %new new H5P libraries.', array('%new' => $newOnes));
} }
$content['library'] = $librariesInUse['preloaded-' . $this->h5pC->mainJsonData['mainLibrary']]['library']; elseif ($oldOnes) {
$content['params'] = file_get_contents($current_path . DIRECTORY_SEPARATOR . 'content.json'); $message = $this->h5pF->t('Updated %old H5P libraries.', array('%old' => $oldOnes));
$contentId = $this->h5pC->saveContent($content, $contentMainId);
$this->contentId = $contentId;
$contents_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content';
if (!is_dir($contents_path)) {
mkdir($contents_path, 0777, true);
} }
// Move the content folder if (isset($message)) {
$destination_path = $contents_path . DIRECTORY_SEPARATOR . $contentId; $this->h5pF->setInfoMessage($message);
@rename($current_path, $destination_path);
// Save the content library dependencies
$this->h5pF->saveLibraryUsage($contentId, $librariesInUse);
H5PCore::deleteFileTree($this->h5pF->getUploadedH5pFolderPath());
} }
// 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)));
}
return $library_saved;
} }
/** /**
@ -1340,8 +1401,9 @@ class H5PStorage {
* The content id * The content id
*/ */
public function deletePackage($contentId) { public function deletePackage($contentId) {
H5PCore::deleteFileTree($this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId); H5PCore::deleteFileTree($this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId);
$this->h5pF->deleteContentData($contentId); $this->h5pF->deleteContentData($contentId);
// TODO: Delete export?
} }
/** /**
@ -1355,9 +1417,9 @@ class H5PStorage {
* TRUE if one or more libraries were updated * TRUE if one or more libraries were updated
* FALSE otherwise * FALSE otherwise
*/ */
public function updatePackage($contentId, $contentMainId = NULL) { public function updatePackage($contentId, $contentMainId = NULL, $options) {
$this->deletePackage($contentId); $this->deletePackage($contentId);
return $this->savePackage($contentId, $contentMainId); return $this->savePackage($contentId, $contentMainId, FALSE, $options);
} }
/** /**
@ -1374,8 +1436,8 @@ class H5PStorage {
* The main id of the new content (used in frameworks that support revisioning) * The main id of the new content (used in frameworks that support revisioning)
*/ */
public function copyPackage($contentId, $copyFromId, $contentMainId = NULL) { public function copyPackage($contentId, $copyFromId, $contentMainId = NULL) {
$source_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $copyFromId; $source_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $copyFromId;
$destination_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId; $destination_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId;
$this->h5pC->copyFileTree($source_path, $destination_path); $this->h5pC->copyFileTree($source_path, $destination_path);
$this->h5pF->copyLibraryUsage($contentId, $copyFromId, $contentMainId); $this->h5pF->copyLibraryUsage($contentId, $copyFromId, $contentMainId);
@ -1411,7 +1473,7 @@ Class H5PExport {
* @return string * @return string
*/ */
public function createExportFile($content) { public function createExportFile($content) {
$h5pDir = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR; $h5pDir = $this->h5pC->path . DIRECTORY_SEPARATOR;
$tempPath = $h5pDir . 'temp' . DIRECTORY_SEPARATOR . $content['id']; $tempPath = $h5pDir . 'temp' . DIRECTORY_SEPARATOR . $content['id'];
$zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $content['id'] . '.h5p'; $zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $content['id'] . '.h5p';
@ -1431,8 +1493,6 @@ Class H5PExport {
// Build h5p.json // Build h5p.json
$h5pJson = array ( $h5pJson = array (
'title' => $content['title'], 'title' => $content['title'],
// TODO - stop using 'und', this is not the preferred way.
// Either remove language from the json if not existing, or use "language": null
'language' => (isset($content['language']) && strlen(trim($content['language'])) !== 0) ? $content['language'] : 'und', 'language' => (isset($content['language']) && strlen(trim($content['language'])) !== 0) ? $content['language'] : 'und',
'mainLibrary' => $content['library']['name'], 'mainLibrary' => $content['library']['name'],
'embedTypes' => $embedTypes, 'embedTypes' => $embedTypes,
@ -1443,7 +1503,7 @@ Class H5PExport {
$library = $dependency['library']; $library = $dependency['library'];
// Copy library to h5p // Copy library to h5p
$source = isset($library['path']) ? $library['path'] : $h5pDir . 'libraries' . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE); $source = $h5pDir . (isset($library['path']) ? $library['path'] : 'libraries' . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE));
$destination = $tempPath . DIRECTORY_SEPARATOR . $library['machineName']; $destination = $tempPath . DIRECTORY_SEPARATOR . $library['machineName'];
$this->h5pC->copyFileTree($source, $destination); $this->h5pC->copyFileTree($source, $destination);
@ -1463,28 +1523,52 @@ Class H5PExport {
$results = print_r(json_encode($h5pJson), true); $results = print_r(json_encode($h5pJson), true);
file_put_contents($tempPath . DIRECTORY_SEPARATOR . 'h5p.json', $results); file_put_contents($tempPath . DIRECTORY_SEPARATOR . 'h5p.json', $results);
// Get a complete file list from our tmp dir
$files = array();
self::populateFileList($tempPath, $files);
// Create new zip instance. // Create new zip instance.
$zip = new ZipArchive(); $zip = new ZipArchive();
$zip->open($zipPath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE); $zip->open($zipPath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE);
// Get all files and folders in $tempPath // Add all the files from the tmp dir.
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($tempPath . DIRECTORY_SEPARATOR)); foreach ($files as $file) {
// Add files to zip // Please note that the zip format has no concept of folders, we must
foreach ($iterator as $key => $value) { // use forward slashes to separate our directories.
$test = '.'; $zip->addFile($file->absolutePath, $file->relativePath);
// Do not add the folders '.' and '..' to the zip. This will make zip invalid.
if (substr_compare($key, $test, -strlen($test), strlen($test)) !== 0) {
// Get files path in $tempPath
$filePath = explode($tempPath . DIRECTORY_SEPARATOR, $key);
// Add files to the zip with the intended file-structure
$zip->addFile($key, $filePath[1]);
}
} }
// Close zip and remove temp dir // Close zip and remove temp dir
$zip->close(); $zip->close();
H5PCore::deleteFileTree($tempPath); H5PCore::deleteFileTree($tempPath);
} }
/**
* Recursive function the will add the files of the given directory to the
* given files list. All files are objects with an absolute path and
* a relative path. The relative path is forward slashes only! Great for
* use in zip files and URLs.
*
* @param string $dir path
* @param array $files list
* @param string $relative prefix. Optional
*/
private static function populateFileList($dir, &$files, $relative = '') {
$strip = strlen($dir) + 1;
foreach (glob($dir . DIRECTORY_SEPARATOR . '*') as $file) {
$rel = $relative . substr($file, $strip);
if (is_dir($file)) {
self::populateFileList($file, $files, $rel . '/');
}
else {
$files[] = (object) array(
'absolutePath' => $file,
'relativePath' => $rel
);
}
}
}
/** /**
* Delete .h5p file * Delete .h5p file
* *
@ -1492,7 +1576,7 @@ Class H5PExport {
* Identifier for the H5P * Identifier for the H5P
*/ */
public function deleteExport($contentId) { public function deleteExport($contentId) {
$h5pDir = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR; $h5pDir = $this->h5pC->path . DIRECTORY_SEPARATOR;
$zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $contentId . '.h5p'; $zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $contentId . '.h5p';
if (file_exists($zipPath)) { if (file_exists($zipPath)) {
unlink($zipPath); unlink($zipPath);
@ -1526,7 +1610,7 @@ class H5PCore {
public static $coreApi = array( public static $coreApi = array(
'majorVersion' => 1, 'majorVersion' => 1,
'minorVersion' => 3 'minorVersion' => 5
); );
public static $styles = array( public static $styles = array(
'styles/h5p.css', 'styles/h5p.css',
@ -1534,6 +1618,9 @@ class H5PCore {
public static $scripts = array( public static $scripts = array(
'js/jquery.js', 'js/jquery.js',
'js/h5p.js', 'js/h5p.js',
'js/h5p-event-dispatcher.js',
'js/h5p-x-api-event.js',
'js/h5p-x-api.js',
); );
public static $adminScripts = array( public static $adminScripts = array(
'js/jquery.js', 'js/jquery.js',
@ -1548,6 +1635,22 @@ class H5PCore {
private $exportEnabled; private $exportEnabled;
// Disable flags
const DISABLE_NONE = 0;
const DISABLE_FRAME = 1;
const DISABLE_DOWNLOAD = 2;
const DISABLE_EMBED = 4;
const DISABLE_COPYRIGHT = 8;
const DISABLE_ABOUT = 16;
// Map flags to string
public static $disable = array(
self::DISABLE_FRAME => 'frame',
self::DISABLE_DOWNLOAD => 'download',
self::DISABLE_EMBED => 'embed',
self::DISABLE_COPYRIGHT => 'copyright'
);
/** /**
* Constructor for the H5PCore * Constructor for the H5PCore
* *
@ -1558,16 +1661,17 @@ class H5PCore {
* @param boolean $export enabled? * @param boolean $export enabled?
* @param int $development_mode mode. * @param int $development_mode mode.
*/ */
public function __construct($H5PFramework, $path, $language = 'en', $export = FALSE, $development_mode = H5PDevelopment::MODE_NONE) { public function __construct($H5PFramework, $path, $url, $language = 'en', $export = FALSE, $development_mode = H5PDevelopment::MODE_NONE) {
$this->h5pF = $H5PFramework; $this->h5pF = $H5PFramework;
$this->h5pF = $H5PFramework; $this->h5pF = $H5PFramework;
$this->path = $path; $this->path = $path;
$this->url = $url;
$this->exportEnabled = $export; $this->exportEnabled = $export;
$this->development_mode = $development_mode; $this->development_mode = $development_mode;
if ($development_mode & H5PDevelopment::MODE_LIBRARY) { if ($development_mode & H5PDevelopment::MODE_LIBRARY) {
$this->h5pD = new H5PDevelopment($this->h5pF, $path, $language); $this->h5pD = new H5PDevelopment($this->h5pF, $path . '/', $language);
} }
} }
@ -1585,6 +1689,9 @@ class H5PCore {
$content['id'] = $this->h5pF->insertContent($content, $contentMainId); $content['id'] = $this->h5pF->insertContent($content, $contentMainId);
} }
// Some user data for content has to be reset when the content changes.
$this->h5pF->resetContentUserData($contentMainId ? $contentMainId : $content['id']);
return $content['id']; return $content['id'];
} }
@ -1658,8 +1765,6 @@ class H5PCore {
// Recreate export file // Recreate export file
$exporter = new H5PExport($this->h5pF, $this); $exporter = new H5PExport($this->h5pF, $this);
$exporter->createExportFile($content); $exporter->createExportFile($content);
// TODO: Should we rather create the file once first accessed, like imagecache?
} }
// Cache. // Cache.
@ -1698,8 +1803,9 @@ class H5PCore {
* @param array $dependency * @param array $dependency
* @param string $type * @param string $type
* @param array $assets * @param array $assets
* @param string $prefix Optional. Make paths relative to another dir.
*/ */
private function getDependencyAssets($dependency, $type, &$assets) { private function getDependencyAssets($dependency, $type, &$assets, $prefix = '') {
// Check if dependency has any files of this type // Check if dependency has any files of this type
if (empty($dependency[$type]) || $dependency[$type][0] === '') { if (empty($dependency[$type]) || $dependency[$type][0] === '') {
return; return;
@ -1709,10 +1815,9 @@ class H5PCore {
if ($type === 'preloadedCss' && (isset($dependency['dropCss']) && $dependency['dropCss'] === '1')) { if ($type === 'preloadedCss' && (isset($dependency['dropCss']) && $dependency['dropCss'] === '1')) {
return; return;
} }
foreach ($dependency[$type] as $file) { foreach ($dependency[$type] as $file) {
$assets[] = (object) array( $assets[] = (object) array(
'path' => $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), 'path' => $prefix . '/' . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file),
'version' => $dependency['version'] 'version' => $dependency['version']
); );
} }
@ -1728,7 +1833,19 @@ class H5PCore {
$urls = array(); $urls = array();
foreach ($assets as $asset) { foreach ($assets as $asset) {
$urls[] = $asset->path . $asset->version; $url = $asset->path;
// Add URL prefix if not external
if (strpos($asset->path, '://') === FALSE) {
$url = $this->url . $url;
}
// Add version/cache buster if set
if (isset($asset->version)) {
$url .= $asset->version;
}
$urls[] = $url;
} }
return $urls; return $urls;
@ -1738,23 +1855,23 @@ class H5PCore {
* Return file paths for all dependecies files. * Return file paths for all dependecies files.
* *
* @param array $dependencies * @param array $dependencies
* @param string $prefix Optional. Make paths relative to another dir.
* @return array files. * @return array files.
*/ */
public function getDependenciesFiles($dependencies) { public function getDependenciesFiles($dependencies, $prefix = '') {
$files = array( $files = array(
'scripts' => array(), 'scripts' => array(),
'styles' => array() 'styles' => array()
); );
foreach ($dependencies as $dependency) { foreach ($dependencies as $dependency) {
if (isset($dependency['path']) === FALSE) { if (isset($dependency['path']) === FALSE) {
$dependency['path'] = $this->path . '/libraries/' . H5PCore::libraryToString($dependency, TRUE); $dependency['path'] = 'libraries/' . H5PCore::libraryToString($dependency, TRUE);
$dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']); $dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']);
$dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']); $dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']);
} }
$dependency['version'] = "?ver={$dependency['majorVersion']}.{$dependency['minorVersion']}.{$dependency['patchVersion']}"; $dependency['version'] = "?ver={$dependency['majorVersion']}.{$dependency['minorVersion']}.{$dependency['patchVersion']}";
$this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts']); $this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts'], $prefix);
$this->getDependencyAssets($dependency, 'preloadedCss', $files['styles']); $this->getDependencyAssets($dependency, 'preloadedCss', $files['styles'], $prefix);
} }
return $files; return $files;
} }
@ -1825,9 +1942,12 @@ class H5PCore {
* *
* @param array $librariesUsed Flat list of all dependencies. * @param array $librariesUsed Flat list of all dependencies.
* @param array $library To find all dependencies for. * @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) { foreach (array('dynamic', 'preloaded', 'editor') as $type) {
$property = $type . 'Dependencies'; $property = $type . 'Dependencies';
if (!isset($library[$property])) { if (!isset($library[$property])) {
@ -1851,7 +1971,8 @@ class H5PCore {
'library' => $dependencyLibrary, 'library' => $dependencyLibrary,
'type' => $type 'type' => $type
); );
$this->findLibraryDependencies($dependencies, $dependencyLibrary, $type === 'editor'); $nextWeight = $this->findLibraryDependencies($dependencies, $dependencyLibrary, $nextWeight, $type === 'editor');
$dependencies[$dependencyKey]['weight'] = $nextWeight++;
} }
else { else {
// This site is missing a dependency! // This site is missing a dependency!
@ -1859,6 +1980,7 @@ class H5PCore {
} }
} }
} }
return $nextWeight;
} }
/** /**
@ -1924,7 +2046,7 @@ class H5PCore {
@mkdir($destination); @mkdir($destination);
while (false !== ($file = readdir($dir))) { while (false !== ($file = readdir($dir))) {
if (($file != '.') && ($file != '..')) { if (($file != '.') && ($file != '..') && $file != '.git' && $file != '.gitignore') {
if (is_dir($source . DIRECTORY_SEPARATOR . $file)) { if (is_dir($source . DIRECTORY_SEPARATOR . $file)) {
$this->copyFileTree($source . DIRECTORY_SEPARATOR . $file, $destination . 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 * Helper function for creating markup for the unsupported libraries list
* *
* TODO: Make help text translatable
*
* @return string Html * @return string Html
* */ * */
public function createMarkupForUnsupportedLibraryList($libraries) { public function createMarkupForUnsupportedLibraryList($libraries) {
@ -2197,10 +2317,83 @@ class H5PCore {
} }
} }
if($platformInfo['uuid'] === '' && isset($json->uuid)) { if($platformInfo['uuid'] === '' && isset($json->uuid)) {
$this->h5pF->setOption('h5p_site_uuid', $json->uuid); $this->h5pF->setOption('site_uuid', $json->uuid);
} }
} }
} }
public function getGlobalDisable() {
$disable = self::DISABLE_NONE;
// Allow global settings to override and disable options
if (!$this->h5pF->getOption('frame', TRUE)) {
$disable |= self::DISABLE_FRAME;
}
else {
if (!$this->h5pF->getOption('export', TRUE)) {
$disable |= self::DISABLE_DOWNLOAD;
}
if (!$this->h5pF->getOption('embed', TRUE)) {
$disable |= self::DISABLE_EMBED;
}
if (!$this->h5pF->getOption('copyright', TRUE)) {
$disable |= self::DISABLE_COPYRIGHT;
}
if (!$this->h5pF->getOption('icon', TRUE)) {
$disable |= self::DISABLE_ABOUT;
}
}
return $disable;
}
/**
* Determine disable state from sources.
*
* @param array $sources
* @return int
*/
public static function getDisable(&$sources) {
$disable = H5PCore::DISABLE_NONE;
if (!isset($sources['frame']) || !$sources['frame']) {
$disable |= H5PCore::DISABLE_FRAME;
}
if (!isset($sources['download']) || !$sources['download']) {
$disable |= H5PCore::DISABLE_DOWNLOAD;
}
if (!isset($sources['copyright']) || !$sources['copyright']) {
$disable |= H5PCore::DISABLE_COPYRIGHT;
}
if (!isset($sources['embed']) || !$sources['embed']) {
$disable |= H5PCore::DISABLE_EMBED;
}
if (!isset($sources['about']) || !$sources['about']) {
$disable |= H5PCore::DISABLE_ABOUT;
}
return $disable;
}
// Cache for getting library ids
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 { class H5PContentValidator {
public $h5pF; public $h5pF;
public $h5pC; public $h5pC;
private $typeMap; private $typeMap, $libraries, $dependencies, $nextWeight;
private $libraries, $dependencies;
/** /**
* Constructor for the H5PContentValidator * Constructor for the H5PContentValidator
@ -2236,10 +2428,10 @@ class H5PContentValidator {
'select' => 'validateSelect', 'select' => 'validateSelect',
'library' => 'validateLibrary', 'library' => 'validateLibrary',
); );
$this->nextWeight = 1;
// Keep track of the libraries we load to avoid loading it multiple times. // Keep track of the libraries we load to avoid loading it multiple times.
$this->libraries = array(); $this->libraries = array();
// TODO: Should this possible be done in core's loadLibrary? This might be done multiple places.
// Keep track of all dependencies for the given content. // Keep track of all dependencies for the given content.
$this->dependencies = array(); $this->dependencies = array();
@ -2562,6 +2754,9 @@ class H5PContentValidator {
$found = FALSE; $found = FALSE;
foreach ($semantics->fields as $field) { foreach ($semantics->fields as $field) {
if ($field->name == $key) { if ($field->name == $key) {
if (isset($semantics->optional) && $semantics->optional) {
$field->optional = TRUE;
}
$function = $this->typeMap[$field->type]; $function = $this->typeMap[$field->type];
$found = TRUE; $found = TRUE;
break; break;
@ -2581,16 +2776,18 @@ class H5PContentValidator {
else { else {
// If validator is not found, something exists in content that does // If validator is not found, something exists in content that does
// not have a corresponding semantics field. Remove it. // 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); unset($group->$key);
} }
} }
} }
if (!(isset($semantics->optional) && $semantics->optional)) {
foreach ($semantics->fields as $field) { foreach ($semantics->fields as $field) {
if (!(isset($field->optional) && $field->optional)) { if (!(isset($field->optional) && $field->optional)) {
// Check if field is in group. // Check if field is in group.
if (! property_exists($group, $field->name)) { if (! property_exists($group, $field->name)) {
$this->h5pF->setErrorMessage($this->h5pF->t('No value given for mandatory field ' . $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 = $this->h5pC->loadLibrary($libspec['machineName'], $libspec['majorVersion'], $libspec['minorVersion']);
$library['semantics'] = $this->h5pC->loadLibrarySemantics($libspec['machineName'], $libspec['majorVersion'], $libspec['minorVersion']); $library['semantics'] = $this->h5pC->loadLibrarySemantics($libspec['machineName'], $libspec['majorVersion'], $libspec['minorVersion']);
$this->libraries[$value->library] = $library; $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 { else {
$library = $this->libraries[$value->library]; $library = $this->libraries[$value->library];
@ -2634,11 +2820,26 @@ class H5PContentValidator {
'type' => 'group', 'type' => 'group',
'fields' => $library['semantics'], 'fields' => $library['semantics'],
), FALSE); ), FALSE);
$validkeys = array('library', 'params'); $validkeys = array('library', 'params', 'subContentId');
if (isset($semantics->extraAttributes)) { if (isset($semantics->extraAttributes)) {
$validkeys = array_merge($validkeys, $semantics->extraAttributes); $validkeys = array_merge($validkeys, $semantics->extraAttributes);
} }
$this->filterParams($value, $validkeys); $this->filterParams($value, $validkeys);
if (isset($value->subContentId) && ! preg_match('/^\{?[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}?$/', $value->subContentId)) {
unset($value->subContentId);
}
// Find all dependencies for this library
$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++;
}
} }
/** /**

19
js/disable.js Normal file
View File

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

View File

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

View File

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

View File

@ -1,13 +1,12 @@
/*jshint -W083 */ /*jshint -W083 */
var H5PUpgrades = H5PUpgrades || {};
(function ($) { (function ($, Version) {
var info, $container, librariesCache = {}; var info, $container, librariesCache = {}, scriptsCache = {};
// Initialize // Initialize
$(document).ready(function () { $(document).ready(function () {
// Get library info // Get library info
info = H5PIntegration.getLibraryInfo(); info = H5PAdminIntegration.libraryInfo;
// Get and reset container // Get and reset container
$container = $('#h5p-admin-container').html('<p>' + info.message + '</p>'); $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. * Displays a throbber in the status field.
* *
@ -154,17 +72,83 @@ var H5PUpgrades = H5PUpgrades || {};
var self = this; var self = this;
// Get selected version // Get selected version
self.version = new Version(null, libraryId); self.version = new Version(info.versions[libraryId]);
self.version.libraryId = libraryId;
// Create throbber with loading text and progress // Create throbber with loading text and progress
self.throbber = new Throbber(info.inProgress.replace('%ver', self.version)); self.throbber = new Throbber(info.inProgress.replace('%ver', self.version));
self.started = new Date().getTime();
self.io = 0;
// Track number of working
self.working = 0;
var start = function () {
// Get the next batch // Get the next batch
self.nextBatch({ self.nextBatch({
libraryId: libraryId, libraryId: libraryId,
token: info.token token: info.token
}); });
};
if (window.Worker !== undefined) {
// Prepare our workers
self.initWorkers();
start();
} }
else {
// No workers, do the job ourselves
self.loadScript(info.scriptBaseUrl + '/h5p-content-upgrade-process.js' + info.buster, start);
}
}
/**
* Initialize workers
*/
ContentUpgrade.prototype.initWorkers = function () {
var self = this;
// Determine number of workers (defaults to 4)
var numWorkers = (window.navigator !== undefined && window.navigator.hardwareConcurrency ? window.navigator.hardwareConcurrency : 4);
self.workers = new Array(numWorkers);
// Register message handlers
var messageHandlers = {
done: function (result) {
self.workDone(result.id, result.params, this);
},
error: function (error) {
self.printError(error.err);
// Stop everything
self.terminate();
},
loadLibrary: function (details) {
var worker = this;
self.loadLibrary(details.name, new Version(details.version), function (err, library) {
if (err) {
// Reset worker?
return;
}
worker.postMessage({
action: 'libraryLoaded',
library: library
});
});
}
};
for (var i = 0; i < numWorkers; i++) {
self.workers[i] = new Worker(info.scriptBaseUrl + '/h5p-content-upgrade-worker.js' + info.buster);
self.workers[i].onmessage = function (event) {
if (event.data.action !== undefined && messageHandlers[event.data.action]) {
messageHandlers[event.data.action].call(this, event.data);
}
};
}
};
/** /**
* Get the next batch and start processing it. * Get the next batch and start processing it.
@ -174,12 +158,24 @@ var H5PUpgrades = H5PUpgrades || {};
ContentUpgrade.prototype.nextBatch = function (outData) { ContentUpgrade.prototype.nextBatch = function (outData) {
var self = this; var self = this;
// Track time spent on IO
var start = new Date().getTime();
$.post(info.infoUrl, outData, function (inData) { $.post(info.infoUrl, outData, function (inData) {
self.io += new Date().getTime() - start;
if (!(inData instanceof Object)) { if (!(inData instanceof Object)) {
// Print errors from backend // Print errors from backend
return self.setStatus(inData); return self.setStatus(inData);
} }
if (inData.left === 0) { if (inData.left === 0) {
var total = new Date().getTime() - self.started;
if (window.console && console.log) {
console.log('The upgrade process took ' + (total / 1000) + ' seconds. (' + (Math.round((self.io / (total / 100)) * 100) / 100) + ' % IO)' );
}
// Terminate workers
self.terminate();
// Nothing left to process // Nothing left to process
return self.setStatus(info.done); return self.setStatus(info.done);
} }
@ -208,116 +204,62 @@ var H5PUpgrades = H5PUpgrades || {};
*/ */
ContentUpgrade.prototype.processBatch = function (parameters) { ContentUpgrade.prototype.processBatch = function (parameters) {
var self = this; var self = this;
var upgraded = {}; // Track upgraded params
var current = 0; // Track progress // Track upgraded params
asyncSerial(parameters, function (id, params, next) { self.upgraded = {};
try { // Track current batch
// Make params possible to work with self.parameters = parameters;
params = JSON.parse(params);
if (!(params instanceof Object)) { // Create id mapping
throw true; self.ids = [];
for (var id in parameters) {
if (parameters.hasOwnProperty(id)) {
self.ids.push(id);
} }
} }
catch (event) {
return next(info.errorContent.replace('%id', id) + ' ' + info.errorParamsBroken); // Keep track of current content
self.current = -1;
if (self.workers !== undefined) {
// Assign each worker content to upgrade
for (var i = 0; i < self.workers.length; i++) {
self.assignWork(self.workers[i]);
} }
// 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);
} }
else {
upgraded[id] = JSON.stringify(params); self.assignWork();
current++;
self.throbber.setProgress(Math.round((info.total - self.left + current) / (info.total / 100)) + ' %');
next();
});
}, function (err) {
// Finished with all parameters that came in
if (err) {
return self.setStatus('<p>' + info.error + '<br/>' + err + '</p>');
} }
// Save upgraded content and get next round of data to process
self.nextBatch({
libraryId: self.version.libraryId,
token: self.token,
params: JSON.stringify(upgraded)
});
});
}; };
/** /**
* Upgade the given content.
* *
* @param {String} name
* @param {Version} oldVersion
* @param {Version} newVersion
* @param {Object} params
* @param {Function} next
* @returns {undefined}
*/ */
ContentUpgrade.prototype.upgrade = function (name, oldVersion, newVersion, params, next) { ContentUpgrade.prototype.assignWork = function (worker) {
var self = this; var self = this;
// Load library details and upgrade routines var id = self.ids[self.current + 1];
self.loadLibrary(name, newVersion, function (err, library) { if (id === undefined) {
if (err) { return false; // Out of work
return next(err);
} }
self.current++;
self.working++;
// Run upgrade routines on params if (worker) {
self.processParams(library, oldVersion, newVersion, params, function (err, params) { worker.postMessage({
if (err) { action: 'newJob',
return next(err); id: id,
name: info.library.name,
oldVersion: info.library.version,
newVersion: self.version.toString(),
params: self.parameters[id]
});
} }
else {
// Check if any of the sub-libraries need upgrading new H5P.ContentUpgradeProcess(info.library.name, new Version(info.library.version), self.version, self.parameters[id], id, function loadLibrary(name, version, next) {
asyncSerial(library.semantics, function (index, field, next) { self.loadLibrary(name, version, function (err, library) {
self.processField(field, params[field.name], function (err, upgradedParams) {
if (upgradedParams) {
params[field.name] = upgradedParams;
}
next(err);
});
}, function (err) {
next(err, params);
});
});
});
};
/**
* Load library data needed for content upgrade.
*
* @param {String} name
* @param {Version} version
* @param {Function} next
*/
ContentUpgrade.prototype.loadLibrary = function (name, version, next) {
var self = this;
var key = name + '/' + version.major + '/' + version.minor;
if (librariesCache[key] !== undefined) {
// Library has been loaded before. Return cache.
next(null, librariesCache[key]);
return;
}
$.ajax({
dataType: 'json',
cache: true,
url: info.libraryBaseUrl + '/' + key
}).fail(function () {
next(info.errorData.replace('%lib', name + ' ' + version));
}).done(function (library) {
librariesCache[key] = library;
if (library.upgradesScript) { if (library.upgradesScript) {
self.loadScript(library.upgradesScript, function (err) { self.loadScript(library.upgradesScript, function (err) {
if (err) { if (err) {
@ -330,6 +272,106 @@ var H5PUpgrades = H5PUpgrades || {};
next(null, library); next(null, library);
} }
}); });
}, function done(err, result) {
if (err) {
self.printError(err);
return ;
}
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.
*
* @param {String} name
* @param {Version} version
* @param {Function} next
*/
ContentUpgrade.prototype.loadLibrary = function (name, version, next) {
var self = this;
var key = name + '/' + version.major + '/' + version.minor;
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 (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 * @param {Function} next
*/ */
ContentUpgrade.prototype.loadScript = function (url, next) { ContentUpgrade.prototype.loadScript = function (url, next) {
var self = this;
if (scriptsCache[url] !== undefined) {
next();
return;
}
// Track time spent loading
var start = new Date().getTime();
$.ajax({ $.ajax({
dataType: 'script', dataType: 'script',
cache: true, cache: true,
url: url url: url
}).fail(function () { }).fail(function () {
self.io += new Date().getTime() - start;
next(true); next(true);
}).done(function () { }).done(function () {
scriptsCache[url] = true;
self.io += new Date().getTime() - start;
next(); next();
}); });
}; };
/** /**
* Run upgrade hooks on params.
* *
* @param {Object} library
* @param {Version} oldVersion
* @param {Version} newVersion
* @param {Object} params
* @param {Function} next
*/ */
ContentUpgrade.prototype.processParams = function (library, oldVersion, newVersion, params, next) { ContentUpgrade.prototype.printError = function (error) {
if (H5PUpgrades[library.name] === undefined) {
if (library.upgradesScript) {
// Upgrades script should be loaded so the upgrades should be here.
return next(info.errorScript.replace('%lib', library.name + ' ' + newVersion));
}
// No upgrades script. Move on
return next(null, params);
}
// Run upgrade hooks. Start by going through major versions
asyncSerial(H5PUpgrades[library.name], function (major, minors, nextMajor) {
if (major < oldVersion.major || major > newVersion.major) {
// Older than the current version or newer than the selected
nextMajor();
}
else {
// Go through the minor versions for this major version
asyncSerial(minors, function (minor, upgrade, nextMinor) {
if (minor <= oldVersion.minor || minor > newVersion.minor) {
// Older than or equal to the current version or newer than the selected
nextMinor();
}
else {
// We found an upgrade hook, run it
var unnecessaryWrapper = (upgrade.contentUpgrade !== undefined ? upgrade.contentUpgrade : upgrade);
try {
unnecessaryWrapper(params, function (err, upgradedParams) {
params = upgradedParams;
nextMinor(err);
});
}
catch (err) {
next(err);
}
}
}, nextMajor);
}
}, function (err) {
next(err, params);
});
};
/**
* Process parameter fields to find and upgrade sub-libraries.
*
* @param {Object} field
* @param {Object} params
* @param {Function} next
*/
ContentUpgrade.prototype.processField = function (field, params, next) {
var self = this; var self = this;
if (params === undefined) { if (error.type === 'errorParamsBroken') {
return next(); error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken;
}
else if (error.type === 'scriptMissing') {
error = info.errorScript.replace('%lib', error.library);
} }
switch (field.type) { self.setStatus('<p>' + info.error + '<br/>' + error + '</p>');
case 'library':
if (params.library === undefined || params.params === undefined) {
return next();
}
// Look for available upgrades
var usedLib = params.library.split(' ', 2);
for (var i = 0; i < field.options.length; i++) {
var availableLib = field.options[i].split(' ', 2);
if (availableLib[0] === usedLib[0]) {
if (availableLib[1] === usedLib[1]) {
return next(); // Same version
}
// We have different versions
var usedVer = new Version(usedLib[1]);
var availableVer = new Version(availableLib[1]);
if (usedVer.major > availableVer.major || (usedVer.major === availableVer.major && usedVer.minor >= availableVer.minor)) {
return next(); // Larger or same version that's available
}
// A newer version is available, upgrade params
return self.upgrade(availableLib[0], usedVer, availableVer, params.params, function (err, upgraded) {
if (!err) {
params.library = availableLib[0] + ' ' + availableVer.major + '.' + availableVer.minor;
params.params = upgraded;
}
next(err, params);
});
}
}
next();
break;
case 'group':
if (field.fields.length === 1) {
// Single field to process, wrapper will be skipped
self.processField(field.fields[0], params, function (err, upgradedParams) {
if (upgradedParams) {
params = upgradedParams;
}
next(err, params);
});
}
else {
// Go through all fields in the group
asyncSerial(field.fields, function (index, subField, next) {
self.processField(subField, params[subField.name], function (err, upgradedParams) {
if (upgradedParams) {
params[subField.name] = upgradedParams;
}
next(err);
});
}, function (err) {
next(err, params);
});
}
break;
case 'list':
// Go trough all params in the list
asyncSerial(params, function (index, subParams, next) {
self.processField(field.field, subParams, function (err, upgradedParams) {
if (upgradedParams) {
params[index] = upgradedParams;
}
next(err);
});
}, function (err) {
next(err, params);
});
break;
default:
next();
}
}; };
})(H5P.jQuery); })(H5P.jQuery, H5P.Version);

View File

@ -32,8 +32,9 @@ var H5PDataView = (function ($) {
* search in column 2. * search in column 2.
* @param {Function} loaded * @param {Function} loaded
* Callback for when data has been 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; var self = this;
self.$container = $(container).addClass('h5p-data-view').html(''); self.$container = $(container).addClass('h5p-data-view').html('');
@ -44,6 +45,7 @@ var H5PDataView = (function ($) {
self.classes = (classes === undefined ? {} : classes); self.classes = (classes === undefined ? {} : classes);
self.filters = (filters === undefined ? [] : filters); self.filters = (filters === undefined ? [] : filters);
self.loaded = loaded; self.loaded = loaded;
self.order = order;
self.limit = 20; self.limit = 20;
self.offset = 0; self.offset = 0;
@ -68,8 +70,8 @@ var H5PDataView = (function ($) {
url += (url.indexOf('?') === -1 ? '?' : '&') + 'offset=' + self.offset + '&limit=' + self.limit; url += (url.indexOf('?') === -1 ? '?' : '&') + 'offset=' + self.offset + '&limit=' + self.limit;
// Add sorting // Add sorting
if (self.sortBy !== undefined && self.sortDir !== undefined) { if (self.order !== undefined) {
url += '&sortBy=' + self.sortBy + '&sortDir=' + self.sortDir; url += '&sortBy=' + self.order.by + '&sortDir=' + self.order.dir;
} }
// Add filters // Add filters
@ -144,12 +146,11 @@ var H5PDataView = (function ($) {
// Create new table // Create new table
self.table = new H5PUtils.Table(self.classes, self.headers); self.table = new H5PUtils.Table(self.classes, self.headers);
self.table.setHeaders(self.headers, function (col, dir) { self.table.setHeaders(self.headers, function (order) {
// Sorting column or direction has changed callback. // Sorting column or direction has changed.
self.sortBy = col; self.order = order;
self.sortDir = dir;
self.loadData(); self.loadData();
}); }, self.order);
self.table.appendTo(self.$container); self.table.appendTo(self.$container);
} }

View File

@ -1,69 +1,27 @@
/*jshint multistr: true */ /*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 head = document.getElementsByTagName('head')[0];
var contentId = 0; var resizer = false;
var contents = {};
/** /**
* Wraps multiple content between a prefix and a suffix. * Loads the resizing script
*/ */
var wrap = function (prefix, content, suffix) { var loadResizer = function (url) {
var result = ''; var data, callback = 'H5POldEmbed';
for (var i = 0; i < content.length; i++) { resizer = true;
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');
// Callback for when content data is loaded. // Callback for when content data is loaded.
window[callback] = function (content) { window[callback] = function (content) {
contents[id] = content; // Add resizing script to head
var resizer = document.createElement('script');
var iframe = document.createElement('iframe'); resizer.src = content;
var parent = script.parentNode; head.appendChild(resizer);
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';
// Clean up // Clean up
parent.removeChild(script);
head.removeChild(data); head.removeChild(data);
delete window[callback]; delete window[callback];
}; };
@ -74,183 +32,44 @@ var H5P = H5P || (function () {
head.appendChild(data); 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. * Go throught all script tags with the data-h5p attribute and load content.
*/ */
function H5P() { function H5POldEmbed() {
var scripts = document.getElementsByTagName('script'); var scripts = document.getElementsByTagName('script');
var h5ps = []; // Use seperate array since scripts grow in size. var h5ps = []; // Use seperate array since scripts grow in size.
for (var i = 0; i < scripts.length; i++) { for (var i = 0; i < scripts.length; i++) {
var script = scripts[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); h5ps.push(script);
} }
} }
for (i = 0; i < h5ps.length; i++) { for (i = 0; i < h5ps.length; i++) {
loadContent(contentId, h5ps[i]); if (!resizer) {
contentId++; loadResizer(h5ps[i].getAttribute('data-h5p'));
} }
}; addIframe(h5ps[i]);
/**
* 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;
}
};
};
// 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';
} }
} }
else if (document.documentElement.mozRequestFullScreen) {
fullScreenBrowserPrefix = 'moz';
}
else if (document.documentElement.msRequestFullscreen) {
fullScreenBrowserPrefix = 'ms';
}
/** return H5POldEmbed;
* 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;
})(); })();
new H5P(); new H5POldEmbed();

224
js/h5p-event-dispatcher.js Normal file
View File

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

View File

@ -7,8 +7,8 @@ var H5PLibraryDetails= H5PLibraryDetails || {};
* Initializing * Initializing
*/ */
H5PLibraryDetails.init = function () { H5PLibraryDetails.init = function () {
H5PLibraryDetails.$adminContainer = H5PIntegration.getAdminContainer(); H5PLibraryDetails.$adminContainer = H5P.jQuery(H5PAdminIntegration.containerSelector);
H5PLibraryDetails.library = H5PIntegration.getLibraryInfo(); H5PLibraryDetails.library = H5PAdminIntegration.libraryInfo;
// currentContent holds the current list if data (relevant for filtering) // currentContent holds the current list if data (relevant for filtering)
H5PLibraryDetails.currentContent = H5PLibraryDetails.library.content; H5PLibraryDetails.currentContent = H5PLibraryDetails.library.content;

View File

@ -7,15 +7,15 @@ var H5PLibraryList = H5PLibraryList || {};
* Initializing * Initializing
*/ */
H5PLibraryList.init = function () { 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) { if (libraryList.notCached) {
$adminContainer.append(H5PUtils.getRebuildCache(libraryList.notCached)); $adminContainer.append(H5PUtils.getRebuildCache(libraryList.notCached));
} }
// Create library list // 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 * @param {object} libraries List of libraries and headers
*/ */
H5PLibraryList.createLibraryList = function (libraries) { H5PLibraryList.createLibraryList = function (libraries) {
var t = H5PIntegration.i18n.H5P; var t = H5PAdminIntegration.l10n;
if(libraries.listData === undefined || libraries.listData.length === 0) { if(libraries.listData === undefined || libraries.listData.length === 0) {
return $('<div>' + t.NA + '</div>'); return $('<div>' + t.NA + '</div>');
} }

131
js/h5p-resizer.js Normal file
View File

@ -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, '*');
}
}
})();

View File

@ -7,7 +7,7 @@ var H5PUtils = H5PUtils || {};
* @param {array} headers List of headers * @param {array} headers List of headers
*/ */
H5PUtils.createTable = function (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) { if(headers) {
var $thead = $('<thead></thead>'); var $thead = $('<thead></thead>');
@ -182,18 +182,30 @@ var H5PUtils = H5PUtils || {};
if (sortByCol !== undefined && col.sortable === true) { if (sortByCol !== undefined && col.sortable === true) {
// Make sortable // Make sortable
options.role = 'button'; options.role = 'button';
options.tabIndex = 1; options.tabIndex = 0;
// This is the first sortable column, use as default sort // This is the first sortable column, use as default sort
if (sortCol === undefined) { if (sortCol === undefined) {
sortCol = id; sortCol = id;
sortDir = 0; sortDir = 0;
}
// This is the sort column
if (sortCol === id) {
options['class'] = 'h5p-sort'; options['class'] = 'h5p-sort';
if (sortDir === 1) {
options['class'] += ' h5p-reverse';
}
} }
options.on.click = function () { options.on.click = function () {
sort($th, id); 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; sortDir = 0;
} }
sortByCol(sortCol, sortDir); sortByCol({
by: sortCol,
dir: sortDir
});
}; };
/** /**
@ -244,11 +259,17 @@ var H5PUtils = H5PUtils || {};
* "text" and "sortable". E.g. * "text" and "sortable". E.g.
* [{text: 'Col 1', sortable: true}, 'Col 2', 'Col 3'] * [{text: 'Col 1', sortable: true}, 'Col 2', 'Col 3']
* @param {Function} sort Callback which is runned when sorting changes * @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; numCols = cols.length;
sortByCol = sort; sortByCol = sort;
if (order) {
sortCol = order.by;
sortDir = order.dir;
}
// Create new head // Create new head
var $newThead = $('<thead/>'); var $newThead = $('<thead/>');
var $tr = $('<tr/>').appendTo($newThead); var $tr = $('<tr/>').appendTo($newThead);

27
js/h5p-version.js Normal file
View File

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

239
js/h5p-x-api-event.js Normal file
View File

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

85
js/h5p-x-api.js Normal file
View File

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

801
js/h5p.js

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long