From 5922786982b046e0029878b8b550f4373939c2d7 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 13 Oct 2014 22:19:59 +0200 Subject: [PATCH 001/149] xAPI --- js/h5p.js | 179 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 165 insertions(+), 14 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 3b5db69..fd45256 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -7,6 +7,8 @@ H5P.isFramed = (window.self !== window.top); // Useful jQuery object. H5P.$window = H5P.jQuery(window); +H5P.instances = []; + // Detect if we support fullscreen, and what prefix to use. if (document.documentElement.requestFullScreen) { H5P.fullScreenBrowserPrefix = ''; @@ -111,28 +113,30 @@ H5P.init = function () { }; var resizeDelay; - instance.$.on('resize', function () { + instance.addH5PEventListener('resize', function () { // Use a delay to make sure iframe is resized to the correct size. clearTimeout(resizeDelay); resizeDelay = setTimeout(function () { resizeIframe(); }, 1); }); + instance.addH5PEventListener('xAPI', H5P.xAPIListener); + H5P.instances.push(instance); } // Resize everything when window is resized. $window.resize(function () { if (window.parent.H5P.isFullscreen) { // Use timeout to avoid bug in certain browsers when exiting fullscreen. Some browser will trigger resize before the fullscreenchange event. - instance.$.trigger('resize'); + instance.triggerH5PEvent('resize'); } else { - instance.$.trigger('resize'); + instance.triggerH5PEvent('resize'); } }); // Resize content. - instance.$.trigger('resize'); + instance.triggerH5PEvent('resize'); }); // Insert H5Ps that should be in iframes. @@ -144,6 +148,15 @@ H5P.init = function () { }); }; +H5P.xAPIListener = function(event) { + if (event.verb === 'completed') { + var points = event.result.score.raw; + var maxPoints = event.result.score.max; + var contentId = event.object.contentId; + H5P.setFinished(contentId, points, maxPoints); + } +} + /** * Enable full screen for the given h5p. * @@ -196,8 +209,8 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { */ var entered = function () { // Do not rely on window resize events. - instance.$.trigger('resize'); - instance.$.trigger('focus'); + instance.triggerH5PEvent('resize'); + instance.triggerH5PEvent('focus'); }; /** @@ -211,8 +224,8 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { $classes.removeClass(classes); // Do not rely on window resize events. - instance.$.trigger('resize'); - instance.$.trigger('focus'); + instance.triggerH5PEvent('resize'); + instance.triggerH5PEvent('focus'); if (exitCallback !== undefined) { exitCallback(); @@ -341,9 +354,10 @@ H5P.classFromName = function (name) { * @param {Number} contentId * @param {jQuery} $attachTo An optional element to attach the instance to. * @param {Boolean} skipResize Optionally skip triggering of the resize event after attaching. + * @param {Object} The parent of this H5P * @return {Object} Instance. */ -H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { +H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { try { var nameSplit = library.library.split(' ', 2); var versionSplit = nameSplit[1].split('.', 2); @@ -373,22 +387,159 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { } var instance = new constructor(library.params, contentId); - - if (instance.$ === undefined) { - instance.$ = H5P.jQuery(instance); + + H5P.addContentTypeFeatures(instance); + + // Make xAPI events bubble +// if (parent !== null && parent.triggerH5PEvent !== undefined) { +// instance.addH5PEventListener('xAPI', parent.triggerH5PEvent); +// } + + // Automatically call resize on resize event if defined + if (typeof instance.resize === 'function') { + instance.addH5PEventListener('resize', instance.resize); } - + if ($attachTo !== undefined) { instance.attach($attachTo); if (skipResize === undefined || !skipResize) { // Resize content. - instance.$.trigger('resize'); + instance.triggerH5PEvent('resize'); } } return instance; }; +/** + * Add features like event handling to an H5P content type library + * + * @param instance + * An H5P content type instance + */ +H5P.addContentTypeFeatures = function(instance) { + if (instance.H5PListeners === undefined) { + instance.H5PListeners = {}; + } + if (instance.addH5PEventListener === undefined) { + instance.addH5PEventListener = function(type, listener) { + if (typeof listener === 'function') { + if (this.H5PListeners[type] === undefined) { + this.H5PListeners[type] = []; + } + this.H5PListeners[type].push(listener); + } + } + } + if (instance.removeH5PEventListener === undefined) { + instance.removeH5PEventListener = function (type, listener) { + if (this.H5PListeners[type] !== undefined) { + var removeIndex = H5PListeners[type].indexOf(listener); + if (removeIndex) { + H5PListeners[type].splice(removeIndex, 1); + } + } + } + } + if (instance.triggerH5PEvent === undefined) { + instance.triggerH5PEvent = function (type, event) { + if (this.H5PListeners[type] !== undefined) { + for (var i = 0; i < this.H5PListeners[type].length; i++) { + this.H5PListeners[type][i](event); + } + } + } + } + if (instance.triggerH5PxAPIEvent === undefined) { + instance.triggerH5PxAPIEvent = function(verb, extra) { + var event = { + 'actor': H5P.getActor(), + 'verb': H5P.getxAPIVerb(verb) + } + if (extra !== undefined) { + for (var i in extra) { + event[i] = extra[i]; + } + } + if (!'object' in event) { + event.object = H5P.getxAPIObject(this); + } + this.triggerH5PEvent('xAPI', event); + } + } +} + +H5P.getActor = function() { + // TODO: Implement or remove? + + // Dummy data... + return { + 'name': 'Ernst Petter', + 'mbox': 'ernst@petter.com' + } +} + +H5P.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' +] + +H5P.getxAPIVerb = function(verb) { + if (H5P.jQuery.inArray(verb, H5P.allowedxAPIVerbs) !== -1) { + return { + 'id': 'http://adlnet.gov/expapi/verbs/' . verb, + 'display': { + 'en-US': verb + } + } + } + // Else: Fail silently... +} + +H5P.getxAPIObject = function(instance) { + // TODO: Implement or remove? + + // Return dummy data... + return { + 'id': 'http://mysite.com/path-to-main-activity#sub-activity', + 'contentId': instance.contentId, + 'reference': instance + } +} + +H5P.getxAPIScoredResult = function(score, maxScore) { + return { + 'score': { + 'min': 0, + 'max': maxScore, + 'raw': score + } + } +} + /** * Used to print useful error messages. * From 2636ddc39350f689fc673da3bda3d802d3bbdf30 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Thu, 23 Oct 2014 14:02:34 +0200 Subject: [PATCH 002/149] Make it easy to disable generic content features. --- js/h5p.js | 78 ++++++++++++++++++++++++++++++++++++-------------- styles/h5p.css | 11 ++++--- 2 files changed, 63 insertions(+), 26 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index ef5b3e7..6f45768 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -30,7 +30,29 @@ else if (document.documentElement.msRequestFullscreen) { H5P.fullScreenBrowserPrefix = 'ms'; } -// Keep track of when the H5Ps where started +/** @const {Number} */ +H5P.DISABLE_NONE = 0; + +/** @const {Number} */ +H5P.DISABLE_FRAME = 1; + +/** @const {Number} */ +H5P.DISABLE_DOWNLOAD = 2; + +/** @const {Number} */ +H5P.DISABLE_EMBED = 4; + +/** @const {Number} */ +H5P.DISABLE_COPYRIGHT = 8; + +/** @const {Number} */ +H5P.DISABLE_ABOUT = 16; + +/** + * Keep track of when the H5Ps where started. + * + * @type {Array} + */ H5P.opened = {}; /** @@ -68,29 +90,41 @@ H5P.init = function () { }); } - var $actions = H5P.jQuery(''); - if (contentData.exportUrl !== '') { - // Display export button - H5P.jQuery('
  • ' + H5P.t('download') + '
  • ').appendTo($actions).click(function () { - window.location.href = contentData.exportUrl; - }); + if (contentData.disable & H5P.DISABLE_FRAME) { + $element.addClass('h5p-no-frame'); } - if (instance.getCopyrights !== undefined) { - // Display copyrights button - H5P.jQuery('
  • ' + H5P.t('copyrights') + '
  • ').appendTo($actions).click(function () { - H5P.openCopyrightsDialog($actions, instance); - }); + else { + // Create action bar + var $actions = H5P.jQuery(''); + + if (!(contentData.disable & H5P.DISABLE_DOWNLOAD)) { + // Add export button + H5P.jQuery('
  • ' + H5P.t('download') + '
  • ').appendTo($actions).click(function () { + window.location.href = contentData.exportUrl; + }); + } + if (!(contentData.disable & H5P.DISABLE_COPYRIGHT) && instance.getCopyrights !== undefined) { + // Add copyrights button + H5P.jQuery('
  • ' + H5P.t('copyrights') + '
  • ').appendTo($actions).click(function () { + H5P.openCopyrightsDialog($actions, instance); + }); + } + if (!(contentData.disable & H5P.DISABLE_EMBED)) { + // Add embed button + H5P.jQuery('
  • ' + H5P.t('embed') + '
  • ').appendTo($actions).click(function () { + H5P.openEmbedDialog($actions, contentData.embedCode); + }); + } + if (!(contentData.disable & H5P.DISABLE_ABOUT)) { + // Add about H5P button icon + H5P.jQuery('
  • ').appendTo($actions); + } + + // Insert action bar if it has any content + if ($actions.children().length) { + $actions.insertAfter($container); + } } - if (contentData.embedCode !== undefined) { - // Display embed button - H5P.jQuery('
  • ' + H5P.t('embed') + '
  • ').appendTo($actions).click(function () { - H5P.openEmbedDialog($actions, contentData.embedCode); - }); - } - if (H5PIntegration.showH5PIconInActionBar()) { - H5P.jQuery('
  • ').appendTo($actions); - } - $actions.insertAfter($container); // Keep track of when we started H5P.opened[contentId] = new Date(); diff --git a/styles/h5p.css b/styles/h5p.css index c9099c6..66aa3c9 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -4,9 +4,9 @@ @font-face { font-family: 'H5P'; src: url('../fonts/h5p.eot?ver=1.2.1'); - src: url('../fonts/h5p.eot?#iefix&ver=1.2.1') format('embedded-opentype'), + src: url('../fonts/h5p.eot?#iefix&ver=1.2.1') format('embedded-opentype'), url('data:application/font-woff;base64,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') format('woff'), - url('../fonts/h5p.ttf?ver=1.2.1') format('truetype'), + url('../fonts/h5p.ttf?ver=1.2.1') format('truetype'), url('../fonts/h5p.svg?ver=1.2.1#h5pregular') format('svg'); font-weight: normal; font-style: normal; @@ -36,6 +36,9 @@ html.h5p-iframe .h5p-content { width: 100%; height: 100%; } +.h5p-content.h5p-no-frame { + border: 0; +} .h5p-container { position: relative; z-index: 1; @@ -262,7 +265,7 @@ div.h5p-fullscreen { } .h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar-track { background: #e0e0e0; -} +} .h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar-thumb { box-shadow: 0 0 10px #000 inset; border-radius: 4px; @@ -322,4 +325,4 @@ div.h5p-fullscreen { padding-left: 38px; min-height: 30px; line-height: 30px; -} \ No newline at end of file +} From 22a559ccd872b30fbf6557650f4f761820f7139a Mon Sep 17 00:00:00 2001 From: falcon Date: Sun, 26 Oct 2014 22:18:28 +0100 Subject: [PATCH 003/149] Shorter function names and a few bugfixes --- js/h5p.js | 72 +++++++++++++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index fd45256..4cac7f6 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -113,30 +113,30 @@ H5P.init = function () { }; var resizeDelay; - instance.addH5PEventListener('resize', function () { + instance.on('resize', function () { // Use a delay to make sure iframe is resized to the correct size. clearTimeout(resizeDelay); resizeDelay = setTimeout(function () { resizeIframe(); }, 1); }); - instance.addH5PEventListener('xAPI', H5P.xAPIListener); H5P.instances.push(instance); } + instance.on('xAPI', H5P.xAPIListener); // Resize everything when window is resized. $window.resize(function () { if (window.parent.H5P.isFullscreen) { // Use timeout to avoid bug in certain browsers when exiting fullscreen. Some browser will trigger resize before the fullscreenchange event. - instance.triggerH5PEvent('resize'); + instance.trigger('resize'); } else { - instance.triggerH5PEvent('resize'); + instance.trigger('resize'); } }); // Resize content. - instance.triggerH5PEvent('resize'); + instance.trigger('resize'); }); // Insert H5Ps that should be in iframes. @@ -149,7 +149,7 @@ H5P.init = function () { }; H5P.xAPIListener = function(event) { - if (event.verb === 'completed') { + if (event.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { var points = event.result.score.raw; var maxPoints = event.result.score.max; var contentId = event.object.contentId; @@ -209,8 +209,8 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { */ var entered = function () { // Do not rely on window resize events. - instance.triggerH5PEvent('resize'); - instance.triggerH5PEvent('focus'); + instance.trigger('resize'); + instance.trigger('focus'); }; /** @@ -224,8 +224,8 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { $classes.removeClass(classes); // Do not rely on window resize events. - instance.triggerH5PEvent('resize'); - instance.triggerH5PEvent('focus'); + instance.trigger('resize'); + instance.trigger('focus'); if (exitCallback !== undefined) { exitCallback(); @@ -391,13 +391,13 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { H5P.addContentTypeFeatures(instance); // Make xAPI events bubble -// if (parent !== null && parent.triggerH5PEvent !== undefined) { -// instance.addH5PEventListener('xAPI', parent.triggerH5PEvent); +// if (parent !== null && parent.trigger !== undefined) { +// instance.on('xAPI', parent.trigger); // } // Automatically call resize on resize event if defined if (typeof instance.resize === 'function') { - instance.addH5PEventListener('resize', instance.resize); + instance.on('resize', instance.resize); } if ($attachTo !== undefined) { @@ -405,7 +405,7 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { if (skipResize === undefined || !skipResize) { // Resize content. - instance.triggerH5PEvent('resize'); + instance.trigger('resize'); } } return instance; @@ -418,40 +418,40 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { * An H5P content type instance */ H5P.addContentTypeFeatures = function(instance) { - if (instance.H5PListeners === undefined) { - instance.H5PListeners = {}; + if (instance.listeners === undefined) { + instance.listeners = {}; } - if (instance.addH5PEventListener === undefined) { - instance.addH5PEventListener = function(type, listener) { + if (instance.on === undefined) { + instance.on = function(type, listener) { if (typeof listener === 'function') { - if (this.H5PListeners[type] === undefined) { - this.H5PListeners[type] = []; + if (this.listeners[type] === undefined) { + this.listeners[type] = []; } - this.H5PListeners[type].push(listener); + this.listeners[type].push(listener); } } } - if (instance.removeH5PEventListener === undefined) { - instance.removeH5PEventListener = function (type, listener) { - if (this.H5PListeners[type] !== undefined) { - var removeIndex = H5PListeners[type].indexOf(listener); + if (instance.off === undefined) { + instance.off = function (type, listener) { + if (this.listeners[type] !== undefined) { + var removeIndex = listeners[type].indexOf(listener); if (removeIndex) { - H5PListeners[type].splice(removeIndex, 1); + listeners[type].splice(removeIndex, 1); } } } } - if (instance.triggerH5PEvent === undefined) { - instance.triggerH5PEvent = function (type, event) { - if (this.H5PListeners[type] !== undefined) { - for (var i = 0; i < this.H5PListeners[type].length; i++) { - this.H5PListeners[type][i](event); + if (instance.trigger === undefined) { + instance.trigger = function (type, event) { + if (this.listeners[type] !== undefined) { + for (var i = 0; i < this.listeners[type].length; i++) { + this.listeners[type][i](event); } } } } - if (instance.triggerH5PxAPIEvent === undefined) { - instance.triggerH5PxAPIEvent = function(verb, extra) { + if (instance.triggerXAPI === undefined) { + instance.triggerXAPI = function(verb, extra) { var event = { 'actor': H5P.getActor(), 'verb': H5P.getxAPIVerb(verb) @@ -461,10 +461,10 @@ H5P.addContentTypeFeatures = function(instance) { event[i] = extra[i]; } } - if (!'object' in event) { + if (!('object' in event)) { event.object = H5P.getxAPIObject(this); } - this.triggerH5PEvent('xAPI', event); + this.trigger('xAPI', event); } } } @@ -510,7 +510,7 @@ H5P.allowedxAPIVerbs = [ H5P.getxAPIVerb = function(verb) { if (H5P.jQuery.inArray(verb, H5P.allowedxAPIVerbs) !== -1) { return { - 'id': 'http://adlnet.gov/expapi/verbs/' . verb, + 'id': 'http://adlnet.gov/expapi/verbs/' + verb, 'display': { 'en-US': verb } From 9391b60a034a55962aa9101ed484d7b0c9121e45 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sat, 1 Nov 2014 21:54:42 +0100 Subject: [PATCH 004/149] Refactoring xAPI to use inheritence and event classes --- h5p.classes.php | 4 +- js/h5p.js | 214 +++++++++++++++++++++++++----------------------- 2 files changed, 114 insertions(+), 104 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 5b23280..c5b93c1 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2163,7 +2163,7 @@ class H5PCore { */ public function getLibrariesMetadata() { // Fetch from cache: - $metadata = $this->h5pF->cacheGet('libraries','metadata'); +// $metadata = $this->h5pF->cacheGet('libraries','metadata'); // If not available in cache, or older than a week => refetch! if ($metadata === NULL || $metadata->lastTimeFetched < (time() - self::SECONDS_IN_WEEK)) { @@ -2174,7 +2174,7 @@ class H5PCore { $metadata->json = ($json === FALSE ? NULL : json_decode($json)); $metadata->lastTimeFetched = time(); - $this->h5pF->cacheSet('libraries','metadata', $metadata); + // $this->h5pF->cacheSet('libraries','metadata', $metadata); } return $metadata->json; diff --git a/js/h5p.js b/js/h5p.js index 020d699..a32b965 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -95,13 +95,6 @@ H5P.init = function () { // Keep track of when we started H5P.opened[contentId] = new Date(); - // Handle events when the user finishes the content. Useful for logging exercise results. - instance.$.on('finish', function (event) { - if (event.data !== undefined) { - H5P.setFinished(contentId, event.data.score, event.data.maxScore, event.data.time); - } - }); - if (H5P.isFramed) { // Make it possible to resize the iframe when the content changes size. This way we get no scrollbars. var iframe = window.parent.document.getElementById('h5p-iframe-' + contentId); @@ -162,11 +155,14 @@ H5P.init = function () { }; H5P.xAPIListener = function(event) { - if (event.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { - var points = event.result.score.raw; - var maxPoints = event.result.score.max; - var contentId = event.object.contentId; - H5P.setFinished(contentId, points, maxPoints); + console.log(event); + if ('verb' in event.statement) { + if (event.statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { + var points = event.statement.result.score.raw; + var maxPoints = event.statement.result.score.max; + var contentId = event.statement.object.contentId; + H5P.setFinished(contentId, points, maxPoints); + } } } @@ -403,8 +399,6 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { var instance = new constructor(library.params, contentId); - H5P.addContentTypeFeatures(instance); - // Make xAPI events bubble // if (parent !== null && parent.trigger !== undefined) { // instance.on('xAPI', parent.trigger); @@ -426,64 +420,113 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { return instance; }; -/** - * Add features like event handling to an H5P content type library - * - * @param instance - * An H5P content type instance - */ -H5P.addContentTypeFeatures = function(instance) { - if (instance.listeners === undefined) { - instance.listeners = {}; - } - if (instance.on === undefined) { - instance.on = function(type, listener) { - if (typeof listener === 'function') { - if (this.listeners[type] === undefined) { - this.listeners[type] = []; - } - this.listeners[type].push(listener); - } +H5P.EventEnabled = function() { + this.listeners = {}; +}; + +H5P.EventEnabled.prototype.on = function(type, listener) { + if (typeof listener === 'function') { + if (this.listeners[type] === undefined) { + this.listeners[type] = []; } + this.listeners[type].push(listener); } - if (instance.off === undefined) { - instance.off = function (type, listener) { - if (this.listeners[type] !== undefined) { - var removeIndex = listeners[type].indexOf(listener); - if (removeIndex) { - listeners[type].splice(removeIndex, 1); - } - } - } - } - if (instance.trigger === undefined) { - instance.trigger = function (type, event) { - if (this.listeners[type] !== undefined) { - for (var i = 0; i < this.listeners[type].length; i++) { - this.listeners[type][i](event); - } - } - } - } - if (instance.triggerXAPI === undefined) { - instance.triggerXAPI = function(verb, extra) { - var event = { - 'actor': H5P.getActor(), - 'verb': H5P.getxAPIVerb(verb) - } - if (extra !== undefined) { - for (var i in extra) { - event[i] = extra[i]; - } - } - if (!('object' in event)) { - event.object = H5P.getxAPIObject(this); - } - this.trigger('xAPI', event); +} + +H5P.EventEnabled.prototype.off = function (type, listener) { + if (this.listeners[type] !== undefined) { + var removeIndex = listeners[type].indexOf(listener); + if (removeIndex) { + listeners[type].splice(removeIndex, 1); } } } +H5P.EventEnabled.prototype.trigger = function (type, event) { + if (event === null) { + event = new H5P.Event(); + } + if (this.listeners[type] !== undefined) { + for (var i = 0; i < this.listeners[type].length; i++) { + this.listeners[type][i](event); + } + } +} + +H5P.Event = function() { + // We're going to add bubbling, propagation and other features here later +} + +H5P.XAPIEvent = function() { + H5P.Event.call(this); + this.statement = {}; +} + +H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype); +H5P.XAPIEvent.prototype.constructor = H5P.XAPIEvent; + +H5P.XAPIEvent.prototype.setScoredResult = function(score, maxScore) { + this.statement.result = { + 'score': { + 'min': 0, + 'max': maxScore, + 'raw': score + } + } +} + +H5P.XAPIEvent.prototype.setVerb = function(verb) { + if (H5P.jQuery.inArray(verb, H5P.XAPIEvent.allowedXAPIVerbs) !== -1) { + this.statement.verb = { + 'id': 'http://adlnet.gov/expapi/verbs/' + verb, + 'display': { + 'en-US': verb + } + } + } + else { + console.log('illegal verb'); + } + // Else: Fail silently... +} + +H5P.XAPIEvent.prototype.setObject = function(instance) { + // TODO: Implement the correct id(url) + + // Return dummy data... + this.statement.object = { + 'id': 'http://mysite.com/path-to-main-activity#sub-activity', + 'contentId': instance.contentId, + 'reference': instance + } +} + +H5P.XAPIEvent.prototype.setActor = function() { + this.statement.actor = H5P.getActor(); +} + +H5P.EventEnabled.prototype.triggerXAPI = function(verb, extra) { + var event = this.createXAPIEventTemplate(verb, extra); + this.trigger('xAPI', event); +} + +H5P.EventEnabled.prototype.createXAPIEventTemplate = function(verb, extra) { + var event = new H5P.XAPIEvent(); + + event.setActor(); + event.setVerb(verb); + if (extra !== undefined) { + for (var i in extra) { + event.statement[i] = extra[i]; + } + } + if (!('object' in event)) { + event.setObject(this); + } + return event; +} + + H5P.getActor = function() { // TODO: Implement or remove? @@ -494,7 +537,7 @@ H5P.getActor = function() { } } -H5P.allowedxAPIVerbs = [ +H5P.XAPIEvent.allowedXAPIVerbs = [ 'answered', 'asked', 'attempted', @@ -522,39 +565,6 @@ H5P.allowedxAPIVerbs = [ 'voided' ] -H5P.getxAPIVerb = function(verb) { - if (H5P.jQuery.inArray(verb, H5P.allowedxAPIVerbs) !== -1) { - return { - 'id': 'http://adlnet.gov/expapi/verbs/' + verb, - 'display': { - 'en-US': verb - } - } - } - // Else: Fail silently... -} - -H5P.getxAPIObject = function(instance) { - // TODO: Implement or remove? - - // Return dummy data... - return { - 'id': 'http://mysite.com/path-to-main-activity#sub-activity', - 'contentId': instance.contentId, - 'reference': instance - } -} - -H5P.getxAPIScoredResult = function(score, maxScore) { - return { - 'score': { - 'min': 0, - 'max': maxScore, - 'raw': score - } - } -} - /** * Used to print useful error messages. * @@ -1212,8 +1222,8 @@ H5P.setFinished = function (contentId, score, maxScore, time) { // TODO: Should we use a variable with the complete path? H5P.jQuery.post(H5P.ajaxPath + 'setFinished', { contentId: contentId, - score: score, - maxScore: maxScore, + points: score, + maxPoints: maxScore, opened: toUnix(H5P.opened[contentId]), finished: toUnix(new Date()), time: time From 01779c000468f90ad6e1d5189805029ba743e4eb Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 2 Nov 2014 21:16:19 +0100 Subject: [PATCH 005/149] Use score instead of points --- js/h5p.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 1ce6024..c05c51d 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -158,10 +158,10 @@ H5P.xAPIListener = function(event) { console.log(event); if ('verb' in event.statement) { if (event.statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { - var points = event.statement.result.score.raw; - var maxPoints = event.statement.result.score.max; + var score = event.statement.result.score.raw; + var maxScore = event.statement.result.score.max; var contentId = event.statement.object.contentId; - H5P.setFinished(contentId, points, maxPoints); + H5P.setFinished(contentId, score, maxScore); } } } @@ -1222,8 +1222,8 @@ H5P.setFinished = function (contentId, score, maxScore, time) { // TODO: Should we use a variable with the complete path? H5P.jQuery.post(H5P.ajaxPath + 'setFinished', { contentId: contentId, - points: score, - maxPoints: maxScore, + score: score, + maxScore: maxScore, opened: toUnix(H5P.opened[contentId]), finished: toUnix(new Date()), time: time From 378224f7c6942f6f39477a94d0a753f7b4ad2693 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 2 Nov 2014 21:16:27 +0100 Subject: [PATCH 006/149] Get real actor --- js/h5p.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index c05c51d..0e47ece 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -528,13 +528,7 @@ H5P.EventEnabled.prototype.createXAPIEventTemplate = function(verb, extra) { H5P.getActor = function() { - // TODO: Implement or remove? - - // Dummy data... - return { - 'name': 'Ernst Petter', - 'mbox': 'ernst@petter.com' - } + return H5PIntegration.getActor(); } H5P.XAPIEvent.allowedXAPIVerbs = [ From 5d32c4451cbb6a6946e46456c3cb1b5ce75601e7 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 2 Nov 2014 21:30:19 +0100 Subject: [PATCH 007/149] Add real url to statement object --- js/h5p.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 0e47ece..7cf7388 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -491,13 +491,10 @@ H5P.XAPIEvent.prototype.setVerb = function(verb) { } H5P.XAPIEvent.prototype.setObject = function(instance) { - // TODO: Implement the correct id(url) - - // Return dummy data... this.statement.object = { - 'id': 'http://mysite.com/path-to-main-activity#sub-activity', - 'contentId': instance.contentId, - 'reference': instance + // TODO: Correct this. contentId might be vid + 'id': window.location.origin + Drupal.settings.basePath + 'node/' + instance.contentId, + 'contentId': instance.contentId } } From 05d9982070d3324de8d38ddf11b9d23de1fd0e77 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 2 Nov 2014 21:37:30 +0100 Subject: [PATCH 008/149] Add code to make it easy for others to register for xAPI events --- js/h5p.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 7cf7388..08f4f84 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -129,6 +129,7 @@ H5P.init = function () { } instance.on('xAPI', H5P.xAPIListener); + instance.on('xAPI', H5P.xAPIEmitter); // Resize everything when window is resized. $window.resize(function () { @@ -155,7 +156,6 @@ H5P.init = function () { }; H5P.xAPIListener = function(event) { - console.log(event); if ('verb' in event.statement) { if (event.statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { var score = event.statement.result.score.raw; @@ -166,6 +166,24 @@ H5P.xAPIListener = function(event) { } } +H5P.xAPIEmitter = function (event) { + if (event.statement !== undefined) { + for (var i = 0; i < H5P.xAPIListeners.length; i++) { + H5P.xAPIListeners[i](event.statement) + } + } +} + +H5P.xAPIListeners = []; + +H5P.onXAPI = function(listener) { + H5P.xAPIListeners.push(listener); +} + +H5P.onXAPI(function(statement) { + console.log(statement); +}); + /** * Enable full screen for the given h5p. * From bc5159e272b0e42857ca3cf220d8f226a0c41655 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 10 Nov 2014 11:31:23 +0100 Subject: [PATCH 009/149] Added event dispatcher. --- h5p.classes.php | 1 + js/h5p-event-dispatcher.js | 135 +++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 js/h5p-event-dispatcher.js diff --git a/h5p.classes.php b/h5p.classes.php index 827903a..d0fd2c8 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1534,6 +1534,7 @@ class H5PCore { public static $scripts = array( 'js/jquery.js', 'js/h5p.js', + 'js/h5p-event-dispatcher.js', ); public static $adminScripts = array( 'js/jquery.js', diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js new file mode 100644 index 0000000..147024d --- /dev/null +++ b/js/h5p-event-dispatcher.js @@ -0,0 +1,135 @@ +/** @namespace H5P */ +var H5P = H5P || {}; + +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 events and listeners for each event. + * @private + * @type {Object} + */ + var events = {}; + + /** + * Add new event listener. + * + * @public + * @throws {TypeError} listener must be a function + * @param {String} type Event type + * @param {Function} listener Event listener + */ + self.on = function (type, listener) { + if (!(listener instanceof Function)) { + throw TypeError('listener must be a function'); + } + + // Trigger event before adding to avoid recursion + self.trigger('newListener', type, listener); + + if (!events[type]) { + // First + events[type] = [listener]; + } + else { + // Append + events[type].push(listener); + } + }; + + /** + * 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 + */ + self.once = function (type, listener) { + if (!(listener instanceof Function)) { + throw TypeError('listener must be a function'); + } + + var once = function () { + self.off(type, once); + listener.apply(self, arguments); + }; + + self.on(type, once); + }; + + /** + * 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 + */ + self.off = function (type, listener) { + if (listener !== undefined && !(listener instanceof Function)) { + throw TypeError('listener must be a function'); + } + + if (events[type] === undefined) { + return; + } + + if (listener === undefined) { + // Remove all listeners + delete events[type]; + self.trigger('removeListener', type); + return; + } + + // Find specific listener + for (var i = 0; i < events[type].length; i++) { + if (events[type][i] === listener) { + events[type].unshift(i, 1); + self.trigger('removeListener', type, listener); + break; + } + } + + // Clean up empty arrays + if (!events[type].length) { + delete events[type]; + } + }; + + /** + * Dispatch event. + * + * @public + * @param {String} type Event type + * @param {...*} args + */ + self.trigger = function (type, args) { + if (events[type] === undefined) { + return; + } + + // Copy all arguments except the first + args = []; + var i; + for (i = 1; i < arguments.length; i++) { + args.push(arguments[i]); + } + + // Call all listeners + for (i = 0; i < events[type].length; i++) { + events[type][i].apply(self, args); + } + }; + } + + return EventDispatcher; +})(); From 2bba9a183b02f55af1569ed69e5c579fac312fab Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 10 Nov 2014 11:35:15 +0100 Subject: [PATCH 010/149] Bumped api version. --- h5p.classes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h5p.classes.php b/h5p.classes.php index d0fd2c8..0830735 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1526,7 +1526,7 @@ class H5PCore { public static $coreApi = array( 'majorVersion' => 1, - 'minorVersion' => 3 + 'minorVersion' => 4 ); public static $styles = array( 'styles/h5p.css', From cc455f2b2d3bd4f8513744040d36401508d13010 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 17 Nov 2014 11:08:43 +0100 Subject: [PATCH 011/149] Implement changes after testing against Scrom Cloud --- js/h5p.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 08f4f84..7a054b3 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -512,7 +512,8 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { this.statement.object = { // TODO: Correct this. contentId might be vid 'id': window.location.origin + Drupal.settings.basePath + 'node/' + instance.contentId, - 'contentId': instance.contentId + //'contentId': instance.contentId, + 'objectType': 'Activity' } } @@ -543,7 +544,12 @@ H5P.EventEnabled.prototype.createXAPIEventTemplate = function(verb, extra) { H5P.getActor = function() { - return H5PIntegration.getActor(); + var user = H5PIntegration.getUser(); + return { + 'name': user.name, + 'mbox': 'mailto:' . user.mail, + 'objectType': 'Agent' + } } H5P.XAPIEvent.allowedXAPIVerbs = [ From fb07f54e3517bff606934b0b056188afce24524d Mon Sep 17 00:00:00 2001 From: falcon Date: Thu, 27 Nov 2014 23:14:15 +0100 Subject: [PATCH 012/149] Make it possible to do the dependency transactions in an atomic way --- h5p-development.class.php | 2 ++ h5p.classes.php | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/h5p-development.class.php b/h5p-development.class.php index 75e9daf..7f33689 100644 --- a/h5p-development.class.php +++ b/h5p-development.class.php @@ -93,6 +93,7 @@ class H5PDevelopment { // TODO: Should we remove libraries without files? Not really needed, but must be cleaned up some time, right? // Go trough libraries and insert dependencies. Missing deps. will just be ignored and not available. (I guess?!) + $this->h5pF->lockDependencyStorage(); foreach ($this->libraries as $library) { $this->h5pF->deleteLibraryDependencies($library['libraryId']); // This isn't optimal, but without it we would get duplicate warnings. @@ -104,6 +105,7 @@ class H5PDevelopment { } } } + $this->h5pF->unlockDependencyStorage(); // TODO: Deps must be inserted into h5p_nodes_libraries as well... ? But only if they are used?! } diff --git a/h5p.classes.php b/h5p.classes.php index 36151b1..5940e06 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -408,6 +408,17 @@ interface H5PFrameworkInterface { */ public function deleteLibraryDependencies($libraryId); + /** + * Start an atomic operation against the dependency storage + */ + public function lockDependencyStorage(); + + /** + * Stops an atomic operation against the dependency storage + */ + public function unlockDependencyStorage(); + + /** * Delete a library from database and file system * From acb36845c06bd5d1c6f90854e3f66418ee5a051c Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 15 Dec 2014 11:50:48 +0100 Subject: [PATCH 013/149] Fixed wrong args. --- js/h5p-event-dispatcher.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index 147024d..b5907b2 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -112,14 +112,13 @@ H5P.EventDispatcher = (function () { * @param {String} type Event type * @param {...*} args */ - self.trigger = function (type, args) { + self.trigger = function (type) { if (events[type] === undefined) { return; } // Copy all arguments except the first - args = []; - var i; + var i, args = []; for (i = 1; i < arguments.length; i++) { args.push(arguments[i]); } From 1aef9e376d09f8e4d2e108dca79efcc7061bb3bd Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 15 Dec 2014 16:04:01 +0100 Subject: [PATCH 014/149] Removed ulgy border from full screen. --- styles/h5p.css | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/styles/h5p.css b/styles/h5p.css index 3ceb8e8..fff975b 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -4,9 +4,9 @@ @font-face { font-family: 'H5P'; src: url('../fonts/h5p.eot?ver=1.2.1'); - src: url('../fonts/h5p.eot?#iefix&ver=1.2.1') format('embedded-opentype'), + src: url('../fonts/h5p.eot?#iefix&ver=1.2.1') format('embedded-opentype'), url('data:application/font-woff;base64,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') format('woff'), - url('../fonts/h5p.ttf?ver=1.2.1') format('truetype'), + url('../fonts/h5p.ttf?ver=1.2.1') format('truetype'), url('../fonts/h5p.svg?ver=1.2.1#h5pregular') format('svg'); font-weight: normal; font-style: normal; @@ -36,6 +36,9 @@ html.h5p-iframe .h5p-content { width: 100%; height: 100%; } +.h5p-fullscreen .h5p-content, .h5p-semi-fullscreen .h5p-content { + border: 0; +} .h5p-container { position: relative; z-index: 1; @@ -264,7 +267,7 @@ div.h5p-fullscreen { } .h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar-track { background: #e0e0e0; -} +} .h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar-thumb { box-shadow: 0 0 10px #000 inset; border-radius: 4px; From 74fc8403661d5248b28642fa2be0e63b90edcfc9 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 17 Dec 2014 16:57:46 +0100 Subject: [PATCH 015/149] Added support for debugging events. --- js/h5p-event-dispatcher.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index b5907b2..fb7f5ef 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -105,6 +105,22 @@ H5P.EventDispatcher = (function () { } }; + /** + * Creates a copy of the arguments list. Skips the given number of arguments. + * + * @private + * @param {Array} args List of arguments + * @param {Number} skip Number of arguments to skip + * @param {Array} Copy og arguments list + */ + var getArgs = function (args, skip) { + var left = []; + for (var i = skip; i < args.length; i++) { + left.push(args[i]); + } + return left; + }; + /** * Dispatch event. * @@ -113,18 +129,20 @@ H5P.EventDispatcher = (function () { * @param {...*} args */ self.trigger = function (type) { + if (self.debug !== undefined) { + // Class has debug enabled. Log events. + console.log(self.debug + ' - Firing event "' + type + '", ' + (events[type] === undefined ? 0 : events[type].length) + ' listeners.', getArgs(arguments, 1)); + } + if (events[type] === undefined) { return; } // Copy all arguments except the first - var i, args = []; - for (i = 1; i < arguments.length; i++) { - args.push(arguments[i]); - } + var args = getArgs(arguments, 1); // Call all listeners - for (i = 0; i < events[type].length; i++) { + for (var i = 0; i < events[type].length; i++) { events[type][i].apply(self, args); } }; From ec4678bf0ba6c5cf70f5b27ffbc6bb2b8c0c89c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20J=C3=B8rgensen?= Date: Tue, 30 Dec 2014 10:54:26 +0100 Subject: [PATCH 016/149] Fixed issue #2397891 (drupal.org), and made jshint a little happier --- js/h5p-embed.js | 2 +- js/h5p.js | 44 ++++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/js/h5p-embed.js b/js/h5p-embed.js index b1b58c3..deeab96 100644 --- a/js/h5p-embed.js +++ b/js/h5p-embed.js @@ -90,7 +90,7 @@ var H5P = H5P || (function () { loadContent(contentId, h5ps[i]); contentId++; } - }; + } /** * Return integration object diff --git a/js/h5p.js b/js/h5p.js index 7a054b3..3afb4c4 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -164,21 +164,21 @@ H5P.xAPIListener = function(event) { H5P.setFinished(contentId, score, maxScore); } } -} +}; H5P.xAPIEmitter = function (event) { if (event.statement !== undefined) { for (var i = 0; i < H5P.xAPIListeners.length; i++) { - H5P.xAPIListeners[i](event.statement) + H5P.xAPIListeners[i](event.statement); } } -} +}; H5P.xAPIListeners = []; H5P.onXAPI = function(listener) { H5P.xAPIListeners.push(listener); -} +}; H5P.onXAPI(function(statement) { console.log(statement); @@ -449,7 +449,7 @@ H5P.EventEnabled.prototype.on = function(type, listener) { } this.listeners[type].push(listener); } -} +}; H5P.EventEnabled.prototype.off = function (type, listener) { if (this.listeners[type] !== undefined) { @@ -458,7 +458,7 @@ H5P.EventEnabled.prototype.off = function (type, listener) { listeners[type].splice(removeIndex, 1); } } -} +}; H5P.EventEnabled.prototype.trigger = function (type, event) { if (event === null) { @@ -469,16 +469,16 @@ H5P.EventEnabled.prototype.trigger = function (type, event) { this.listeners[type][i](event); } } -} +}; H5P.Event = function() { // We're going to add bubbling, propagation and other features here later -} +}; H5P.XAPIEvent = function() { H5P.Event.call(this); this.statement = {}; -} +}; H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype); H5P.XAPIEvent.prototype.constructor = H5P.XAPIEvent; @@ -490,8 +490,8 @@ H5P.XAPIEvent.prototype.setScoredResult = function(score, maxScore) { 'max': maxScore, 'raw': score } - } -} + }; +}; H5P.XAPIEvent.prototype.setVerb = function(verb) { if (H5P.jQuery.inArray(verb, H5P.XAPIEvent.allowedXAPIVerbs) !== -1) { @@ -500,13 +500,13 @@ H5P.XAPIEvent.prototype.setVerb = function(verb) { 'display': { 'en-US': verb } - } + }; } else { console.log('illegal verb'); } // Else: Fail silently... -} +}; H5P.XAPIEvent.prototype.setObject = function(instance) { this.statement.object = { @@ -514,17 +514,17 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { 'id': window.location.origin + Drupal.settings.basePath + 'node/' + instance.contentId, //'contentId': instance.contentId, 'objectType': 'Activity' - } -} + }; +}; H5P.XAPIEvent.prototype.setActor = function() { this.statement.actor = H5P.getActor(); -} +}; H5P.EventEnabled.prototype.triggerXAPI = function(verb, extra) { var event = this.createXAPIEventTemplate(verb, extra); this.trigger('xAPI', event); -} +}; H5P.EventEnabled.prototype.createXAPIEventTemplate = function(verb, extra) { var event = new H5P.XAPIEvent(); @@ -540,17 +540,17 @@ H5P.EventEnabled.prototype.createXAPIEventTemplate = function(verb, extra) { event.setObject(this); } return event; -} +}; H5P.getActor = function() { var user = H5PIntegration.getUser(); return { 'name': user.name, - 'mbox': 'mailto:' . user.mail, + 'mbox': 'mailto:' + user.mail, 'objectType': 'Agent' - } -} + }; +}; H5P.XAPIEvent.allowedXAPIVerbs = [ 'answered', @@ -578,7 +578,7 @@ H5P.XAPIEvent.allowedXAPIVerbs = [ 'suspended', 'terminated', 'voided' -] +]; /** * Used to print useful error messages. From e7b7cd2abe219de096f1a9b5fcb2704f427deebc Mon Sep 17 00:00:00 2001 From: git Date: Fri, 9 Jan 2015 09:59:29 +0100 Subject: [PATCH 017/149] Issue https://www.drupal.org/node/2402099 by drinkdecaf: H5P on Pantheon Environments --- h5p.classes.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/h5p.classes.php b/h5p.classes.php index 5940e06..619463a 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1275,7 +1275,8 @@ class H5PStorage { } $destination_path = $libraries_path . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE); H5PCore::deleteFileTree($destination_path); - rename($library['uploadDirectory'], $destination_path); + $this->h5pC->copyFileTree($library['uploadDirectory'], $destination_path); + H5PCore::deleteFileTree($library['uploadDirectory']); $library_saved = TRUE; } From ae8609dd86084aa78b7d8d0c8f0b57840a414597 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 9 Jan 2015 10:00:40 +0100 Subject: [PATCH 018/149] Replaced another rename function. --- h5p.classes.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 619463a..af50df5 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -412,13 +412,13 @@ interface H5PFrameworkInterface { * 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 * @@ -1327,7 +1327,8 @@ class H5PStorage { // Move the content folder $destination_path = $contents_path . DIRECTORY_SEPARATOR . $contentId; - @rename($current_path, $destination_path); + $this->h5pC->copyFileTree($current_path, $destination_path); + H5PCore::deleteFileTree($current_path); // Save the content library dependencies $this->h5pF->saveLibraryUsage($contentId, $librariesInUse); From 61eb998d48b1e927c0181a18984e17747f937a47 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 14 Jan 2015 17:27:32 +0100 Subject: [PATCH 019/149] Add xAPI todo list --- js/h5p.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/js/h5p.js b/js/h5p.js index 3afb4c4..5595b4f 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -155,6 +155,28 @@ H5P.init = function () { }); }; +/* + * TODO xAPI: + * 1. Create a xAPI.js file and move xAPI code there (public) + * 2. Be able to listen for events from both div and iframe embedded content + * via the same API (this is about adding communication between the iframe and + * it's parent and make sure that the parent distributes the events from the + * iframe) (public) + * 3. Create a separate Drupal module that is able to listen for events from + * both div and iframe embedded content and send them to analytics (custom for Zavango) + * 4. Move the event system code to a separate file (public) + * 5. Make sure the helper functions provides all the relevant data, example values + * and time spent (public) + * 6. Add documentation to the functions (public) + * 7. Add xAPI events to all the basic questiontype: + * 7.1 Multichoice + * 7.2 Fill in the blanks + * 7.3 Drag and drop + * 7.4 Drag the words + * 7.5 Mark the words + * 8. Add xAPI events to interactive video + */ + H5P.xAPIListener = function(event) { if ('verb' in event.statement) { if (event.statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { From 64dd059c03e653df40dfdba10b190a87f81d033f Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 14 Jan 2015 18:08:36 +0100 Subject: [PATCH 020/149] Add support for deprecated event system --- js/h5p.js | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 5595b4f..207aab4 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -118,7 +118,7 @@ H5P.init = function () { }; var resizeDelay; - instance.on('resize', function () { + H5P.deprecatedOn(instance, 'resize', function () { // Use a delay to make sure iframe is resized to the correct size. clearTimeout(resizeDelay); resizeDelay = setTimeout(function () { @@ -128,22 +128,22 @@ H5P.init = function () { H5P.instances.push(instance); } - instance.on('xAPI', H5P.xAPIListener); - instance.on('xAPI', H5P.xAPIEmitter); + H5P.deprecatedOn(instance, 'xAPI', H5P.xAPIListener); + H5P.deprecatedOn(instance, 'xAPI', H5P.xAPIEmitter); // Resize everything when window is resized. $window.resize(function () { if (window.parent.H5P.isFullscreen) { // Use timeout to avoid bug in certain browsers when exiting fullscreen. Some browser will trigger resize before the fullscreenchange event. - instance.trigger('resize'); + H5P.deprecatedTrigger(instance, 'resize'); } else { - instance.trigger('resize'); + H5P.deprecatedTrigger(instance, 'resize'); } }); // Resize content. - instance.trigger('resize'); + H5P.deprecatedTrigger(instance, 'resize'); }); // Insert H5Ps that should be in iframes. @@ -258,8 +258,8 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { */ var entered = function () { // Do not rely on window resize events. - instance.trigger('resize'); - instance.trigger('focus'); + H5P.deprecatedTrigger(instance, 'resize'); + H5P.deprecatedTrigger(instance, 'focus'); }; /** @@ -273,8 +273,8 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { $classes.removeClass(classes); // Do not rely on window resize events. - instance.trigger('resize'); - instance.trigger('focus'); + H5P.deprecatedTrigger(instance, 'resize'); + H5P.deprecatedTrigger(instance, 'focus'); if (exitCallback !== undefined) { exitCallback(); @@ -438,6 +438,10 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { } var instance = new constructor(library.params, contentId); + + if (instance.$ === undefined) { + instance.$ = H5P.jQuery(instance); + } // Make xAPI events bubble // if (parent !== null && parent.trigger !== undefined) { @@ -446,7 +450,7 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { // Automatically call resize on resize event if defined if (typeof instance.resize === 'function') { - instance.on('resize', instance.resize); + H5P.deprecatedOn(instance, 'resize', instance.resize); } if ($attachTo !== undefined) { @@ -454,7 +458,7 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { if (skipResize === undefined || !skipResize) { // Resize content. - instance.trigger('resize'); + H5P.deprecatedTrigger(instance, 'resize'); } } return instance; @@ -1305,3 +1309,21 @@ if (H5P.jQuery) { H5P.htmlSpecialChars = function(string) { return string.toString().replace(//g, '>').replace(/'/g, ''').replace(/"/g, '"'); }; + +H5P.deprecatedTrigger = function(instance, event) { + if (instance.trigger !== undefined) { + instance.trigger(event); + } + else if (instance.$ !== undefined) { + instance.$.trigger(event) + } +} + +H5P.deprecatedOn = function(instance, event, handler) { + if (instance.on !== undefined) { + instance.on(event, handler); + } + else if (instance.$ !== undefined) { + instance.$.on(event, handler) + } +} \ No newline at end of file From 3beaa8302a6fbe232388728d0823a1d26296f017 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 14 Jan 2015 18:13:23 +0100 Subject: [PATCH 021/149] Remove H5P special chars --- js/h5p.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 249d2bd..8881b86 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1303,13 +1303,6 @@ if (H5P.jQuery) { }); } - /** - * Mimics how php's htmlspecialchars works (the way we use it) - */ -H5P.htmlSpecialChars = function(string) { - return string.toString().replace(//g, '>').replace(/'/g, ''').replace(/"/g, '"'); -}; - H5P.deprecatedTrigger = function(instance, event) { if (instance.trigger !== undefined) { instance.trigger(event); From ead790ae1a6bef42bf31ab9cc4c93d116f3dfdfd Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 20 Jan 2015 14:48:22 +0100 Subject: [PATCH 022/149] Make sure fields in optional groups also becomes optional. --- h5p.classes.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 0164f1f..f558192 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -412,13 +412,13 @@ interface H5PFrameworkInterface { * 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 * @@ -2574,6 +2574,9 @@ class H5PContentValidator { $found = FALSE; foreach ($semantics->fields as $field) { if ($field->name == $key) { + if (isset($semantics->optional) && $semantics->optional) { + $field->optional = TRUE; + } $function = $this->typeMap[$field->type]; $found = TRUE; break; @@ -2598,11 +2601,13 @@ class H5PContentValidator { } } } - foreach ($semantics->fields as $field) { - if (!(isset($field->optional) && $field->optional)) { - // Check if field is in group. - if (! property_exists($group, $field->name)) { - $this->h5pF->setErrorMessage($this->h5pF->t('No value given for mandatory field ' . $field->name)); + if (!(isset($semantics->optional) && $semantics->optional)) { + foreach ($semantics->fields as $field) { + if (!(isset($field->optional) && $field->optional)) { + // Check if field is in group. + if (! property_exists($group, $field->name)) { + $this->h5pF->setErrorMessage($this->h5pF->t('No value given for mandatory field ' . $field->name)); + } } } } From b0e6bed5173b18e7939ac8c01b15a359747e4f61 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 27 Jan 2015 11:28:02 +0100 Subject: [PATCH 023/149] Update README.txt --- README.txt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.txt b/README.txt index f6ff53d..a6caa31 100644 --- a/README.txt +++ b/README.txt @@ -1,13 +1,12 @@ This folder contains the h5p general library. The files within this folder are not specific to any framework. Any interaction with LMS, CMS or other frameworks is done through interfaces. Plattforms needs to implement -the following interfaces in order for the h5p libraries to work: - - - TODO: Fill in here - -In addition frameworks need to do the following: +the H5PFrameworkInterface(in h5p.classes.php) and also do the following: - Provide a form for uploading h5p packages. - Place the uploaded h5p packages in a temporary directory + +++ -See existing implementations for details. For instance the Drupal h5p module located on drupal.org/project/h5p \ No newline at end of file +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 From ae6329595ba603179d62992c206458004d01606b Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Fri, 30 Jan 2015 10:59:40 +0100 Subject: [PATCH 024/149] Add ordering of libraries --- h5p.classes.php | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 5940e06..1503ad4 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1305,8 +1305,8 @@ class H5PStorage { // Find out which libraries are used by this package/content $librariesInUse = array(); - $this->h5pC->findLibraryDependencies($librariesInUse, $this->h5pC->mainJsonData); - + $nextWeight = $this->h5pC->findLibraryDependencies($librariesInUse, $this->h5pC->mainJsonData); + // Save content if ($content === NULL) { $content = array(); @@ -1836,9 +1836,12 @@ class H5PCore { * * @param array $librariesUsed Flat list of all dependencies. * @param array $library To find all dependencies for. - * @param bool $editor Used interally to force all preloaded sub dependencies of an editor dependecy to be editor dependencies. + * @param int $nextWeight An integer determining the order of the libraries + * when they are loaded + * @param bool $editor Used interally to force all preloaded sub dependencies + * of an editor dependecy to be editor dependencies. */ - public function findLibraryDependencies(&$dependencies, $library, $editor = FALSE) { + public function findLibraryDependencies(&$dependencies, $library, $nextWeight = 1, $editor = FALSE) { foreach (array('dynamic', 'preloaded', 'editor') as $type) { $property = $type . 'Dependencies'; if (!isset($library[$property])) { @@ -1862,7 +1865,8 @@ class H5PCore { 'library' => $dependencyLibrary, 'type' => $type ); - $this->findLibraryDependencies($dependencies, $dependencyLibrary, $type === 'editor'); + $nextWeight = $this->findLibraryDependencies($dependencies, $dependencyLibrary, $nextWeight, $type === 'editor'); + $dependencies[$dependencyKey]['weight'] = $nextWeight++; } else { // This site is missing a dependency! @@ -1870,6 +1874,7 @@ class H5PCore { } } } + return $nextWeight; } /** @@ -2220,8 +2225,7 @@ class H5PCore { class H5PContentValidator { public $h5pF; public $h5pC; - private $typeMap; - private $libraries, $dependencies; + private $typeMap, $libraries, $dependencies, $nextWeight; /** * Constructor for the H5PContentValidator @@ -2247,6 +2251,7 @@ class H5PContentValidator { 'select' => 'validateSelect', 'library' => 'validateLibrary', ); + $this->nextWeight = 1; // Keep track of the libraries we load to avoid loading it multiple times. $this->libraries = array(); @@ -2634,7 +2639,8 @@ class H5PContentValidator { 'type' => 'preloaded' ); - $this->h5pC->findLibraryDependencies($this->dependencies, $library); + $this->nextWeight = $this->h5pC->findLibraryDependencies($this->dependencies, $library, $this->nextWeight); + $this->dependencies[$depkey]['weight'] = $this->nextWeight++; } } else { From efdd7f9f7aa6a418c43a77835d6549017acbcc4f Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 3 Feb 2015 20:11:01 +0100 Subject: [PATCH 025/149] Moving x-api and Event handling code to separate files and refining it --- h5p.classes.php | 2 + js/event-dispatcher.js | 163 ++++++++++++++++++++++++++++++++++++++ js/h5p.js | 173 +---------------------------------------- js/x-api.js | 142 +++++++++++++++++++++++++++++++++ 4 files changed, 308 insertions(+), 172 deletions(-) create mode 100644 js/event-dispatcher.js create mode 100644 js/x-api.js diff --git a/h5p.classes.php b/h5p.classes.php index a6add6b..92647e7 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1547,6 +1547,8 @@ class H5PCore { public static $scripts = array( 'js/jquery.js', 'js/h5p.js', + 'js/event-dispatcher.js', + 'js/x-api.js', ); public static $adminScripts = array( 'js/jquery.js', diff --git a/js/event-dispatcher.js b/js/event-dispatcher.js new file mode 100644 index 0000000..194cec0 --- /dev/null +++ b/js/event-dispatcher.js @@ -0,0 +1,163 @@ +/** @namespace H5P */ +var H5P = H5P || {}; + +H5P.Event = function() { + // We're going to add bubbling, propagation and other features here later +}; + +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 + */ + self.on = function (type, listener) { + if (!(listener instanceof Function)) { + throw TypeError('listener must be a function'); + } + + // Trigger event before adding to avoid recursion + self.trigger('newListener', type, listener); + + if (!triggers[type]) { + // First + triggers[type] = [listener]; + } + else { + // Append + triggers[type].push(listener); + } + }; + + /** + * 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 + */ + self.once = function (type, listener) { + if (!(listener instanceof Function)) { + throw TypeError('listener must be a function'); + } + + var once = function () { + self.off(type, once); + listener.apply(self, arguments); + }; + + self.on(type, once); + }; + + /** + * 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 + */ + self.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) { + triggers[type].unshift(i, 1); + self.trigger('removeListener', type, listener); + break; + } + } + + // Clean up empty arrays + if (!triggers[type].length) { + delete triggers[type]; + } + }; + + /** + * Creates a copy of the arguments list. Skips the given number of arguments. + * + * @private + * @param {Array} args List of arguments + * @param {Number} skip Number of arguments to skip + * @param {Array} Copy og arguments list + */ + var getArgs = function (args, skip, event) { + var left = [event]; + for (var i = skip; i < args.length; i++) { + left.push(args[i]); + } + return left; + }; + + /** + * Dispatch event. + * + * @public + * @param {String} type Event type + * @param {...*} args + */ + self.trigger = function (type, event) { + console.log('triggering'); + if (self.debug !== undefined) { + // Class has debug enabled. Log events. + console.log(self.debug + ' - Firing event "' + type + '", ' + (triggers[type] === undefined ? 0 : triggers[type].length) + ' listeners.', getArgs(arguments, 1)); + } + + if (event === null) { + event = new H5P.Event(); + } + console.log(triggers); + if (triggers[type] === undefined) { + return; + } + + // Copy all arguments except the first two + var args = getArgs(arguments, 2, event); + + + // Call all listeners + console.log(triggers); + for (var i = 0; i < triggers[type].length; i++) { + triggers[type][i].apply(self, args); + } + }; + } + + return EventDispatcher; +})(); \ No newline at end of file diff --git a/js/h5p.js b/js/h5p.js index 8881b86..157755e 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -177,35 +177,6 @@ H5P.init = function () { * 8. Add xAPI events to interactive video */ -H5P.xAPIListener = function(event) { - if ('verb' in event.statement) { - if (event.statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { - var score = event.statement.result.score.raw; - var maxScore = event.statement.result.score.max; - var contentId = event.statement.object.contentId; - H5P.setFinished(contentId, score, maxScore); - } - } -}; - -H5P.xAPIEmitter = function (event) { - if (event.statement !== undefined) { - for (var i = 0; i < H5P.xAPIListeners.length; i++) { - H5P.xAPIListeners[i](event.statement); - } - } -}; - -H5P.xAPIListeners = []; - -H5P.onXAPI = function(listener) { - H5P.xAPIListeners.push(listener); -}; - -H5P.onXAPI(function(statement) { - console.log(statement); -}); - /** * Enable full screen for the given h5p. * @@ -464,148 +435,6 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { return instance; }; -H5P.EventEnabled = function() { - this.listeners = {}; -}; - -H5P.EventEnabled.prototype.on = function(type, listener) { - if (typeof listener === 'function') { - if (this.listeners[type] === undefined) { - this.listeners[type] = []; - } - this.listeners[type].push(listener); - } -}; - -H5P.EventEnabled.prototype.off = function (type, listener) { - if (this.listeners[type] !== undefined) { - var removeIndex = listeners[type].indexOf(listener); - if (removeIndex) { - listeners[type].splice(removeIndex, 1); - } - } -}; - -H5P.EventEnabled.prototype.trigger = function (type, event) { - if (event === null) { - event = new H5P.Event(); - } - if (this.listeners[type] !== undefined) { - for (var i = 0; i < this.listeners[type].length; i++) { - this.listeners[type][i](event); - } - } -}; - -H5P.Event = function() { - // We're going to add bubbling, propagation and other features here later -}; - -H5P.XAPIEvent = function() { - H5P.Event.call(this); - this.statement = {}; -}; - -H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype); -H5P.XAPIEvent.prototype.constructor = H5P.XAPIEvent; - -H5P.XAPIEvent.prototype.setScoredResult = function(score, maxScore) { - this.statement.result = { - 'score': { - 'min': 0, - 'max': maxScore, - 'raw': score - } - }; -}; - -H5P.XAPIEvent.prototype.setVerb = function(verb) { - if (H5P.jQuery.inArray(verb, H5P.XAPIEvent.allowedXAPIVerbs) !== -1) { - this.statement.verb = { - 'id': 'http://adlnet.gov/expapi/verbs/' + verb, - 'display': { - 'en-US': verb - } - }; - } - else { - console.log('illegal verb'); - } - // Else: Fail silently... -}; - -H5P.XAPIEvent.prototype.setObject = function(instance) { - this.statement.object = { - // TODO: Correct this. contentId might be vid - 'id': window.location.origin + Drupal.settings.basePath + 'node/' + instance.contentId, - //'contentId': instance.contentId, - 'objectType': 'Activity' - }; -}; - -H5P.XAPIEvent.prototype.setActor = function() { - this.statement.actor = H5P.getActor(); -}; - -H5P.EventEnabled.prototype.triggerXAPI = function(verb, extra) { - var event = this.createXAPIEventTemplate(verb, extra); - this.trigger('xAPI', event); -}; - -H5P.EventEnabled.prototype.createXAPIEventTemplate = function(verb, extra) { - var event = new H5P.XAPIEvent(); - - event.setActor(); - event.setVerb(verb); - if (extra !== undefined) { - for (var i in extra) { - event.statement[i] = extra[i]; - } - } - if (!('object' in event)) { - event.setObject(this); - } - return event; -}; - - -H5P.getActor = function() { - var user = H5PIntegration.getUser(); - return { - 'name': user.name, - 'mbox': 'mailto:' + user.mail, - 'objectType': 'Agent' - }; -}; - -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' -]; - /** * Used to print useful error messages. * @@ -1258,7 +1087,7 @@ H5P.setFinished = function (contentId, score, maxScore, time) { var toUnix = function (date) { return Math.round(date.getTime() / 1000); }; - + // Post the results // TODO: Should we use a variable with the complete path? H5P.jQuery.post(H5P.ajaxPath + 'setFinished', { diff --git a/js/x-api.js b/js/x-api.js new file mode 100644 index 0000000..b93437c --- /dev/null +++ b/js/x-api.js @@ -0,0 +1,142 @@ +var H5P = H5P || {}; + +H5P.xAPIListener = function(event) { + if ('verb' in event.statement) { + if (event.statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { + var score = event.statement.result.score.raw; + var maxScore = event.statement.result.score.max; + var contentId = event.statement.object.extensions['http://h5p.org/x-api/h5p-local-content-id']; + H5P.setFinished(contentId, score, maxScore); + } + } +}; + +H5P.xAPIEmitter = function (event) { + if (event.statement !== undefined) { + for (var i = 0; i < H5P.xAPIListeners.length; i++) { + H5P.xAPIListeners[i](event.statement); + } + } +}; + +H5P.xAPIListeners = []; + +H5P.onXAPI = function(listener) { + H5P.xAPIListeners.push(listener); +}; + +H5P.onXAPI(function(statement) { + console.log(statement); +}); + +H5P.XAPIEvent = function() { + H5P.Event.call(this); + this.statement = {}; +}; + +H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype); +H5P.XAPIEvent.prototype.constructor = H5P.XAPIEvent; + +H5P.XAPIEvent.prototype.setScoredResult = function(score, maxScore) { + this.statement.result = { + 'score': { + 'min': 0, + 'max': maxScore, + 'raw': score + } + }; +}; + +H5P.XAPIEvent.prototype.setVerb = function(verb) { + if (H5P.jQuery.inArray(verb, H5P.XAPIEvent.allowedXAPIVerbs) !== -1) { + this.statement.verb = { + 'id': 'http://adlnet.gov/expapi/verbs/' + verb, + 'display': { + 'en-US': verb + } + }; + } + else { + console.log('illegal verb'); + } + // Else: Fail silently... +}; + +H5P.XAPIEvent.prototype.setObject = function(instance) { + this.statement.object = { + // TODO: Correct this. contentId might be vid + 'id': window.location.origin + Drupal.settings.basePath + 'node/' + instance.contentId, + 'objectType': 'Activity', + 'extensions': { + 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId + } + }; +}; + +H5P.XAPIEvent.prototype.setActor = function() { + this.statement.actor = H5P.getActor(); +}; + +H5P.EventDispatcher.prototype.triggerXAPI = function(verb, extra) { + var event = this.createXAPIEventTemplate(verb, extra); + this.trigger('xAPI', event); +}; + +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.statement[i] = extra[i]; + } + } + if (!('object' in event)) { + event.setObject(this); + } + return event; +}; + +H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) { + var event = this.createXAPIEventTemplate('completed'); + event.setScoredResult(score, maxScore); + this.trigger('xAPI', event); +} + +H5P.getActor = function() { + var user = H5PIntegration.getUser(); + return { + 'name': user.name, + 'mbox': 'mailto:' + user.mail, + 'objectType': 'Agent' + }; +}; + +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' +]; \ No newline at end of file From fc4f791e6eb0a845f3ba06121039510c28119a7c Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 3 Feb 2015 20:15:15 +0100 Subject: [PATCH 026/149] Bump core api version --- h5p.classes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h5p.classes.php b/h5p.classes.php index 92647e7..687da5d 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1539,7 +1539,7 @@ class H5PCore { public static $coreApi = array( 'majorVersion' => 1, - 'minorVersion' => 3 + 'minorVersion' => 4 ); public static $styles = array( 'styles/h5p.css', From c8a24658276327d29ca232aefb07fb56f1abe03a Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 4 Feb 2015 18:43:06 +0100 Subject: [PATCH 027/149] Add documentation and change function names for deprecated functions --- js/h5p.js | 71 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 157755e..5df3f2b 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -118,7 +118,7 @@ H5P.init = function () { }; var resizeDelay; - H5P.deprecatedOn(instance, 'resize', function () { + H5P.on(instance, 'resize', function () { // Use a delay to make sure iframe is resized to the correct size. clearTimeout(resizeDelay); resizeDelay = setTimeout(function () { @@ -128,22 +128,22 @@ H5P.init = function () { H5P.instances.push(instance); } - H5P.deprecatedOn(instance, 'xAPI', H5P.xAPIListener); - H5P.deprecatedOn(instance, 'xAPI', H5P.xAPIEmitter); + H5P.on(instance, 'xAPI', H5P.xAPIListener); + H5P.on(instance, 'xAPI', H5P.xAPIEmitter); // Resize everything when window is resized. $window.resize(function () { if (window.parent.H5P.isFullscreen) { // Use timeout to avoid bug in certain browsers when exiting fullscreen. Some browser will trigger resize before the fullscreenchange event. - H5P.deprecatedTrigger(instance, 'resize'); + H5P.trigger(instance, 'resize'); } else { - H5P.deprecatedTrigger(instance, 'resize'); + H5P.trigger(instance, 'resize'); } }); // Resize content. - H5P.deprecatedTrigger(instance, 'resize'); + H5P.trigger(instance, 'resize'); }); // Insert H5Ps that should be in iframes. @@ -229,8 +229,8 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { */ var entered = function () { // Do not rely on window resize events. - H5P.deprecatedTrigger(instance, 'resize'); - H5P.deprecatedTrigger(instance, 'focus'); + H5P.trigger(instance, 'resize'); + H5P.trigger(instance, 'focus'); }; /** @@ -244,8 +244,8 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { $classes.removeClass(classes); // Do not rely on window resize events. - H5P.deprecatedTrigger(instance, 'resize'); - H5P.deprecatedTrigger(instance, 'focus'); + H5P.trigger(instance, 'resize'); + H5P.trigger(instance, 'focus'); if (exitCallback !== undefined) { exitCallback(); @@ -421,7 +421,7 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { // Automatically call resize on resize event if defined if (typeof instance.resize === 'function') { - H5P.deprecatedOn(instance, 'resize', instance.resize); + H5P.on(instance, 'resize', instance.resize); } if ($attachTo !== undefined) { @@ -429,7 +429,7 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { if (skipResize === undefined || !skipResize) { // Resize content. - H5P.deprecatedTrigger(instance, 'resize'); + H5P.trigger(instance, 'resize'); } } return instance; @@ -1132,20 +1132,47 @@ if (H5P.jQuery) { }); } -H5P.deprecatedTrigger = function(instance, event) { +/** + * Trigger an event on an instance + * + * Helper function that triggers an event if the instance supports event handling + * + * @param {function} instance + * An H5P instance + * @param {string} eventType + * The event type + */ +H5P.trigger = function(instance, eventType) { + // Try new event system first if (instance.trigger !== undefined) { - instance.trigger(event); + instance.trigger(eventType); } - else if (instance.$ !== undefined) { - instance.$.trigger(event) + // Try deprecated event system + else if (instance.$ !== undefined && instance.$.trigger !== undefined) { + instance.$.trigger(eventType) } -} +}; -H5P.deprecatedOn = function(instance, event, handler) { +/** + * Register an event handler + * + * Helper function that registers an event handler for an event type if + * the instance supports event handling + * + * @param {function} instance + * An h5p instance + * @param {string} eventType + * The event type + * @param {function} handler + * Callback that gets triggered for events of the specified type + */ +H5P.on = function(instance, eventType, handler) { + // Try new event system first if (instance.on !== undefined) { - instance.on(event, handler); + instance.on(eventType, handler); } - else if (instance.$ !== undefined) { - instance.$.on(event, handler) + // Try deprecated event system + else if (instance.$ !== undefined && instance.$.on !== undefined) { + instance.$.on(eventType, handler) } -} +}; \ No newline at end of file From 9a0da8614b86c76f07a464195413ad8586af3e5e Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Thu, 5 Feb 2015 10:46:55 +0100 Subject: [PATCH 028/149] Add helper functions --- js/event-dispatcher.js | 3 -- js/x-api.js | 77 ++++++++++++++++++++++++++---------------- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/js/event-dispatcher.js b/js/event-dispatcher.js index 194cec0..70a21cd 100644 --- a/js/event-dispatcher.js +++ b/js/event-dispatcher.js @@ -133,7 +133,6 @@ H5P.EventDispatcher = (function () { * @param {...*} args */ self.trigger = function (type, event) { - console.log('triggering'); if (self.debug !== undefined) { // Class has debug enabled. Log events. console.log(self.debug + ' - Firing event "' + type + '", ' + (triggers[type] === undefined ? 0 : triggers[type].length) + ' listeners.', getArgs(arguments, 1)); @@ -142,7 +141,6 @@ H5P.EventDispatcher = (function () { if (event === null) { event = new H5P.Event(); } - console.log(triggers); if (triggers[type] === undefined) { return; } @@ -152,7 +150,6 @@ H5P.EventDispatcher = (function () { // Call all listeners - console.log(triggers); for (var i = 0; i < triggers[type].length; i++) { triggers[type][i].apply(self, args); } diff --git a/js/x-api.js b/js/x-api.js index b93437c..a7ecb32 100644 --- a/js/x-api.js +++ b/js/x-api.js @@ -77,6 +77,53 @@ H5P.XAPIEvent.prototype.setActor = function() { this.statement.actor = H5P.getActor(); }; +H5P.XAPIEvent.prototype.getMaxScore = function() { + return this.getVerifiedStatementValue(['result', 'score', 'max']); +}; + +H5P.XAPIEvent.prototype.getScore = function() { + return this.getVerifiedStatementValue(['result', 'score', 'raw']); +}; + +H5P.XAPIEvent.prototype.getVerifiedStatementValue = function(keys) { + var val = this.statement; + for (var i in keys) { + if (val[keys[i]] === undefined) { + return null; + } + val = val[keys[i]]; + } + return val; +} + +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' +]; + H5P.EventDispatcher.prototype.triggerXAPI = function(verb, extra) { var event = this.createXAPIEventTemplate(verb, extra); this.trigger('xAPI', event); @@ -111,32 +158,4 @@ H5P.getActor = function() { 'mbox': 'mailto:' + user.mail, 'objectType': 'Agent' }; -}; - -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' -]; \ No newline at end of file +}; \ No newline at end of file From 18a3ed4a8f4c63d6cac45c055836190d833b6c32 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 9 Feb 2015 10:22:14 +0100 Subject: [PATCH 029/149] Simplified integration code to avoid to much duplication. Added resizing script for new embed code. Added iframe communicator when embedding. --- js/h5p-api.js | 0 js/h5p-resizer.js | 155 ++++++++++++++++++++++++++++ js/h5p.js | 252 +++++++++++++++++++++++++++++++++------------- 3 files changed, 337 insertions(+), 70 deletions(-) create mode 100644 js/h5p-api.js create mode 100644 js/h5p-resizer.js diff --git a/js/h5p-api.js b/js/h5p-api.js new file mode 100644 index 0000000..e69de29 diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js new file mode 100644 index 0000000..86cb280 --- /dev/null +++ b/js/h5p-resizer.js @@ -0,0 +1,155 @@ +// H5P iframe Resizer +(function () { + if (!window.postMessage || !window.addEventListener) { + return; // Not supported + } + + // 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 = {}; + + // Retain parent size to avoid jumping/scrolling + responseData.parentHeight = iframe.parentElement.style.height; + //iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px'; + + // Reset iframe height, in case content has shrinked. + iframe.style.height = '1px'; + + respond('resizePrepared', responseData); + }; + + /** + * 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'; + + // Free parent + //iframe.parentElement.style.height = data.parentHeight; + }; + + /** + * Keyup event handler. Exits full screen on escape. + * + * @param {Event} event + */ + var escape = function (event) { + if (event.keyCode === 27) { + exitFullScreen(); + } + }; + + /** + * Enter semi full screen. + * Expands the iframe so that it covers the whole page. + * + * @private + * @param {Object} iframe Element + * @param {Object} data Payload + * @param {Function} respond Send a response to the iframe + */ + actionHandlers.fullScreen = function (iframe, data, respond) { + iframe.style.position = 'fixed'; + iframe.style.top = iframe.style.left = 0; + iframe.style.zIndex = 101; + iframe.style.width = iframe.style.height = '100%'; + document.body.addEventListener('keyup', escape, false); + respond('fullScreen'); + }; + + /** + * Exit semi full screen. + * + * @private + * @param {Object} iframe Element + * @param {Object} data Payload + * @param {Function} respond Send a response to the iframe + */ + actionHandlers.exitFullScreen = function (iframe, data, respond) { + iframe.style.position = ''; + iframe.style.top = iframe.style.left = ''; + iframe.style.zIndex = ''; + iframe.style.width = '100%'; + iframe.style.height = ''; + document.body.removeEventListener('keyup', escape, false); + respond('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 (action, data) { + if (data === undefined) { + data = {}; + } + data.action = action; + data.context = 'h5p'; + event.source.postMessage(data, event.origin); + }); + } + }, false); +})(); diff --git a/js/h5p.js b/js/h5p.js index d53f8e2..8ff166d 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -31,6 +31,8 @@ else if (document.documentElement.msRequestFullscreen) { // Keep track of when the H5Ps where started H5P.opened = {}; +H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? (document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled) : true; + /** * Initialize H5P content. * Scans for ".h5p-content" in the document and initializes H5P instances where found. @@ -39,15 +41,12 @@ H5P.init = function () { // Useful jQuery object. H5P.$body = H5P.jQuery(document.body); - // Prepare internal resizer for content. - var $window = H5P.jQuery(window.parent); - // H5Ps added in normal DIV. var $containers = H5P.jQuery(".h5p-content").each(function () { var $element = H5P.jQuery(this); var $container = H5P.jQuery('
    ').appendTo($element); var contentId = $element.data('content-id'); - var contentData = H5PIntegration.getContentData(contentId); + var contentData = H5P.contentDatas['cid-' + contentId]; if (contentData === undefined) { return H5P.error('No data for content id ' + contentId + '. Perhaps the library is gone?'); } @@ -85,7 +84,7 @@ H5P.init = function () { H5P.openEmbedDialog($actions, contentData.embedCode); }); } - if (H5PIntegration.showH5PIconInActionBar()) { + if (contentData.showH5PIconInActionBar) { H5P.jQuery('
  • ').appendTo($actions); } $actions.insertAfter($container); @@ -100,48 +99,98 @@ H5P.init = function () { } }); - if (H5P.isFramed) { - // Make it possible to resize the iframe when the content changes size. This way we get no scrollbars. - var iframe = window.parent.document.getElementById('h5p-iframe-' + contentId); - var resizeIframe = function () { - if (window.parent.H5P.isFullscreen) { - return; // Skip if full screen. - } + if (H5P.isFramed) + var resizeDelay;{ + if (H5P.externalEmbed === false) { + // Internal embed + // Make it possible to resize the iframe when the content changes size. This way we get no scrollbars. + var iframe = window.parent.document.getElementById('h5p-iframe-' + contentId); + var resizeIframe = function () { + if (window.parent.H5P.isFullscreen) { + return; // Skip if full screen. + } - // Retain parent size to avoid jumping/scrolling - var parentHeight = iframe.parentElement.style.height; - iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px'; + // Retain parent size to avoid jumping/scrolling + var parentHeight = iframe.parentElement.style.height; + iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px'; - // Reset iframe height, in case content has shrinked. - iframe.style.height = '1px'; + // Reset iframe height, in case content has shrinked. + iframe.style.height = '1px'; - // Resize iframe so all content is visible. - iframe.style.height = (iframe.contentDocument.body.scrollHeight) + 'px'; + // Resize iframe so all content is visible. + iframe.style.height = (iframe.contentDocument.body.scrollHeight) + 'px'; - // Free parent - iframe.parentElement.style.height = parentHeight; - }; + // Free parent + iframe.parentElement.style.height = parentHeight; + }; - var resizeDelay; - instance.$.on('resize', function () { - // Use a delay to make sure iframe is resized to the correct size. - clearTimeout(resizeDelay); - resizeDelay = setTimeout(function () { - resizeIframe(); - }, 1); - }); + instance.$.on('resize', function () { + // Use a delay to make sure iframe is resized to the correct size. + clearTimeout(resizeDelay); + resizeDelay = setTimeout(function () { + resizeIframe(); + }, 1); + }); + } + else if (H5P.communicator) { + // External embed + var parentIsFriendly = false; + + // Handle hello message from our parent window + H5P.communicator.on('hello', function () { + // Initial setup/handshake is done + parentIsFriendly = true; + + // Hide scrollbars for correct size + document.body.style.overflow = 'hidden'; + + H5P.communicator.send('prepareResize'); + }); + + // When resize has been prepared tell parent window to resize + H5P.communicator.on('resizePrepared', function (data) { + H5P.communicator.send('resize', { + height: document.body.scrollHeight, + parentHeight: data.parentHeight + }); + }); + + H5P.communicator.on('resize', function () { + instance.$.trigger('resize'); + }); + + instance.$.on('resize', function () { + if (H5P.isFullscreen) { + return; // Skip iframe resize + } + + // Use a delay to make sure iframe is resized to the correct size. + clearTimeout(resizeDelay); + resizeDelay = setTimeout(function () { + // Only resize if the iframe can be resized + if (parentIsFriendly) { + H5P.communicator.send('prepareResize'); + } + else { + H5P.communicator.send('hello'); + } + }, 0); + }); + } } - // Resize everything when window is resized. - $window.resize(function () { - if (window.parent.H5P.isFullscreen) { - // Use timeout to avoid bug in certain browsers when exiting fullscreen. Some browser will trigger resize before the fullscreenchange event. + if (!H5P.isFramed || H5P.externalEmbed === false) { + // Resize everything when window is resized. + H5P.jQuery(window.top).resize(function () { + if (window.parent.H5P.isFullscreen) { + // Use timeout to avoid bug in certain browsers when exiting fullscreen. Some browser will trigger resize before the fullscreenchange event. + instance.$.trigger('resize'); + } + else { instance.$.trigger('resize'); - } - else { - instance.$.trigger('resize'); - } - }); + } + }); + } // Resize content. instance.$.trigger('resize'); @@ -151,11 +200,69 @@ H5P.init = function () { H5P.jQuery("iframe.h5p-iframe").each(function () { var contentId = H5P.jQuery(this).data('content-id'); this.contentDocument.open(); - this.contentDocument.write('' + H5PIntegration.getHeadTags(contentId) + '
    '); + this.contentDocument.write('' + H5P.getHeadTags(contentId) + '
    '); this.contentDocument.close(); + this.contentWindow.H5P = { + externalEmbed: false + }; }); }; +H5P.communicator = (function () { + /** + * @class + */ + function Communicator() { + var self = this; + + // Maps actions to functions + var actionHandlers = {}; + + // Register message listener + window.addEventListener('message', function receiveMessage(event) { + if (window.parent !== event.source || event.data.context !== 'h5p') { + return; // Only handle messages from parent and in the correct context + } + + if (actionHandlers[event.data.action] !== undefined) { + actionHandlers[event.data.action](event.data); + } + } , false); + + + /** + * Register action listener. + * + * @public + * @param {String} action What you are waiting for + * @param {Function} handler What you want done + */ + self.on = function (action, handler) { + actionHandlers[action] = handler; + }; + + /** + * Send a message to the all mighty father. + * + * @public + * @param {String} action + * @param {Object} [data] payload + */ + self.send = function (action, data) { + if (data === undefined) { + data = {}; + } + data.context = 'h5p'; + data.action = action; + + // Parent origin can be anything + window.parent.postMessage(data, '*'); + }; + } + + return (window.postMessage && window.addEventListener ? new Communicator() : undefined); +})(); + /** * Enable full screen for the given h5p. * @@ -166,9 +273,19 @@ H5P.init = function () { * @returns {undefined} */ H5P.fullScreen = function ($element, instance, exitCallback, body) { - if (H5P.isFramed) { + if (H5P.exitFullScreen !== undefined) { + return; // Cannot enter new fullscreen until previous is over + } + + if (H5P.isFramed && H5P.externalEmbed === false) { // Trigger resize on wrapper in parent window. - window.parent.H5P.fullScreen($element, instance, exitCallback, H5P.$body.get()); + window.top.H5P.fullScreen($element, instance, exitCallback, H5P.$body.get()); + H5P.isFullscreen = true; + H5P.exitFullScreen = function () { + window.top.H5P.exitFullScreen(); + H5P.isFullscreen = false; + H5P.exitFullScreen = undefined; + }; return; } @@ -226,6 +343,7 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { instance.$.trigger('resize'); instance.$.trigger('focus'); + H5P.exitFullScreen = undefined; if (exitCallback !== undefined) { exitCallback(); } @@ -235,6 +353,10 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { if (H5P.fullScreenBrowserPrefix === undefined) { // Create semi fullscreen. + if (H5P.isFramed) { + return; // TODO: Ask parent for iframe + } + before('h5p-semi-fullscreen'); var $disable = H5P.jQuery('
    ').appendTo($container.find('.h5p-content-controls')); var keyup, disableSemiFullscreen = function () { @@ -277,6 +399,19 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { var params = (H5P.fullScreenBrowserPrefix === 'webkit' && H5P.safariBrowser === 0 ? Element.ALLOW_KEYBOARD_INPUT : undefined); $element[0][method](params); } + + // Allows everone to exit + H5P.exitFullScreen = function () { + if (H5P.fullScreenBrowserPrefix === '') { + document.exitFullscreen(); + } + else if (H5P.fullScreenBrowserPrefix === 'moz') { + document.mozCancelFullScreen(); + } + else { + document[H5P.fullScreenBrowserPrefix + 'ExitFullscreen'](); + } + }; } }; @@ -300,7 +435,7 @@ H5P.getPath = function (path, contentId) { } if (contentId !== undefined) { - prefix = H5PIntegration.getContentPath(contentId); + prefix = H5P.url + '/content/' + contentId; } else if (window.H5PEditor !== undefined) { prefix = H5PEditor.filesPath; @@ -316,18 +451,6 @@ H5P.getPath = function (path, contentId) { return prefix + '/' + path; }; -/** - * THIS FUNCTION IS DEPRECATED, USE getPath INSTEAD - * - * Find the path to the content files folder based on the id of the content - * - * @param contentId - * Id of the content requesting a path - */ -H5P.getContentPath = function (contentId) { - return H5PIntegration.getContentPath(contentId); -}; - /** * Get library class constructor from H5P by classname. * Note that this class will only work for resolve "H5P.NameWithoutDot". @@ -428,15 +551,15 @@ H5P.t = function (key, vars, ns) { ns = 'H5P'; } - if (H5PIntegration.i18n[ns] === undefined) { + if (H5P.l10n[ns] === undefined) { return '[Missing translation namespace "' + ns + '"]'; } - if (H5PIntegration.i18n[ns][key] === undefined) { + if (H5P.l10n[ns][key] === undefined) { return '[Missing translation "' + key + '" in "' + ns + '"]'; } - var translation = H5PIntegration.i18n[ns][key]; + var translation = H5P.l10n[ns][key]; if (vars !== undefined) { // Replace placeholder with variables. @@ -946,7 +1069,7 @@ H5P.libraryFromString = function (library) { * @returns {String} The full path to the library. */ H5P.getLibraryPath = function (library) { - return H5PIntegration.getLibraryPath(library); + return H5P.url + '/libraries/' + library; }; /** @@ -1088,14 +1211,3 @@ if (String.prototype.trim === undefined) { return H5P.trim(this); }; } - -// Finally, we want to run init when document is ready. -// TODO: Move to integration. Systems like Moodle using YUI cannot get its translations set before this starts! -if (H5P.jQuery) { - H5P.jQuery(document).ready(function () { - if (!H5P.initialized) { - H5P.initialized = true; - H5P.init(); - } - }); -} From 11d9cb2f35d44e7b759dc40b44bdb6ce41c9b37e Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 9 Feb 2015 11:47:33 +0100 Subject: [PATCH 030/149] Trigger events. --- js/h5p.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/h5p.js b/js/h5p.js index 8ff166d..9567fee 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -327,6 +327,7 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { // Do not rely on window resize events. instance.$.trigger('resize'); instance.$.trigger('focus'); + instance.$.trigger('enterFullScreen'); }; /** @@ -347,6 +348,8 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { if (exitCallback !== undefined) { exitCallback(); } + + instance.$.trigger('exitFullScreen'); }; H5P.isFullscreen = true; From a0c5fe0e1808ff97e96b5e553b8999502419f04b Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 9 Feb 2015 11:51:39 +0100 Subject: [PATCH 031/149] Comment out semi full screen. --- js/h5p-resizer.js | 70 +++++++++++++++++++++++------------------------ js/h5p.js | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js index 86cb280..54ec5d8 100644 --- a/js/h5p-resizer.js +++ b/js/h5p-resizer.js @@ -84,41 +84,41 @@ } }; - /** - * Enter semi full screen. - * Expands the iframe so that it covers the whole page. - * - * @private - * @param {Object} iframe Element - * @param {Object} data Payload - * @param {Function} respond Send a response to the iframe - */ - actionHandlers.fullScreen = function (iframe, data, respond) { - iframe.style.position = 'fixed'; - iframe.style.top = iframe.style.left = 0; - iframe.style.zIndex = 101; - iframe.style.width = iframe.style.height = '100%'; - document.body.addEventListener('keyup', escape, false); - respond('fullScreen'); - }; - - /** - * Exit semi full screen. - * - * @private - * @param {Object} iframe Element - * @param {Object} data Payload - * @param {Function} respond Send a response to the iframe - */ - actionHandlers.exitFullScreen = function (iframe, data, respond) { - iframe.style.position = ''; - iframe.style.top = iframe.style.left = ''; - iframe.style.zIndex = ''; - iframe.style.width = '100%'; - iframe.style.height = ''; - document.body.removeEventListener('keyup', escape, false); - respond('exitFullScreen'); - }; + // /** + // * Enter semi full screen. + // * Expands the iframe so that it covers the whole page. + // * + // * @private + // * @param {Object} iframe Element + // * @param {Object} data Payload + // * @param {Function} respond Send a response to the iframe + // */ + // actionHandlers.fullScreen = function (iframe, data, respond) { + // iframe.style.position = 'fixed'; + // iframe.style.top = iframe.style.left = 0; + // iframe.style.zIndex = 101; + // iframe.style.width = iframe.style.height = '100%'; + // document.body.addEventListener('keyup', escape, false); + // respond('fullScreen'); + // }; + // + // /** + // * Exit semi full screen. + // * + // * @private + // * @param {Object} iframe Element + // * @param {Object} data Payload + // * @param {Function} respond Send a response to the iframe + // */ + // actionHandlers.exitFullScreen = function (iframe, data, respond) { + // iframe.style.position = ''; + // iframe.style.top = iframe.style.left = ''; + // iframe.style.zIndex = ''; + // iframe.style.width = '100%'; + // iframe.style.height = ''; + // document.body.removeEventListener('keyup', escape, false); + // respond('exitFullScreen'); + // }; // Listen for messages from iframes diff --git a/js/h5p.js b/js/h5p.js index 9567fee..95a7728 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -357,7 +357,7 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { // Create semi fullscreen. if (H5P.isFramed) { - return; // TODO: Ask parent for iframe + return; // TODO: Should we support semi-fullscreen for IE9 & 10 ? } before('h5p-semi-fullscreen'); From c764d13b3254b172a11e032a6fe8fd4d181503cd Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 10 Feb 2015 16:29:16 +0100 Subject: [PATCH 032/149] Moved display options helpers into core. --- h5p.classes.php | 68 +++++++++++++++++++++++++++++++++++++++++++++++-- js/disable.js | 19 ++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 js/disable.js diff --git a/h5p.classes.php b/h5p.classes.php index ae7d2f5..2dc1bb7 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1307,7 +1307,7 @@ class H5PStorage { // Find out which libraries are used by this package/content $librariesInUse = array(); $nextWeight = $this->h5pC->findLibraryDependencies($librariesInUse, $this->h5pC->mainJsonData); - + // Save content if ($content === NULL) { $content = array(); @@ -1562,6 +1562,22 @@ class H5PCore { private $exportEnabled; + // Disable flags + const DISABLE_NONE = 0; + const DISABLE_FRAME = 1; + const DISABLE_DOWNLOAD = 2; + const DISABLE_EMBED = 4; + const DISABLE_COPYRIGHT = 8; + const DISABLE_ABOUT = 16; + + // Map flags to string + public static $disable = array( + self::DISABLE_FRAME => 'frame', + self::DISABLE_DOWNLOAD => 'download', + self::DISABLE_EMBED => 'embed', + self::DISABLE_COPYRIGHT => 'copyright' + ); + /** * Constructor for the H5PCore * @@ -1841,7 +1857,7 @@ class H5PCore { * @param array $library To find all dependencies for. * @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 + * @param bool $editor Used interally to force all preloaded sub dependencies * of an editor dependecy to be editor dependencies. */ public function findLibraryDependencies(&$dependencies, $library, $nextWeight = 1, $editor = FALSE) { @@ -2220,6 +2236,54 @@ class H5PCore { } } } + + 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 (!$sources['frame']) { + $disable |= H5PCore::DISABLE_FRAME; + } + if (!$sources['download']) { + $disable |= H5PCore::DISABLE_DOWNLOAD; + } + if (!$sources['copyright']) { + $disable |= H5PCore::DISABLE_COPYRIGHT; + } + if (!$sources['embed']) { + $disable |= H5PCore::DISABLE_EMBED; + } + return $disable; + } } /** diff --git a/js/disable.js b/js/disable.js new file mode 100644 index 0000000..83d740c --- /dev/null +++ b/js/disable.js @@ -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); From fb77a4d20dc9b84cffa6f16877babc4a1cb74daa Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 10 Feb 2015 16:40:23 +0100 Subject: [PATCH 033/149] bugfix. --- h5p.classes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h5p.classes.php b/h5p.classes.php index 2dc1bb7..85d0616 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2232,7 +2232,7 @@ class H5PCore { } } if($platformInfo['uuid'] === '' && isset($json->uuid)) { - $this->h5pF->setOption('h5p_site_uuid', $json->uuid); + $this->h5pF->setOption('site_uuid', $json->uuid); } } } From 42fbb6ab1f0dec5b83273d1b0e6be079769fd2ed Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 11 Feb 2015 14:40:01 +0100 Subject: [PATCH 034/149] Reverse direction of dependency loading. The libraries used last in the parameters are now loaded first, e.g. summary is now loaded before iv! --- h5p.classes.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index ae7d2f5..f0ca139 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1307,7 +1307,7 @@ class H5PStorage { // Find out which libraries are used by this package/content $librariesInUse = array(); $nextWeight = $this->h5pC->findLibraryDependencies($librariesInUse, $this->h5pC->mainJsonData); - + // Save content if ($content === NULL) { $content = array(); @@ -1841,7 +1841,7 @@ class H5PCore { * @param array $library To find all dependencies for. * @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 + * @param bool $editor Used interally to force all preloaded sub dependencies * of an editor dependecy to be editor dependencies. */ public function findLibraryDependencies(&$dependencies, $library, $nextWeight = 1, $editor = FALSE) { @@ -2633,18 +2633,6 @@ class H5PContentValidator { $library = $this->h5pC->loadLibrary($libspec['machineName'], $libspec['majorVersion'], $libspec['minorVersion']); $library['semantics'] = $this->h5pC->loadLibrarySemantics($libspec['machineName'], $libspec['majorVersion'], $libspec['minorVersion']); $this->libraries[$value->library] = $library; - - // Find all dependencies for this library - $depkey = 'preloaded-' . $libspec['machineName']; - if (!isset($this->dependencies[$depkey])) { - $this->dependencies[$depkey] = array( - 'library' => $library, - 'type' => 'preloaded' - ); - - $this->nextWeight = $this->h5pC->findLibraryDependencies($this->dependencies, $library, $this->nextWeight); - $this->dependencies[$depkey]['weight'] = $this->nextWeight++; - } } else { $library = $this->libraries[$value->library]; @@ -2659,6 +2647,18 @@ class H5PContentValidator { $validkeys = array_merge($validkeys, $semantics->extraAttributes); } $this->filterParams($value, $validkeys); + + // 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++; + } } /** From 079f4992b4d3815daa15639aaa801ef601683fcc Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 11 Feb 2015 15:56:35 +0100 Subject: [PATCH 035/149] bugfixes --- js/event-dispatcher.js | 55 ++++++++++++++---------------------------- js/x-api.js | 35 +++++++++++++-------------- 2 files changed, 35 insertions(+), 55 deletions(-) diff --git a/js/event-dispatcher.js b/js/event-dispatcher.js index 70a21cd..e9152b0 100644 --- a/js/event-dispatcher.js +++ b/js/event-dispatcher.js @@ -1,8 +1,9 @@ /** @namespace H5P */ var H5P = H5P || {}; -H5P.Event = function() { - // We're going to add bubbling, propagation and other features here later +H5P.Event = function(type, data) { + this.type = type; + this.data = data; }; H5P.EventDispatcher = (function () { @@ -109,49 +110,29 @@ H5P.EventDispatcher = (function () { } }; - /** - * Creates a copy of the arguments list. Skips the given number of arguments. - * - * @private - * @param {Array} args List of arguments - * @param {Number} skip Number of arguments to skip - * @param {Array} Copy og arguments list - */ - var getArgs = function (args, skip, event) { - var left = [event]; - for (var i = skip; i < args.length; i++) { - left.push(args[i]); - } - return left; - }; - /** * Dispatch event. * * @public - * @param {String} type Event type - * @param {...*} args + * @param {String|Function} + * */ - self.trigger = function (type, event) { - if (self.debug !== undefined) { - // Class has debug enabled. Log events. - console.log(self.debug + ' - Firing event "' + type + '", ' + (triggers[type] === undefined ? 0 : triggers[type].length) + ' listeners.', getArgs(arguments, 1)); - } - - if (event === null) { - event = new H5P.Event(); - } - if (triggers[type] === undefined) { + self.trigger = function (event, eventData) { + if (event === undefined) { + return; + } + if (typeof event === 'string') { + event = new H5P.Event(event, eventData); + } + else if (eventData !== undefined) { + event.data = eventData; + } + if (triggers[event.type] === undefined) { return; } - - // Copy all arguments except the first two - var args = getArgs(arguments, 2, event); - - // Call all listeners - for (var i = 0; i < triggers[type].length; i++) { - triggers[type][i].apply(self, args); + for (var i = 0; i < triggers[event.type].length; i++) { + triggers[event.type][i].call(self, event); } }; } diff --git a/js/x-api.js b/js/x-api.js index a7ecb32..687dc59 100644 --- a/js/x-api.js +++ b/js/x-api.js @@ -1,20 +1,21 @@ var H5P = H5P || {}; H5P.xAPIListener = function(event) { - if ('verb' in event.statement) { - if (event.statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { - var score = event.statement.result.score.raw; - var maxScore = event.statement.result.score.max; - var contentId = event.statement.object.extensions['http://h5p.org/x-api/h5p-local-content-id']; + var statement = event.data.statement; + if ('verb' in statement) { + if (statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { + var score = statement.result.score.raw; + var maxScore = statement.result.score.max; + var contentId = statement.object.extensions['http://h5p.org/x-api/h5p-local-content-id']; H5P.setFinished(contentId, score, maxScore); } } }; H5P.xAPIEmitter = function (event) { - if (event.statement !== undefined) { + if (event.data.statement !== undefined) { for (var i = 0; i < H5P.xAPIListeners.length; i++) { - H5P.xAPIListeners[i](event.statement); + H5P.xAPIListeners[i](event.data.statement); } } }; @@ -30,15 +31,14 @@ H5P.onXAPI(function(statement) { }); H5P.XAPIEvent = function() { - H5P.Event.call(this); - this.statement = {}; + H5P.Event.call(this, 'xAPI', {'statement': {}}); }; H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype); H5P.XAPIEvent.prototype.constructor = H5P.XAPIEvent; H5P.XAPIEvent.prototype.setScoredResult = function(score, maxScore) { - this.statement.result = { + this.data.statement.result = { 'score': { 'min': 0, 'max': maxScore, @@ -49,7 +49,7 @@ H5P.XAPIEvent.prototype.setScoredResult = function(score, maxScore) { H5P.XAPIEvent.prototype.setVerb = function(verb) { if (H5P.jQuery.inArray(verb, H5P.XAPIEvent.allowedXAPIVerbs) !== -1) { - this.statement.verb = { + this.data.statement.verb = { 'id': 'http://adlnet.gov/expapi/verbs/' + verb, 'display': { 'en-US': verb @@ -63,7 +63,7 @@ H5P.XAPIEvent.prototype.setVerb = function(verb) { }; H5P.XAPIEvent.prototype.setObject = function(instance) { - this.statement.object = { + this.data.statement.object = { // TODO: Correct this. contentId might be vid 'id': window.location.origin + Drupal.settings.basePath + 'node/' + instance.contentId, 'objectType': 'Activity', @@ -74,7 +74,7 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { }; H5P.XAPIEvent.prototype.setActor = function() { - this.statement.actor = H5P.getActor(); + this.data.statement.actor = H5P.getActor(); }; H5P.XAPIEvent.prototype.getMaxScore = function() { @@ -86,7 +86,7 @@ H5P.XAPIEvent.prototype.getScore = function() { }; H5P.XAPIEvent.prototype.getVerifiedStatementValue = function(keys) { - var val = this.statement; + var val = this.data.statement; for (var i in keys) { if (val[keys[i]] === undefined) { return null; @@ -125,8 +125,7 @@ H5P.XAPIEvent.allowedXAPIVerbs = [ ]; H5P.EventDispatcher.prototype.triggerXAPI = function(verb, extra) { - var event = this.createXAPIEventTemplate(verb, extra); - this.trigger('xAPI', event); + this.trigger(this.createXAPIEventTemplate(verb, extra)); }; H5P.EventDispatcher.prototype.createXAPIEventTemplate = function(verb, extra) { @@ -136,7 +135,7 @@ H5P.EventDispatcher.prototype.createXAPIEventTemplate = function(verb, extra) { event.setVerb(verb); if (extra !== undefined) { for (var i in extra) { - event.statement[i] = extra[i]; + event.data.statement[i] = extra[i]; } } if (!('object' in event)) { @@ -148,7 +147,7 @@ H5P.EventDispatcher.prototype.createXAPIEventTemplate = function(verb, extra) { H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) { var event = this.createXAPIEventTemplate('completed'); event.setScoredResult(score, maxScore); - this.trigger('xAPI', event); + this.trigger(event); } H5P.getActor = function() { From f8434bf7883e899dcfe1d3fe0e80b6afd4af3840 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 11 Feb 2015 17:31:01 +0100 Subject: [PATCH 036/149] Add scopes to event system --- js/event-dispatcher.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/js/event-dispatcher.js b/js/event-dispatcher.js index e9152b0..7a86962 100644 --- a/js/event-dispatcher.js +++ b/js/event-dispatcher.js @@ -31,21 +31,24 @@ H5P.EventDispatcher = (function () { * @param {String} type Event type * @param {Function} listener Event listener */ - self.on = function (type, listener) { + self.on = function (type, listener, scope) { + if (scope === undefined) { + scope = self; + } if (!(listener instanceof Function)) { throw TypeError('listener must be a function'); } // Trigger event before adding to avoid recursion - self.trigger('newListener', type, listener); + self.trigger('newListener', {'type': type, 'listener': listener}); if (!triggers[type]) { // First - triggers[type] = [listener]; + triggers[type] = [{'listener': listener, 'scope': scope}]; } else { // Append - triggers[type].push(listener); + triggers[type].push({'listener': listener, 'scope': scope}); } }; @@ -57,17 +60,20 @@ H5P.EventDispatcher = (function () { * @param {String} type Event type * @param {Function} listener Event listener */ - self.once = function (type, listener) { + self.once = function (type, listener, scope) { + if (scope === undefined) { + scope = self; + } if (!(listener instanceof Function)) { throw TypeError('listener must be a function'); } - var once = function () { - self.off(type, once); - listener.apply(self, arguments); + var once = function (event) { + self.off(event, once); + listener.apply(scope, event); }; - self.on(type, once); + self.on(type, once, scope); }; /** @@ -97,9 +103,9 @@ H5P.EventDispatcher = (function () { // Find specific listener for (var i = 0; i < triggers[type].length; i++) { - if (triggers[type][i] === listener) { + if (triggers[type][i].listener === listener) { triggers[type].unshift(i, 1); - self.trigger('removeListener', type, listener); + self.trigger('removeListener', type, {'listener': listener}); break; } } @@ -132,7 +138,7 @@ H5P.EventDispatcher = (function () { } // Call all listeners for (var i = 0; i < triggers[event.type].length; i++) { - triggers[event.type][i].call(self, event); + triggers[event.type][i].listener.call(triggers[event.type][i].scope, event); } }; } From 5483a3f151d58d935a53f89653665e1dde76966e Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 11 Feb 2015 18:02:58 +0100 Subject: [PATCH 037/149] Remove event dispatcher --- js/h5p-event-dispatcher.js | 152 ------------------------------------- 1 file changed, 152 deletions(-) delete mode 100644 js/h5p-event-dispatcher.js diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js deleted file mode 100644 index fb7f5ef..0000000 --- a/js/h5p-event-dispatcher.js +++ /dev/null @@ -1,152 +0,0 @@ -/** @namespace H5P */ -var H5P = H5P || {}; - -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 events and listeners for each event. - * @private - * @type {Object} - */ - var events = {}; - - /** - * Add new event listener. - * - * @public - * @throws {TypeError} listener must be a function - * @param {String} type Event type - * @param {Function} listener Event listener - */ - self.on = function (type, listener) { - if (!(listener instanceof Function)) { - throw TypeError('listener must be a function'); - } - - // Trigger event before adding to avoid recursion - self.trigger('newListener', type, listener); - - if (!events[type]) { - // First - events[type] = [listener]; - } - else { - // Append - events[type].push(listener); - } - }; - - /** - * 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 - */ - self.once = function (type, listener) { - if (!(listener instanceof Function)) { - throw TypeError('listener must be a function'); - } - - var once = function () { - self.off(type, once); - listener.apply(self, arguments); - }; - - self.on(type, once); - }; - - /** - * 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 - */ - self.off = function (type, listener) { - if (listener !== undefined && !(listener instanceof Function)) { - throw TypeError('listener must be a function'); - } - - if (events[type] === undefined) { - return; - } - - if (listener === undefined) { - // Remove all listeners - delete events[type]; - self.trigger('removeListener', type); - return; - } - - // Find specific listener - for (var i = 0; i < events[type].length; i++) { - if (events[type][i] === listener) { - events[type].unshift(i, 1); - self.trigger('removeListener', type, listener); - break; - } - } - - // Clean up empty arrays - if (!events[type].length) { - delete events[type]; - } - }; - - /** - * Creates a copy of the arguments list. Skips the given number of arguments. - * - * @private - * @param {Array} args List of arguments - * @param {Number} skip Number of arguments to skip - * @param {Array} Copy og arguments list - */ - var getArgs = function (args, skip) { - var left = []; - for (var i = skip; i < args.length; i++) { - left.push(args[i]); - } - return left; - }; - - /** - * Dispatch event. - * - * @public - * @param {String} type Event type - * @param {...*} args - */ - self.trigger = function (type) { - if (self.debug !== undefined) { - // Class has debug enabled. Log events. - console.log(self.debug + ' - Firing event "' + type + '", ' + (events[type] === undefined ? 0 : events[type].length) + ' listeners.', getArgs(arguments, 1)); - } - - if (events[type] === undefined) { - return; - } - - // Copy all arguments except the first - var args = getArgs(arguments, 1); - - // Call all listeners - for (var i = 0; i < events[type].length; i++) { - events[type][i].apply(self, args); - } - }; - } - - return EventDispatcher; -})(); From 36b1fd2d8c4961582f8ee63e01164b4057350c09 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 11 Feb 2015 20:06:40 +0100 Subject: [PATCH 038/149] Add helper function to get verbs --- js/x-api.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/js/x-api.js b/js/x-api.js index 687dc59..f93eab3 100644 --- a/js/x-api.js +++ b/js/x-api.js @@ -62,6 +62,16 @@ H5P.XAPIEvent.prototype.setVerb = function(verb) { // Else: Fail silently... }; +H5P.XAPIEvent.prototype.getShortVerb = function() { + var statement = this.data.statement; + if ('verb' in statement) { + return statement.verb.id.slice(31); + } + else { + return null; + } +} + H5P.XAPIEvent.prototype.setObject = function(instance) { this.data.statement.object = { // TODO: Correct this. contentId might be vid From 9fc5f82b2f95cd94550ab86e8725bbe36f3ef169 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Thu, 12 Feb 2015 11:52:55 +0100 Subject: [PATCH 039/149] Fixed empty frame. --- js/h5p.js | 62 +++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index df93a0c..9381c3e 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -88,40 +88,38 @@ H5P.init = function () { }); } - if (contentData.disable & H5P.DISABLE_FRAME) { - $element.addClass('h5p-no-frame'); + // Create action bar + var $actions = H5P.jQuery('
      '); + + if (!(contentData.disable & H5P.DISABLE_DOWNLOAD)) { + // Add export button + H5P.jQuery('
    • ' + H5P.t('download') + '
    • ').appendTo($actions).click(function () { + window.location.href = contentData.exportUrl; + }); + } + if (!(contentData.disable & H5P.DISABLE_COPYRIGHT) && instance.getCopyrights !== undefined) { + // Add copyrights button + H5P.jQuery('
    • ' + H5P.t('copyrights') + '
    • ').appendTo($actions).click(function () { + H5P.openCopyrightsDialog($actions, instance, library.params, contentId); + }); + } + if (!(contentData.disable & H5P.DISABLE_EMBED)) { + // Add embed button + H5P.jQuery('
    • ' + H5P.t('embed') + '
    • ').appendTo($actions).click(function () { + H5P.openEmbedDialog($actions, contentData.embedCode); + }); + } + if (!(contentData.disable & H5P.DISABLE_ABOUT)) { + // Add about H5P button icon + H5P.jQuery('
    • ').appendTo($actions); + } + + // Insert action bar if it has any content + if ($actions.children().length) { + $actions.insertAfter($container); } else { - // Create action bar - var $actions = H5P.jQuery('
        '); - - if (!(contentData.disable & H5P.DISABLE_DOWNLOAD)) { - // Add export button - H5P.jQuery('
      • ' + H5P.t('download') + '
      • ').appendTo($actions).click(function () { - window.location.href = contentData.exportUrl; - }); - } - if (!(contentData.disable & H5P.DISABLE_COPYRIGHT) && instance.getCopyrights !== undefined) { - // Add copyrights button - H5P.jQuery('
      • ' + H5P.t('copyrights') + '
      • ').appendTo($actions).click(function () { - H5P.openCopyrightsDialog($actions, instance, library.params, contentId); - }); - } - if (!(contentData.disable & H5P.DISABLE_EMBED)) { - // Add embed button - H5P.jQuery('
      • ' + H5P.t('embed') + '
      • ').appendTo($actions).click(function () { - H5P.openEmbedDialog($actions, contentData.embedCode); - }); - } - if (!(contentData.disable & H5P.DISABLE_ABOUT)) { - // Add about H5P button icon - H5P.jQuery('
      • ').appendTo($actions); - } - - // Insert action bar if it has any content - if ($actions.children().length) { - $actions.insertAfter($container); - } + $element.addClass('h5p-no-frame'); } // Keep track of when we started From 83a077be158f2d39c770cc644433c40fe0dbc868 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Thu, 12 Feb 2015 15:46:25 +0100 Subject: [PATCH 040/149] Rename api function --- js/x-api.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/x-api.js b/js/x-api.js index f93eab3..e94adf4 100644 --- a/js/x-api.js +++ b/js/x-api.js @@ -62,9 +62,12 @@ H5P.XAPIEvent.prototype.setVerb = function(verb) { // Else: Fail silently... }; -H5P.XAPIEvent.prototype.getShortVerb = function() { +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 { From 1d24d073ed679c3e3ab3662d3c5715cffe12ce33 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Thu, 12 Feb 2015 15:56:31 +0100 Subject: [PATCH 041/149] Moved "libraries saving" out of savePackage to make it easier to read and handle. Added UI messages when adding and updating libraries. Minor code optimizations. --- h5p.classes.php | 238 ++++++++++++++++++++++++++++++------------------ 1 file changed, 151 insertions(+), 87 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index f0ca139..b6d3b37 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -770,15 +770,14 @@ class H5PValidator { // When upgrading, we opnly add allready installed libraries, // and new dependent libraries $upgrades = array(); - foreach ($libraries as &$library) { + foreach ($libraries as $libString => &$library) { // Is this library already installed? - if ($this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']) !== FALSE) { - $upgrades[H5PCore::libraryToString($library)] = $library; + if ($this->h5pC->getLibraryId($library, $libString) !== FALSE) { + $upgrades[$libString] = $library; } } while ($missingLibraries = $this->getMissingLibraries($upgrades)) { - foreach ($missingLibraries as $missing) { - $libString = H5PCore::libraryToString($missing); + foreach ($missingLibraries as $libString => $missing) { $library = $libraries[$libString]; if ($library) { $upgrades[$libString] = $library; @@ -798,15 +797,15 @@ class H5PValidator { } $missingLibraries = $this->getMissingLibraries($libraries); - foreach ($missingLibraries as $missing) { - if ($this->h5pF->getLibraryId($missing['machineName'], $missing['majorVersion'], $missing['minorVersion'])) { - unset($missingLibraries[H5PCore::libraryToString($missing)]); + foreach ($missingLibraries as $libString => $missing) { + if ($this->h5pC->getLibraryId($missing, $libString)) { + unset($missingLibraries[$libString]); } } if (!empty($missingLibraries)) { - foreach ($missingLibraries as $library) { - $this->h5pF->setErrorMessage($this->h5pF->t('Missing required library @library', array('@library' => H5PCore::libraryToString($library)))); + foreach ($missingLibraries as $libString => $library) { + $this->h5pF->setErrorMessage($this->h5pF->t('Missing required library @library', array('@library' => $libString))); } if (!$this->h5pF->mayUpdateLibraries()) { $this->h5pF->setInfoMessage($this->h5pF->t("Note that the libraries may exist in the file you uploaded, but you're not allowed to upload new libraries. Contact the site administrator about this.")); @@ -937,8 +936,9 @@ class H5PValidator { private function getMissingDependencies($dependencies, $libraries) { $missing = array(); foreach ($dependencies as $dependency) { - if (!isset($libraries[H5PCore::libraryToString($dependency)])) { - $missing[H5PCore::libraryToString($dependency)] = $dependency; + $libString = H5PCore::libraryToString($dependency); + if (!isset($libraries[$libString])) { + $missing[$libString] = $dependency; } } return $missing; @@ -1238,75 +1238,15 @@ class H5PStorage { * TRUE if one or more libraries were updated * FALSE otherwise */ - public function savePackage($content = NULL, $contentMainId = NULL, $skipContent = FALSE, $upgradeOnly = FALSE) { - // Save the libraries we processed during validation - $library_saved = FALSE; - $upgradedLibsCount = 0; - $mayUpdateLibraries = $this->h5pF->mayUpdateLibraries(); - - foreach ($this->h5pC->librariesJsonData as &$library) { - $libraryId = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']); - $library['saveDependencies'] = TRUE; - - if (!$libraryId) { - $new = TRUE; - } - elseif ($this->h5pF->isPatchedLibrary($library)) { - $new = FALSE; - $library['libraryId'] = $libraryId; - } - else { - $library['libraryId'] = $libraryId; - // We already have the same or a newer version of this library - $library['saveDependencies'] = FALSE; - continue; - } - - if (!$mayUpdateLibraries) { - // This shouldn't happen, but just to be safe... - continue; - } - - $this->h5pF->saveLibraryData($library, $new); - - $libraries_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'libraries'; - if (!is_dir($libraries_path)) { - mkdir($libraries_path, 0777, true); - } - $destination_path = $libraries_path . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE); - H5PCore::deleteFileTree($destination_path); - $this->h5pC->copyFileTree($library['uploadDirectory'], $destination_path); - H5PCore::deleteFileTree($library['uploadDirectory']); - - $library_saved = TRUE; - } - - foreach ($this->h5pC->librariesJsonData as &$library) { - if ($library['saveDependencies']) { - $this->h5pF->deleteLibraryDependencies($library['libraryId']); - if (isset($library['preloadedDependencies'])) { - $this->h5pF->saveLibraryDependencies($library['libraryId'], $library['preloadedDependencies'], 'preloaded'); - } - if (isset($library['dynamicDependencies'])) { - $this->h5pF->saveLibraryDependencies($library['libraryId'], $library['dynamicDependencies'], 'dynamic'); - } - if (isset($library['editorDependencies'])) { - $this->h5pF->saveLibraryDependencies($library['libraryId'], $library['editorDependencies'], 'editor'); - } - - // Make sure libraries dependencies, parameter filtering and export files gets regenerated for all content who uses this library. - $this->h5pF->clearFilteredParameters($library['libraryId']); - - $upgradedLibsCount++; - } + public function savePackage($content = NULL, $contentMainId = NULL, $skipContent = FALSE) { + if ($this->h5pF->mayUpdateLibraries()) { + // Save the libraries we processed during validation + $this->saveLibraries(); } if (!$skipContent) { - $current_path = $this->h5pF->getUploadedH5pFolderPath() . DIRECTORY_SEPARATOR . 'content'; - - // Find out which libraries are used by this package/content - $librariesInUse = array(); - $nextWeight = $this->h5pC->findLibraryDependencies($librariesInUse, $this->h5pC->mainJsonData); + $basePath = $this->h5pF->getUploadedH5pFolderPath(); + $current_path = $basePath . DIRECTORY_SEPARATOR . 'content'; // Save content if ($content === NULL) { @@ -1315,7 +1255,16 @@ class H5PStorage { if (!is_array($content)) { $content = array('id' => $content); } - $content['library'] = $librariesInUse['preloaded-' . $this->h5pC->mainJsonData['mainLibrary']]['library']; + + // Find main library version + foreach ($this->h5pC->mainJsonData['preloadedDependencies'] as $dep) { + if ($dep['machineName'] === $this->h5pC->mainJsonData['mainLibrary']) { + $dep['libraryId'] = $this->h5pC->getLibraryId($dep); + $content['library'] = $dep; + break; + } + } + $content['params'] = file_get_contents($current_path . DIRECTORY_SEPARATOR . 'content.json'); $contentId = $this->h5pC->saveContent($content, $contentMainId); $this->contentId = $contentId; @@ -1328,22 +1277,115 @@ class H5PStorage { // Move the content folder $destination_path = $contents_path . DIRECTORY_SEPARATOR . $contentId; $this->h5pC->copyFileTree($current_path, $destination_path); - H5PCore::deleteFileTree($current_path); - // Save the content library dependencies - $this->h5pF->saveLibraryUsage($contentId, $librariesInUse); - H5PCore::deleteFileTree($this->h5pF->getUploadedH5pFolderPath()); + // Remove temp content folder + H5PCore::deleteFileTree($basePath); } // Update supported library list if neccessary: $this->h5pC->validateLibrarySupport(TRUE); + } - if ($upgradeOnly) { - // TODO - support translation - $this->h5pF->setInfoMessage($this->h5pF->t('@num libraries were upgraded!', array('@num' => $upgradedLibsCount))); + /** + * Helps savePackage. + * + * @return int Number of libraries saved + */ + private function saveLibraries() { + // Keep track of the number of libraries that have been saved + $newOnes = 0; + $oldOnes = 0; + + // Find libraries directory and make sure it exists + $libraries_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'libraries'; + if (!is_dir($libraries_path)) { + mkdir($libraries_path, 0777, true); } - return $library_saved; + // Go through libraries that came with this package + foreach ($this->h5pC->librariesJsonData as $libString => &$library) { + // Find local library identifier + $libraryId = $this->h5pC->getLibraryId($library, $libString); + + // Assume new library + $new = TRUE; + if ($libraryId) { + // Found old library + $library['libraryId'] = $libraryId; + + if ($this->h5pF->isPatchedLibrary($library)) { + // This is a newer version than ours. Upgrade! + $new = FALSE; + } + else { + $library['saveDependencies'] = FALSE; + // This is an older version, no need to save. + continue; + } + } + + // Indicate that the dependencies of this library should be saved. + $library['saveDependencies'] = TRUE; + + // Save library meta data + $this->h5pF->saveLibraryData($library, $new); + + // Make sure destination dir is free + $destination_path = $libraries_path . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE); + H5PCore::deleteFileTree($destination_path); + + // Move library folder + $this->h5pC->copyFileTree($library['uploadDirectory'], $destination_path); + H5PCore::deleteFileTree($library['uploadDirectory']); + + if ($new) { + $newOnes++; + } + else { + $oldOnes++; + } + } + + // Go through the libraries again to save dependencies. + foreach ($this->h5pC->librariesJsonData as &$library) { + if (!$library['saveDependencies']) { + continue; + } + + // TODO: Should the table be locked for this operation? + + // Remove any old dependencies + $this->h5pF->deleteLibraryDependencies($library['libraryId']); + + // Insert the different new ones + if (isset($library['preloadedDependencies'])) { + $this->h5pF->saveLibraryDependencies($library['libraryId'], $library['preloadedDependencies'], 'preloaded'); + } + if (isset($library['dynamicDependencies'])) { + $this->h5pF->saveLibraryDependencies($library['libraryId'], $library['dynamicDependencies'], 'dynamic'); + } + if (isset($library['editorDependencies'])) { + $this->h5pF->saveLibraryDependencies($library['libraryId'], $library['editorDependencies'], 'editor'); + } + + // Make sure libraries dependencies, parameter filtering and export files gets regenerated for all content who uses this library. + $this->h5pF->clearFilteredParameters($library['libraryId']); + } + + // Tell the user what we've done. + if ($newOnes && $oldOnes) { + $message = $this->h5pF->t('Added %new new H5P libraries and updated %old old.', array('%new' => $newOnes, '%old' => $oldOnes)); + } + elseif ($newOnes) { + $message = $this->h5pF->t('Added %new new H5P libraries.', array('%new' => $newOnes)); + } + elseif ($oldOnes) { + $message = $this->h5pF->t('Updated %old H5P libraries.', array('%old' => $oldOnes)); + } + + if (isset($message)) { + $this->h5pF->setInfoMessage($message); + } } /** @@ -2220,6 +2262,28 @@ class H5PCore { } } } + + // 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]; + } } /** From 10fbca1549ac568e113af13557ffbf6b6e0422c5 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 16 Feb 2015 14:22:54 +0100 Subject: [PATCH 042/149] Documentation updates --- js/event-dispatcher.js | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/js/event-dispatcher.js b/js/event-dispatcher.js index 7a86962..36066ca 100644 --- a/js/event-dispatcher.js +++ b/js/event-dispatcher.js @@ -1,6 +1,10 @@ /** @namespace H5P */ var H5P = H5P || {}; +/** + * The Event class for the EventDispatcher + * @class + */ H5P.Event = function(type, data) { this.type = type; this.data = data; @@ -27,13 +31,14 @@ H5P.EventDispatcher = (function () { * Add new event listener. * * @public - * @throws {TypeError} listener must be a function - * @param {String} type Event type - * @param {Function} listener Event listener + * @throws {TypeError} listener - Must be a function + * @param {String} type - Event type + * @param {Function} listener - Event listener + * @param {Function} thisArg - Optional thisArg to call the listener from */ - self.on = function (type, listener, scope) { - if (scope === undefined) { - scope = self; + self.on = function (type, listener, thisArg) { + if (thisArg === undefined) { + thisArg = self; } if (!(listener instanceof Function)) { throw TypeError('listener must be a function'); @@ -44,11 +49,11 @@ H5P.EventDispatcher = (function () { if (!triggers[type]) { // First - triggers[type] = [{'listener': listener, 'scope': scope}]; + triggers[type] = [{'listener': listener, 'thisArg': thisArg}]; } else { // Append - triggers[type].push({'listener': listener, 'scope': scope}); + triggers[type].push({'listener': listener, 'thisArg': thisArg}); } }; @@ -60,9 +65,9 @@ H5P.EventDispatcher = (function () { * @param {String} type Event type * @param {Function} listener Event listener */ - self.once = function (type, listener, scope) { - if (scope === undefined) { - scope = self; + self.once = function (type, listener, thisArg) { + if (thisArg === undefined) { + thisArg = self; } if (!(listener instanceof Function)) { throw TypeError('listener must be a function'); @@ -70,10 +75,10 @@ H5P.EventDispatcher = (function () { var once = function (event) { self.off(event, once); - listener.apply(scope, event); + listener.apply(thisArg, event); }; - self.on(type, once, scope); + self.on(type, once, thisArg); }; /** @@ -138,7 +143,7 @@ H5P.EventDispatcher = (function () { } // Call all listeners for (var i = 0; i < triggers[event.type].length; i++) { - triggers[event.type][i].listener.call(triggers[event.type][i].scope, event); + triggers[event.type][i].listener.call(triggers[event.type][i].thisArg, event); } }; } From 2237f026c976b3367524b978ff81eb067e78ac51 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 16 Feb 2015 14:26:09 +0100 Subject: [PATCH 043/149] Documentation updates --- js/event-dispatcher.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/js/event-dispatcher.js b/js/event-dispatcher.js index 36066ca..25a3b5c 100644 --- a/js/event-dispatcher.js +++ b/js/event-dispatcher.js @@ -34,7 +34,7 @@ H5P.EventDispatcher = (function () { * @throws {TypeError} listener - Must be a function * @param {String} type - Event type * @param {Function} listener - Event listener - * @param {Function} thisArg - Optional thisArg to call the listener from + * @param {Function} thisArg - Optionally specify the this value when calling listener. */ self.on = function (type, listener, thisArg) { if (thisArg === undefined) { @@ -61,9 +61,10 @@ H5P.EventDispatcher = (function () { * 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 + * @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. */ self.once = function (type, listener, thisArg) { if (thisArg === undefined) { @@ -86,9 +87,9 @@ H5P.EventDispatcher = (function () { * 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 + * @throws {TypeError} listener - must be a function + * @param {String} type - Event type + * @param {Function} listener - Event listener */ self.off = function (type, listener) { if (listener !== undefined && !(listener instanceof Function)) { @@ -125,8 +126,10 @@ H5P.EventDispatcher = (function () { * Dispatch event. * * @public - * @param {String|Function} - * + * @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 */ self.trigger = function (event, eventData) { if (event === undefined) { From b575426f83e6ee17b6fdc902722992cbf51e5f4e Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 16 Feb 2015 15:30:49 +0100 Subject: [PATCH 044/149] Add documentation --- js/x-api.js | 111 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 104 insertions(+), 7 deletions(-) diff --git a/js/x-api.js b/js/x-api.js index e94adf4..a2fedee 100644 --- a/js/x-api.js +++ b/js/x-api.js @@ -1,5 +1,10 @@ var H5P = H5P || {}; +/** + * Internal H5P function listening for xAPI completed events and stores scores + * + * @param {function} event - xAPI event + */ H5P.xAPIListener = function(event) { var statement = event.data.statement; if ('verb' in statement) { @@ -12,6 +17,11 @@ H5P.xAPIListener = function(event) { } }; +/** + * Trigger xAPI events on all registered listeners + * + * @param {Function} event - xAPI event + */ H5P.xAPIEmitter = function (event) { if (event.data.statement !== undefined) { for (var i = 0; i < H5P.xAPIListeners.length; i++) { @@ -22,14 +32,20 @@ H5P.xAPIEmitter = function (event) { H5P.xAPIListeners = []; +/** + * API function used to register for xAPI events + * + * @param {Function} listener + */ H5P.onXAPI = function(listener) { H5P.xAPIListeners.push(listener); }; -H5P.onXAPI(function(statement) { - console.log(statement); -}); - +/** + * Constructor for xAPI events + * + * @class + */ H5P.XAPIEvent = function() { H5P.Event.call(this, 'xAPI', {'statement': {}}); }; @@ -37,6 +53,12 @@ H5P.XAPIEvent = function() { 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': { @@ -47,6 +69,13 @@ H5P.XAPIEvent.prototype.setScoredResult = function(score, maxScore) { }; }; +/** + * 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 = { @@ -62,6 +91,13 @@ H5P.XAPIEvent.prototype.setVerb = function(verb) { // Else: Fail silently... }; +/** + * 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) { @@ -75,9 +111,17 @@ H5P.XAPIEvent.prototype.getVerb = function(full) { } } +/** + * 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) { this.data.statement.object = { - // TODO: Correct this. contentId might be vid + // TODO: Correct this. contentId might be vid, and this can't be Drupal + // specific 'id': window.location.origin + Drupal.settings.basePath + 'node/' + instance.contentId, 'objectType': 'Activity', 'extensions': { @@ -86,29 +130,55 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { }; }; +/** + * Helper function to set the actor, email and name will be added automatically + */ H5P.XAPIEvent.prototype.setActor = function() { this.data.statement.actor = H5P.getActor(); }; +/** + * 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']); }; +/** + * 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 in keys) { + 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', @@ -137,10 +207,26 @@ H5P.XAPIEvent.allowedXAPIVerbs = [ 'voided' ]; +/** + * 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(); @@ -157,12 +243,23 @@ H5P.EventDispatcher.prototype.createXAPIEventTemplate = function(verb, extra) { return event; }; +/** + * Helper function to create xAPI completed 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 + */ H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) { var event = this.createXAPIEventTemplate('completed'); event.setScoredResult(score, maxScore); this.trigger(event); } +/** + * Helps get the data for the actor part of the xAPI statement + * + * @returns {object} - the actor object for the xAPI statement + */ H5P.getActor = function() { var user = H5PIntegration.getUser(); return { From c33028235173a81483c64444c25f69082c48f70a Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 16 Feb 2015 16:47:04 +0100 Subject: [PATCH 045/149] Refactor xAPI code --- h5p.classes.php | 5 +- ...-dispatcher.js => h5p-event-dispatcher.js} | 0 js/{x-api.js => h5p-x-api-event.js} | 112 ++---------------- js/h5p-x-api.js | 71 +++++++++++ js/h5p.js | 4 +- 5 files changed, 83 insertions(+), 109 deletions(-) rename js/{event-dispatcher.js => h5p-event-dispatcher.js} (100%) rename js/{x-api.js => h5p-x-api-event.js} (59%) create mode 100644 js/h5p-x-api.js diff --git a/h5p.classes.php b/h5p.classes.php index c54a92c..f85b0dc 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1590,8 +1590,9 @@ class H5PCore { public static $scripts = array( 'js/jquery.js', 'js/h5p.js', - 'js/event-dispatcher.js', - 'js/x-api.js', + 'js/h5p-event-dispatcher.js', + 'js/h5p-x-api-event.js', + 'js/h5p-x-api.js', ); public static $adminScripts = array( 'js/jquery.js', diff --git a/js/event-dispatcher.js b/js/h5p-event-dispatcher.js similarity index 100% rename from js/event-dispatcher.js rename to js/h5p-event-dispatcher.js diff --git a/js/x-api.js b/js/h5p-x-api-event.js similarity index 59% rename from js/x-api.js rename to js/h5p-x-api-event.js index a2fedee..2d8e24e 100644 --- a/js/x-api.js +++ b/js/h5p-x-api-event.js @@ -1,46 +1,5 @@ var H5P = H5P || {}; -/** - * Internal H5P function listening for xAPI completed events and stores scores - * - * @param {function} event - xAPI event - */ -H5P.xAPIListener = function(event) { - var statement = event.data.statement; - if ('verb' in statement) { - if (statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { - var score = statement.result.score.raw; - var maxScore = statement.result.score.max; - var contentId = statement.object.extensions['http://h5p.org/x-api/h5p-local-content-id']; - H5P.setFinished(contentId, score, maxScore); - } - } -}; - -/** - * Trigger xAPI events on all registered listeners - * - * @param {Function} event - xAPI event - */ -H5P.xAPIEmitter = function (event) { - if (event.data.statement !== undefined) { - for (var i = 0; i < H5P.xAPIListeners.length; i++) { - H5P.xAPIListeners[i](event.data.statement); - } - } -}; - -H5P.xAPIListeners = []; - -/** - * API function used to register for xAPI events - * - * @param {Function} listener - */ -H5P.onXAPI = function(listener) { - H5P.xAPIListeners.push(listener); -}; - /** * Constructor for xAPI events * @@ -134,7 +93,12 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { * Helper function to set the actor, email and name will be added automatically */ H5P.XAPIEvent.prototype.setActor = function() { - this.data.statement.actor = H5P.getActor(); + var user = H5PIntegration.getUser(); + this.data.statement.actor = { + 'name': user.name, + 'mbox': 'mailto:' + user.mail, + 'objectType': 'Agent' + }; }; /** @@ -205,66 +169,4 @@ H5P.XAPIEvent.allowedXAPIVerbs = [ 'suspended', 'terminated', 'voided' -]; - -/** - * 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)) { - event.setObject(this); - } - return event; -}; - -/** - * Helper function to create xAPI completed 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 - */ -H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) { - var event = this.createXAPIEventTemplate('completed'); - event.setScoredResult(score, maxScore); - this.trigger(event); -} - -/** - * Helps get the data for the actor part of the xAPI statement - * - * @returns {object} - the actor object for the xAPI statement - */ -H5P.getActor = function() { - var user = H5PIntegration.getUser(); - return { - 'name': user.name, - 'mbox': 'mailto:' + user.mail, - 'objectType': 'Agent' - }; -}; \ No newline at end of file +]; \ No newline at end of file diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js new file mode 100644 index 0000000..230497c --- /dev/null +++ b/js/h5p-x-api.js @@ -0,0 +1,71 @@ +var H5P = H5P || {}; + +// Create object where external code may register and listen for H5P Events +H5P.xAPIExternal = 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)) { + event.setObject(this); + } + return event; +}; + +/** + * Helper function to create xAPI completed 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 + */ +H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) { + var event = this.createXAPIEventTemplate('completed'); + 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) { + var statement = event.data.statement; + if ('verb' in statement) { + if (statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { + var score = statement.result.score.raw; + var maxScore = statement.result.score.max; + var contentId = statement.object.extensions['http://h5p.org/x-api/h5p-local-content-id']; + H5P.setFinished(contentId, score, maxScore); + } + } +}; \ No newline at end of file diff --git a/js/h5p.js b/js/h5p.js index 5df3f2b..4f11a32 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -128,8 +128,8 @@ H5P.init = function () { H5P.instances.push(instance); } - H5P.on(instance, 'xAPI', H5P.xAPIListener); - H5P.on(instance, 'xAPI', H5P.xAPIEmitter); + H5P.on(instance, 'xAPI', H5P.xAPICompletedListener); + H5P.on(instance, 'xAPI', H5P.xAPIExternal.trigger); // Resize everything when window is resized. $window.resize(function () { From 537c62ab3320734f334717336aa2534cd2062f9c Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 17 Feb 2015 10:57:12 +0100 Subject: [PATCH 046/149] Get url to the H5P content from the framework --- js/h5p-x-api-event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 2d8e24e..e6fba9b 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -81,7 +81,7 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { this.data.statement.object = { // TODO: Correct this. contentId might be vid, and this can't be Drupal // specific - 'id': window.location.origin + Drupal.settings.basePath + 'node/' + instance.contentId, + 'id': H5PIntegration.getContentUrl(instance.contentId), 'objectType': 'Activity', 'extensions': { 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId From 0a6dd4808b7534e067eade837128e5fa2aae801a Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 17 Feb 2015 10:57:21 +0100 Subject: [PATCH 047/149] Rename the external api --- js/h5p-x-api.js | 2 +- js/h5p.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index 230497c..0cf9a84 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -1,7 +1,7 @@ var H5P = H5P || {}; // Create object where external code may register and listen for H5P Events -H5P.xAPIExternal = new H5P.EventDispatcher(); +H5P.externalDispatcher = new H5P.EventDispatcher(); // EventDispatcher extensions diff --git a/js/h5p.js b/js/h5p.js index 4f11a32..4222210 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -129,7 +129,7 @@ H5P.init = function () { } H5P.on(instance, 'xAPI', H5P.xAPICompletedListener); - H5P.on(instance, 'xAPI', H5P.xAPIExternal.trigger); + H5P.on(instance, 'xAPI', H5P.externalDispatcher.trigger); // Resize everything when window is resized. $window.resize(function () { From a96e5cc7b9e8bb0afdc01e85da0bb59fd02f8f85 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 18 Feb 2015 09:07:19 +0100 Subject: [PATCH 048/149] Make the xAPI integration work for stuff that aren't content types --- js/h5p-x-api-event.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index e6fba9b..db87477 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -78,15 +78,21 @@ H5P.XAPIEvent.prototype.getVerb = function(full) { * @param {object} instance - the H5P instance */ H5P.XAPIEvent.prototype.setObject = function(instance) { - this.data.statement.object = { - // TODO: Correct this. contentId might be vid, and this can't be Drupal - // specific - 'id': H5PIntegration.getContentUrl(instance.contentId), - 'objectType': 'Activity', - 'extensions': { - 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId - } - }; + if (instance.contentId) { + this.data.statement.object = { + 'id': H5PIntegration.getContentUrl(instance.contentId), + 'objectType': 'Activity', + 'extensions': { + 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId + } + }; + } + else { + // Not triggered by an H5P content type... + this.data.statement.object = { + 'objectType': 'Activity' + }; + } }; /** From 1995da20bbcf12d366295553ae1790c7eee58e29 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 18 Feb 2015 09:07:38 +0100 Subject: [PATCH 049/149] Remove old todo items --- js/h5p.js | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 4222210..dd948b5 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -155,28 +155,6 @@ H5P.init = function () { }); }; -/* - * TODO xAPI: - * 1. Create a xAPI.js file and move xAPI code there (public) - * 2. Be able to listen for events from both div and iframe embedded content - * via the same API (this is about adding communication between the iframe and - * it's parent and make sure that the parent distributes the events from the - * iframe) (public) - * 3. Create a separate Drupal module that is able to listen for events from - * both div and iframe embedded content and send them to analytics (custom for Zavango) - * 4. Move the event system code to a separate file (public) - * 5. Make sure the helper functions provides all the relevant data, example values - * and time spent (public) - * 6. Add documentation to the functions (public) - * 7. Add xAPI events to all the basic questiontype: - * 7.1 Multichoice - * 7.2 Fill in the blanks - * 7.3 Drag and drop - * 7.4 Drag the words - * 7.5 Mark the words - * 8. Add xAPI events to interactive video - */ - /** * Enable full screen for the given h5p. * From d6db07481e152559a35554df8a71b905ca444e84 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 18 Feb 2015 09:07:57 +0100 Subject: [PATCH 050/149] Make sure the contentId is set on content types --- js/h5p.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/h5p.js b/js/h5p.js index dd948b5..84a2921 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -391,6 +391,10 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { if (instance.$ === undefined) { instance.$ = H5P.jQuery(instance); } + + if (instance.contentId === undefined) { + instance.conentId = contentId; + } // Make xAPI events bubble // if (parent !== null && parent.trigger !== undefined) { From e981e7d605542877dc234081e98496247e35b364 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 18 Feb 2015 10:59:47 +0100 Subject: [PATCH 051/149] Trigger xAPI events on top as well if inside ifra --- js/h5p-event-dispatcher.js | 2 +- js/h5p-x-api.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index 25a3b5c..204fda9 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -40,7 +40,7 @@ H5P.EventDispatcher = (function () { if (thisArg === undefined) { thisArg = self; } - if (!(listener instanceof Function)) { + if (typeof listener !== 'function') { throw TypeError('listener must be a function'); } diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index 0cf9a84..77b9357 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -3,6 +3,10 @@ var H5P = H5P || {}; // Create object where external code may register and listen for H5P Events H5P.externalDispatcher = new H5P.EventDispatcher(); +if (window.top !== window.self && window.top.H5P !== undefined && window.top.H5P.externalDispatcher !== undefined) { + H5P.externalDispatcher.on('xAPI', window.top.H5P.externalDispatcher.trigger); +} + // EventDispatcher extensions /** From 0d8fe7498c1c4bbf32ff832c90b0639edf35a571 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 18 Feb 2015 15:08:55 +0100 Subject: [PATCH 052/149] Remove code not being used --- js/h5p.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 84a2921..58f6459 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -396,16 +396,6 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { instance.conentId = contentId; } - // Make xAPI events bubble -// if (parent !== null && parent.trigger !== undefined) { -// instance.on('xAPI', parent.trigger); -// } - - // Automatically call resize on resize event if defined - if (typeof instance.resize === 'function') { - H5P.on(instance, 'resize', instance.resize); - } - if ($attachTo !== undefined) { instance.attach($attachTo); From c7c46bc7791b44f324ba966488f30cbaff1a6305 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 18 Feb 2015 15:20:20 +0100 Subject: [PATCH 053/149] Fix spelling error --- js/h5p.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 58f6459..aa1dcfe 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -393,7 +393,7 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { } if (instance.contentId === undefined) { - instance.conentId = contentId; + instance.contentId = contentId; } if ($attachTo !== undefined) { From 3d4d72c5a78547da955a3a80c5a65653c908921b Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 18 Feb 2015 19:50:23 +0100 Subject: [PATCH 054/149] Avoid exporting git files --- h5p.classes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h5p.classes.php b/h5p.classes.php index f85b0dc..af251d2 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1988,7 +1988,7 @@ class H5PCore { @mkdir($destination); while (false !== ($file = readdir($dir))) { - if (($file != '.') && ($file != '..')) { + if (($file != '.') && ($file != '..') && $file != '.git' && $file != '.gitignore') { if (is_dir($source . DIRECTORY_SEPARATOR . $file)) { $this->copyFileTree($source . DIRECTORY_SEPARATOR . $file, $destination . DIRECTORY_SEPARATOR . $file); } From 95a0b000db5eb6ae4fa68ac7eca1089c7c9369aa Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 18 Feb 2015 19:58:03 +0100 Subject: [PATCH 055/149] Remove scary error messages --- h5p.classes.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index af251d2..47df2f2 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2670,7 +2670,7 @@ class H5PContentValidator { else { // If validator is not found, something exists in content that does // not have a corresponding semantics field. Remove it. - $this->h5pF->setErrorMessage($this->h5pF->t('H5P internal error: no validator exists for @key', array('@key' => $key))); + // $this->h5pF->setErrorMessage($this->h5pF->t('H5P internal error: no validator exists for @key', array('@key' => $key))); unset($group->$key); } } @@ -2680,7 +2680,7 @@ class H5PContentValidator { if (!(isset($field->optional) && $field->optional)) { // Check if field is in group. if (! property_exists($group, $field->name)) { - $this->h5pF->setErrorMessage($this->h5pF->t('No value given for mandatory field ' . $field->name)); + //$this->h5pF->setErrorMessage($this->h5pF->t('No value given for mandatory field ' . $field->name)); } } } From 971a55df585135729e3b2fa71b0249414eb098ed Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 20 Feb 2015 10:26:33 +0100 Subject: [PATCH 056/149] Satisfy JSHint and prevent crashing. --- js/h5p-x-api-event.js | 27 +++++++++++++-------------- js/h5p-x-api.js | 12 ++++++------ js/h5p.js | 6 +++--- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index db87477..346bf64 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -2,7 +2,7 @@ var H5P = H5P || {}; /** * Constructor for xAPI events - * + * * @class */ H5P.XAPIEvent = function() { @@ -14,7 +14,7 @@ H5P.XAPIEvent.prototype.constructor = H5P.XAPIEvent; /** * Helperfunction to set scored result statements - * + * * @param {int} score * @param {int} maxScore */ @@ -30,7 +30,7 @@ H5P.XAPIEvent.prototype.setScoredResult = function(score, maxScore) { /** * Helperfunction to set a verb. - * + * * @param {string} verb * Verb in short form, one of the verbs defined at * http://adlnet.gov/expapi/verbs/ @@ -45,14 +45,13 @@ H5P.XAPIEvent.prototype.setVerb = function(verb) { }; } else { - console.log('illegal verb'); + H5P.error('illegal verb'); } - // Else: Fail silently... }; /** * 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 @@ -68,13 +67,13 @@ H5P.XAPIEvent.prototype.getVerb = function(full) { 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) { @@ -109,7 +108,7 @@ H5P.XAPIEvent.prototype.setActor = function() { /** * 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() { @@ -118,7 +117,7 @@ H5P.XAPIEvent.prototype.getMaxScore = function() { /** * 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() { @@ -127,7 +126,7 @@ H5P.XAPIEvent.prototype.getScore = function() { /** * 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 @@ -146,7 +145,7 @@ H5P.XAPIEvent.prototype.getVerifiedStatementValue = function(keys) { /** * List of verbs defined at http://adlnet.gov/expapi/verbs/ - * + * * @type Array */ H5P.XAPIEvent.allowedXAPIVerbs = [ @@ -175,4 +174,4 @@ H5P.XAPIEvent.allowedXAPIVerbs = [ 'suspended', 'terminated', 'voided' -]; \ No newline at end of file +]; diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index 77b9357..37a593b 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -11,7 +11,7 @@ if (window.top !== window.self && window.top.H5P !== undefined && window.top.H5P /** * 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 */ @@ -21,10 +21,10 @@ H5P.EventDispatcher.prototype.triggerXAPI = function(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 @@ -55,11 +55,11 @@ H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) { var event = this.createXAPIEventTemplate('completed'); 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) { @@ -72,4 +72,4 @@ H5P.xAPICompletedListener = function(event) { H5P.setFinished(contentId, score, maxScore); } } -}; \ No newline at end of file +}; diff --git a/js/h5p.js b/js/h5p.js index bdbdff1..9d40789 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -548,7 +548,7 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { * @returns {undefined} */ H5P.error = function (err) { - if (window['console'] !== undefined && console.error !== undefined) { + if (window.console !== undefined && console.error !== undefined) { console.error(err); } }; @@ -1244,7 +1244,7 @@ H5P.trigger = function(instance, eventType) { } // Try deprecated event system else if (instance.$ !== undefined && instance.$.trigger !== undefined) { - instance.$.trigger(eventType) + instance.$.trigger(eventType); } }; @@ -1268,6 +1268,6 @@ H5P.on = function(instance, eventType, handler) { } // Try deprecated event system else if (instance.$ !== undefined && instance.$.on !== undefined) { - instance.$.on(eventType, handler) + instance.$.on(eventType, handler); } }; From f76290cddee0b9c7ba1f180821927fd36aa1b4b0 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 20 Feb 2015 11:41:09 +0100 Subject: [PATCH 057/149] Made user results work in new embed. --- js/h5p-event-dispatcher.js | 4 ++-- js/h5p-x-api-event.js | 7 +++---- js/h5p-x-api.js | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index 204fda9..3df9495 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -11,7 +11,7 @@ H5P.Event = function(type, data) { }; H5P.EventDispatcher = (function () { - + /** * The base of the event system. * Inherit this class if you want your H5P to dispatch events. @@ -152,4 +152,4 @@ H5P.EventDispatcher = (function () { } return EventDispatcher; -})(); \ No newline at end of file +})(); diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 346bf64..5e1a025 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -79,7 +79,7 @@ H5P.XAPIEvent.prototype.getVerb = function(full) { H5P.XAPIEvent.prototype.setObject = function(instance) { if (instance.contentId) { this.data.statement.object = { - 'id': H5PIntegration.getContentUrl(instance.contentId), + 'id': H5P.contentDatas['cid-' + instance.contentId].url, 'objectType': 'Activity', 'extensions': { 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId @@ -98,10 +98,9 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { * Helper function to set the actor, email and name will be added automatically */ H5P.XAPIEvent.prototype.setActor = function() { - var user = H5PIntegration.getUser(); this.data.statement.actor = { - 'name': user.name, - 'mbox': 'mailto:' + user.mail, + 'name': H5P.user.name, + 'mbox': 'mailto:' + H5P.user.mail, 'objectType': 'Agent' }; }; diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index 37a593b..e4f4edb 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -3,7 +3,7 @@ var H5P = H5P || {}; // Create object where external code may register and listen for H5P Events H5P.externalDispatcher = new H5P.EventDispatcher(); -if (window.top !== window.self && window.top.H5P !== undefined && window.top.H5P.externalDispatcher !== undefined) { +if (H5P.isFramed && H5P.externalEmbed === false) { H5P.externalDispatcher.on('xAPI', window.top.H5P.externalDispatcher.trigger); } From 152b51f02a380e3caaa544cc41d3bdbf8255998d Mon Sep 17 00:00:00 2001 From: Thomas Marstrander Date: Fri, 20 Feb 2015 13:00:50 +0100 Subject: [PATCH 058/149] Added new font --- fonts/h5p.eot | Bin 5956 -> 4396 bytes fonts/h5p.svg | 26 ++++---------------------- fonts/h5p.ttf | Bin 5764 -> 4248 bytes fonts/h5p.woff | Bin 0 -> 4324 bytes styles/h5p.css | 12 ++++++------ 5 files changed, 10 insertions(+), 28 deletions(-) create mode 100644 fonts/h5p.woff diff --git a/fonts/h5p.eot b/fonts/h5p.eot index 9680263b5d3282bcb9f66238f410cba90a4604ec..b71c1c89c2063e98f8fed4cfe33539915dd2c74a 100644 GIT binary patch literal 4396 zcmeHLdu$xV8K3vuKJKyh-L0M2KKstTb4YT&+{*=$*ba$X5?j0+Xa%U45OT!{vEm>l zsI)1Fw?w0oKrEF|3RJXeQU7TxwJC}KAyHar5f!Bpl~607s!&m-7F6m>f3tgb>;SFQ zzq{I*`QG2mH#;-m>^hH-6A3~nVPxe)$x&)36PK=}8`tkq)1BOoYewi9600%;iPoQ6+`8Cn|ub8 zQz+N%+BZG>?kjKK4xujKQ+uZ8jH?82(?<5}J$&N_dv}ZjKSYT8eq;Ca4f3|TF}PR* z{CWd~NJRV#__u)fH})MobaATv1n}p9e{S#oUDN*eQV~MpF7W)m=|i)mo5p}wf%o1# zy>IuLn{xL9pMsCR+5K||(N8=H_``s>AoM7ags^$`OHjTZw)8KgLM^Q7qJ%;=9#{*hh}ZD>f$OvW{~K+Ae84%o*NmMOv`-84gLlhQ2H==kw3^zlN@BSF_ud9Fu#_|=0|9~oS|v8mZmoITD8b>>{#{MZH1ot_H|cT zLf$3g>P{)FtJZC=X9l-jTVtnDSBqC{S#P~;t0%R7QrRzC>$hA{gxVkm!RZO|EI-Dc zL3}z%|5<;P@@5uz(3MoK)}E_XYAC5nialghjoI2YroE;%I5qXvr_1HL2H7(PT_l@! z!bC2z-)72t2fDZBOH(8L{UcK)v;%%> zPC&#Jmrsuyf$?cGXEq%qr-WobBQBO%j;tpakgeo0ay7XQOO8;LuK4Nt5Uu9pbhwbu z#;HnX=s;ejv`UZAGFbj_y*fksCULnx3HStMqn=f$Ha?2ZAM_aabEQ%_=)WcCa`r(H! zoje)Fv77u{xa_%4iDNxKgyEFunPlj?v|jL1;5UXp8h?QBS5T!<*^ysB^E+ z>!Tq0i&04NwmWR@*eir z1&=+uFvE_st6|Lj6k99(esqpxV5;SHr6k?8@K2_i1^WFac1I%EYVtK6V`r9PNrGqu zUtjt{T1X{Pm+PA-H(yk+MDlB?U#p_ds&2|7TFCt(D)vjE-+9#06>XnfPC99WY~OEz65^54Cx1 zmSwF@=%{{Ryua4wc{Guj)*W81;Q3N1rJKJ`bm~3D8*V*x=+@&=-?HOMSz?dwOr$cI z6q}AGCN~Mq81>q`b*tL@J+I5+Odm}o66?FW*2NDid8Jl5ZWPye(fF?)f4q6*=uvHH zov^HQG#*W4(&-ElG)o?Vp+A#4xrzLVI*3HNHEe9e1-uwcKAS@nR0x|u;PrzDU^6gi zJ?%@B9;#78{{jlw0Ac)Miy?unYM>to`h(Cga%;XUfUl`#yVW3DVO*LqIn0VAL@$sN4S^sFsoR*m=wo%@Tx?_iA(gOcPzsM zWij-r_o7}LuF*7O1KS6%;0641#a7o=SqW`A*rp3GvO2|>&UM0Gth$P0r8p{eUP8ws z%F*;Y9mS5$4V{dkCzP?iZCS?or5d|3Q;u>y`UTITKVXb`N#h-F@#-R#mK0!nb|k@U z-{k^5cYT*q6;lv-B_dtBBf)Jq+UZgrbL@64BNDI8oOHR<>-BWpR*uwh9hC_L?6q!J zB!XdUft>|?FvCu}8kSuLvFsb^Z8l zVee2A6G~-eOifH0T*}GGkEa*SA3-7DK^J0^lgn^pYcf2m{(t7LOf~+uGoR5pe1<<} zUm;z@%zF>*rb#t~6Jiss)-l0pgSRp!b7qH(OiK`qd??61&!yyV3OsKbXkLhU+QzAv zKZi~)W0ts3a3rXfC<~iC#5iz^&EU&!G??NyAxFAUFm@bm8GFbv#^eQP%`HaI!{jCY zL-s^)u5Kh3n{RdL0L}HCJwW{|x{ckfq_DSdC$kUgy(4{PODbrlaMA7kG_1P~G{ari|nQHDO6f8vYrjejSOFsq9E#(nIL zE-4T k8O%S2X>t(l4WM054x_%9?1I!Jiun^!G(j@A>i_Wk8|tYvasU7T literal 5956 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 - - -{ - "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" -} - - +Generated by IcoMoon - + + @@ -37,6 +19,6 @@ - + \ No newline at end of file diff --git a/fonts/h5p.ttf b/fonts/h5p.ttf index c0ffaf53e54a700855bfb88cab9c7a6c48155c71..a9bc2fd7d17905a79d61db7f73637000b054d644 100644 GIT binary patch literal 4248 zcmeHKdyHIF89(oHU-P)TGjn&_-JRW;-I*%L`xNklgm~{ZZ|lSe=qPE*ch1Kzi>lGgE$V<4>2tTtJWn^x&_{0B`AfQKC!zE~@;twvohEtkSIAY zpDT>hMkPx#YAsEV@p`Sq^6Y5snytmY#owAXLGyacgfc*W^)@-#ono4|fs+BPoTF}*WzTTOySL>+h zYMMP@M2*_|)uz9?K0Gt?l_x8euMM-O47xOI`VE^mx9+SyTz$C;Ta4aCe##$Wzb1WT zEg?h2F&bKEdswYBMuSWRKGupuxkA0xs8%xRl*;pmZcYV*<2y&nJI98G#&(v6w)QLt zSLB3fy}b3h>$aNu&dt4B3gwyc!NKvFGWww)y&%BiiOXiE48zo{Swx2plT$*npA(N{ z5a0FW0mAz3M$cks z>)P>gW?Rp3)i%qUTPE7NwlA?NzC6PTI`RJdFPuCX#j%(CLU`<%Pl#iqFar0}K_5LQ zGFaY1UdQRm$RM=YFtjE5k*KFK)zLA!GV0tP@CPWWPv7NC%u9qqMzcg5_#}OeOX^Z3 z<^_XCVIUno36C(MKv~J$D#Cjymsz_^An#^>TlCq}i*xK4y9&y@PtbL?-4D*;3{|zQ zp3O;jE&hwCR+0Xoh0~D;wwipE$Jv?XSduUr;oq0JkQUP^w3Wst%FQ1YZjr)T8q{lO zbE=neOAEPQ#Ke9n^ji^k0?}IO0 zEzx3BM*AqlF#EhHCd>WDz8AMMw(VQJBOQK+ZQHApI;QWN8mxEtK20WPb(fzn`hiqR z>DC{T-FkQF`dbeky!BWtu$_cbw%Dz^lj&?W&1MtH=}kg2M*R+d-Kx$(-|w+G(?^oY zyN!dmhyrH{xjdqvBG?21B?u#c!@!`8OdwJFs74F<3n*X%MDdF+g#@~4p?t_3 zV38Z6QMB4xAQhGn4egaeV_1!Fyb5C*+uCq)8pBH>03cNiQsoVqlrfBb8Mc6pjb#s@ zBH)o!r5s@RJemi+pb|LXj5-VfA1yZmp(&%uLf~M%JViOJ+*qZ8b_{iP7CM}`CI7CH{d}!G-Dv zdSZhX_wznh6?+$p;s!2?N?LA0VjlgIStclpV@~}S_2Y7lWf&XUHiQi?V4o|ty1vRz zYBRwOU4)X=X~uNE8~Wn4RUA9TRq6ARI$nNqGN>tiXfb2U~k~x9L1!nFA9;GU-!19tMJ*O+l9WU1HQ66`l zPAx5o;xIQ;>Gu15op6*ZbwbBv5&?Uq*AvOmZ7p!JU=GIZjHjX5a}mpd@qVtQmI8Bt z-)K8y+&Erf5`M#|V4AI}ueK64{NMu}m`qFsz1SXIy-?7dwh=dJGsjNAtnwgZSxIg9 zF)=seP)dKMV+!U~Oegv}f9D~)!9D&Ua)xh)Im7uMlQDE=Qn)sABiI>e3bk?kLugFu zG8*PDAO}(6QZ#7<5*YOLyQR#@gq+@?;y63hjvY$nWL)i-Hn^12(;rPQT0e$BNP{fc zrl(inM%Q$7SN;FaKRea<-|l=y7w{YYgngOx5VP)mw3nvT2yTc?wAR1^XAIuXn9Q9W z5wdMY5DF0^`y7{&zb^2-ZlDuFoS+??iWBFI(@U8xE)*OIsx2zQVGl44+-7t5vztw( z_)W->E)*`aMbxOO=y-qyt+g&l=I8jZDF3ioP{oDsmqz>awf{&n$xgq|bXt!@lo5d7 zI2(su#1eJngA&k>7fc+_ADp)?i*z=5-Z)kO|*tM6NLdVrs?9DdOme zx~Mf`(7Y(u&<9bEquzx&jM^*Lu+`A=2H+g3p_}mq)I+GRh8i5aVfsj!+(^Df{zz|U z!)zb>yErUniMuhjLry-bVhKbGlCoIm6IfV9NvI|_(s786o#3Fph8{p%={{R43Cqn=L literal 5764 zcmeHLdyJIT6~E^`zi%Gj%#DXp8V?1?aa@Y7y{=tS>#?l_DJM43q2v)Dh+}jvukUcMqRBdFm9#z|p&RyEdKrWBw~03^##-7y&BbsDxvn-TsVQ z^31ybWsDwXzY^c!C)hF&EC$>ZM7~lgR;hI{4O1<*mZZ{Z{wqRi@uZLvg_hzKf5qsg zYGd2F0Ve1OdrlnY&q5A8Y{_XJ>a}5BX;pd2)J>_9F171zy!m>bKLiFUZiQxJ+uCJB z%hqmdEZa<*=$0STN~+TxR3ZNCz`Cu?=GJusbM?y6(b+rdkJaC(GfsE0r^TcEWmaaZ z8CzO$TdM>juM51L8|LkNf!po#a%HKm@^%?R5KAB`;?bFCBBqE+BUcX%Zn+4OC||T? zFukO>IB@`*l%ih{37UPqIDCFDQSbS~#nOd?Jw!c&7nc4y6ZG{j?OM5Mq+}^PR>Siq@dsk4Y1=<@t?CU*2n5>4ZE6M$8Kb|v%A?hAvwK}a`ij} zH%L{k6V$F$sfaq@(BjfYIiS4BWeQYp*M_M)NcncF(Lxm83P7e=4#fhde?fAAb1a(W6#xi`Xw@j6Z)4y@gsR z2i!_>6+~?EwVV|-46s>InN?O7jVe!&E^4T+2#ZLNNUO9WylMJ?lcX<^ny)vUYUm9T6QlGN z)HWx{b7*Z&l5+Rh=scy(4xCn+!U~f3#3iFp8su#0kK9B2eL9)s&!4=3_+jFohjK$5 zJ8;o~&N*&IE)%EOv_=N&n#bk8K;0S+%m)e&*|hBUD4{#11tz66E!fd%HZ3zFblu61 zh#Q(6Ao?NAZe~pKUUsVp_^H#nV-%jA&e?*>R#PRLDe%>p;1`j=336UDJULbP&U_Qv zJI{L8MI<~}7l~=EwC2;`Pl~w-rP-1Lq$I`)@a$7aSH%9CjRX zUNn{FJSYX2O1qM+G%G23mNjz95@6ha}8{Hsf%^69`X7gf_mG1K;|gB#>%>53@4&Prk2Uou!l-s*NU|gR@DflW(ZDfUTvu(1cTvl z?^ri0LNEE2ID@W4WGOT<}?6Ct`6#bvP3f zkANg617C?CWjr4pj+BNJjMGLQm53)~Jd;gFbr6j!8%4m7fcarPVNzWQ&H2N5P%ag8S85i zvkr>)F6fOz79N}mVp^ih$1>Vz5ltteZnLp;!YYy|miBFI#1AXMvV@Fa2gXS3z+l1y z86!hlZpV;V6xk>}BLM&#hpH{4NhDMLZ@{d(25{u~&xr?Ij=cf?C(QCsXV_`D5G09k z6ocgag-suk&mntsNr<}yw9niH63A~lw}A5ctvwJ@uJwc8taVS|E~peMtxxLiU@ zq8}oZeB(w$b1wUZ#<9En3IKRivU-+dW1q~_XMe&Cq*Jt#1SZO&H3GDq)QXU=KpSPz z4Q$-_|J)dI6FgNBjgCp}({x!CoA>AT)e3J_Zc`sr~rG;>V8wVni9pzb{+ZH*&8+3Wdm~?!5;>;oBTV z>o(!->`L2^2tz_z+&4$nHpEPas8Su`_PYnY3F%(Nzsefuz%w;YUt;HOyp%x-4r{Sh zcso6Pm9@b9ohj?ehlG!OZO@dehV01Hyz!|$^Tz4K6f#&}4#Ks|sVQ3yx&gF5eH^S= zUK#t-uG$@6NL=`L7L^!a?9~@Sw>(!PGq>M*?AM?00gnBK#F)Z28JNd? z_Z_smJNM470__QKum`*x_>Yj6I$kU%9)Ur;*+_-WO@ E0f&Tg>;M1& diff --git a/fonts/h5p.woff b/fonts/h5p.woff new file mode 100644 index 0000000000000000000000000000000000000000..66394c10c29d618c76bc5a15fffdc7f78a306179 GIT binary patch literal 4324 zcmeHKdyHIF89(27&b{-zyEAil+wRWp%)1B3)oP=Y~BFd-7aD3O>bMBJ&rbMNf3TOcO> z?alqp`QG0--?`_U?|$K;$w@{GPwI6R4f^9DZSm#LMVAg%7^4WdpR(Yxkk+!F!sq*gF>9-MwwHaox-f96b}Z>c*2iq8CL zh4gP}*#*L&Ax6+mE{TO0Bj*O8KZyU|c=POCA^2T(?o={9mFSt9rk|ya)AZ0yJAO$U zcQB&+*|Xwaewr1alTYwWx|{p8LcTag^-7MidJSbpMQymu3;gKt)muv4^{s0!cci*q zCG}U8bS_`JwVoT;a&?WLMmt=-bkjQLc~?Jb%%j?U-dVTl(lX2jFe^dF*^}ZZe**dG zwBNu&ylQ2X2N6s)Q>e8UYSkJbQ_b-Etf_TdyUO-g)dr@gzx;Tma{B;(!jcPY+ppiW zsrl9FgVh(Sh{fp+_EYfy|26AoYZ&V@j?~q_b5&TOXw1Vv%!~xdI{js zJA%vo3aL=xD3J#-L9Yl!QPOh1RP31_j7@wGhe`)6qeu3Ax-Ytqj7e{tO`(f6D76qkG@`-({LGmE)oK{kS~FMB?f zGHJAx`bHA=iwd?#aSi#kVYGSOMIvIP+9%_3pOWUy2cxEH+>=XLCta|jyb)1fQ+Okh z$BgTm$C{6w#jq#lYsBOvKgB3*-RUYp}MD^n(J z?wRbXwRs+;QZuH*E0jE6X{}B352;SGt9lf;S+8c;z4%RwLk75o|-t8%|Cec)#o;*=`-F4ili zNl@kbDoJYVZ*MQQxd|mAk>2r~n~92u$c^PlitLcj1vMjy6;YQW$qXuuC{D9zN=Rvd zSiHye2hxd-m^LI7zJYi#3fK3oiuXl?SMac^*t=Mis2|0vQ;}#=VH`aZIWCDOFs7ah zdT_bMvz+&D?Z<|fh%Yo>SzF>!YHF8#(;UFZI25(@gi54H-buT_=?)fNYcSPL~k1g6W4wh zwns-79CT(J8eEGTP$Qi$IX{NJf-~eO=juH44STQBA@-d3A%7&e zS2wT=?YFvkffjl{xIq0phK@b0l_-C6)|era zHoIrh3>h=qqGxxvXgWP>GG;K83<8D5avE`wOTgg!Cm#81a>GAZRN7s|ej9t}wxvC) z2Ky&v|E|IZHo-p{Q>!gQw*GuuQropH!2>gRo>!~se!y|S9e@Eqms-tN!pm#G^8hQi z{5ilOz{`Oj7ymHbY=~XYzRCVbxAFnLm;YTJmcLhDR4?fb8h^FHT-qqc+`wXCq)Ug9 zV2`){@QCS_12OR4zDU z9{LRN&}WE;K0{o?w!dW?`fRWo+s`h;pUaJGj(wi(V|!T-ThGeKLLh6@_J;_&1WLQu wO>8%tfpTa#85kUZ$u3Cu25x590rb~`cMUs)_CmH3T2lb)yd2KowjcZa2h12PrvLx| literal 0 HcmV?d00001 diff --git a/styles/h5p.css b/styles/h5p.css index fff975b..dfb92db 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -2,12 +2,12 @@ /* Custom H5P font to use for icons. */ @font-face { - font-family: 'H5P'; - src: url('../fonts/h5p.eot?ver=1.2.1'); - src: url('../fonts/h5p.eot?#iefix&ver=1.2.1') format('embedded-opentype'), - url('data:application/font-woff;base64,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') format('woff'), - url('../fonts/h5p.ttf?ver=1.2.1') format('truetype'), - url('../fonts/h5p.svg?ver=1.2.1#h5pregular') format('svg'); + font-family: 'h5p'; + src:url('../fonts/h5p.eot?-9qymee'); + src:url('../fonts/h5p.eot?#iefix-9qymee') format('embedded-opentype'), + url('../fonts/h5p.woff?-9qymee') format('woff'), + url('../fonts/h5p.ttf?-9qymee') format('truetype'), + url('../fonts/h5p.svg?-9qymee#h5p') format('svg'); font-weight: normal; font-style: normal; } From 93cd6eea2811da017b6cc8ef5dab3761bcb9a73a Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 20 Feb 2015 13:17:41 +0100 Subject: [PATCH 059/149] Removed empty file. --- js/h5p-api.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 js/h5p-api.js diff --git a/js/h5p-api.js b/js/h5p-api.js deleted file mode 100644 index e69de29..0000000 From d6e69e63ee69fcaef0fd2712e0d263d9d641d5b4 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Fri, 20 Feb 2015 15:34:38 +0100 Subject: [PATCH 060/149] Use this instead of self so that we're compatible with jsdoc --- js/h5p-event-dispatcher.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index 204fda9..f403ea2 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -36,7 +36,7 @@ H5P.EventDispatcher = (function () { * @param {Function} listener - Event listener * @param {Function} thisArg - Optionally specify the this value when calling listener. */ - self.on = function (type, listener, thisArg) { + this.on = function (type, listener, thisArg) { if (thisArg === undefined) { thisArg = self; } @@ -66,7 +66,7 @@ H5P.EventDispatcher = (function () { * @param {Function} listener - Event listener * @param {Function} thisArg - Optionally specify the this value when calling listener. */ - self.once = function (type, listener, thisArg) { + this.once = function (type, listener, thisArg) { if (thisArg === undefined) { thisArg = self; } @@ -91,7 +91,7 @@ H5P.EventDispatcher = (function () { * @param {String} type - Event type * @param {Function} listener - Event listener */ - self.off = function (type, listener) { + this.off = function (type, listener) { if (listener !== undefined && !(listener instanceof Function)) { throw TypeError('listener must be a function'); } @@ -131,7 +131,7 @@ H5P.EventDispatcher = (function () { * Custom event data(used when event type as string is used as first * argument */ - self.trigger = function (event, eventData) { + this.trigger = function (event, eventData) { if (event === undefined) { return; } From 0c5aacfa241acd02e15145cbe8434a22110e66b8 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 24 Feb 2015 16:02:14 +0100 Subject: [PATCH 061/149] Fixed old fullscreen exit in iframe. --- js/h5p.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 9d40789..3042977 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -290,9 +290,11 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) { H5P.isFullscreen = true; H5P.exitFullScreen = function () { window.top.H5P.exitFullScreen(); + }; + H5P.on(instance, 'exitFullScreen', function () { H5P.isFullscreen = false; H5P.exitFullScreen = undefined; - }; + }); return; } From 1861f696c1ac8d4157cdb2c5c0b8a2530c5318e3 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 25 Feb 2015 12:10:07 +0100 Subject: [PATCH 062/149] Added new embed design. --- js/h5p.js | 55 +++++++++++++++++++++-- styles/h5p.css | 119 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 141 insertions(+), 33 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 3042977..5c26b6e 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -83,7 +83,10 @@ H5P.init = function () { if (contentData.embedCode !== undefined) { // Display embed button H5P.jQuery('
      • ' + H5P.t('embed') + '
      • ').appendTo($actions).click(function () { - H5P.openEmbedDialog($actions, contentData.embedCode); + H5P.openEmbedDialog($actions, contentData.embedCode, contentData.resizeCode, { + width: $container.width(), + height: $container.height() + }); }); } if (contentData.showH5PIconInActionBar) { @@ -713,12 +716,56 @@ H5P.findCopyrights = function (info, parameters, contentId) { * @param {string} embed code. * @returns {undefined} */ -H5P.openEmbedDialog = function ($element, embedCode) { - var dialog = new H5P.Dialog('embed', H5P.t('embed'), '', $element); +H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { + var dialog = new H5P.Dialog('embed', H5P.t('embed'), '' + H5P.t('size') + ': × px
        ' + H5P.t('showAdvanced') + '

        ' + H5P.t('advancedHelp') + '

        ', $element); // Selecting embed code when dialog is opened H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { - $dialog.find('.h5p-embed-code-container').select(); + // Handle changing of width/height + var $w = $dialog.find('.h5p-embed-size:eq(0)'); + var $h = $dialog.find('.h5p-embed-size:eq(1)'); + var getNum = function ($e, d) { + var num = parseFloat($e.val()); + if (isNaN(num)) { + return d; + } + return Math.ceil(num); + }; + var updateEmbed = function () { + $dialog.find('.h5p-embed-code-container:first').val(embedCode.replace(':w', getNum($w, size.width)).replace(':h', getNum($h, size.height))); + }; + + var w = size.width; + $w.change(function () { + // Keep aspect ratio when changing width + var newW = getNum($w, size.width); + $h.val(Math.ceil(newW * (getNum($h, size.height) / w))); + w = newW; + updateEmbed(); + }); + $h.change(updateEmbed); + updateEmbed(); + + // Select text and expand textareas + $dialog.find('.h5p-embed-code-container').focus(function () { + H5P.jQuery(this).select().css('height', this.scrollHeight + 'px'); + }).blur(function () { + H5P.jQuery(this).css('height', ''); + }).select(); + + // Expand advanced embed + $dialog.find('.h5p-expander').click(function () { + var $expander = H5P.jQuery(this); + var $content = $expander.next(); + if ($content.is(':visible')) { + $expander.removeClass('h5p-open').text(H5P.t('showAdvanced')); + $content.hide(); + } + else { + $expander.addClass('h5p-open').text(H5P.t('hideAdvanced')); + $content.show(); + } + }); }); dialog.open(); diff --git a/styles/h5p.css b/styles/h5p.css index fff975b..57f40cc 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -220,47 +220,80 @@ div.h5p-fullscreen { -moz-transition: opacity 0.2s; -o-transition: opacity 0.2s; transition: opacity 0.2s; + background:#000; + background:rgba(0,0,0,0.75); } .h5p-popup-dialog.h5p-open { opacity: 1; } .h5p-popup-dialog .h5p-inner { box-sizing: border-box; - box-shadow: 0 0 2em #000; + -moz-box-sizing: border-box; background: #fff; - height: 90%; + height: 100%; max-height: 100%; - padding: 0.75em; position: relative; } .h5p-popup-dialog .h5p-inner > h2 { - font-size: 1.5em; - margin: 0.25em 0; position: absolute; + box-sizing: border-box; + -moz-box-sizing: border-box; + width: 100%; + margin: 0; + background: #eee; + display: block; + color: #656565; + font-size: 1.25em; + padding: 0.325em 0.5em 0.25em; line-height: 1.25em; - padding: 0; + border-bottom: 1px solid #ccc; } .h5p-embed-dialog .h5p-inner { - width: 50%; - left: 25%; - top: 25%; - height: auto; + width: 300px; + left: 50%; + top: 50%; + height: 180px; + margin: -90px 0 0 -150px; } -.h5p-embed-dialog .h5p-embed-code-container { - width: 90%; - padding: .3em; - min-height: 10em; +.h5p-embed-dialog .h5p-embed-code-container, +.h5p-embed-size { resize: none; outline: none; + width: 100%; + padding: 0.375em 0.5em 0.25em; + margin: 0; + overflow: hidden; + border: 1px solid #ccc; + box-shadow: 0 1px 2px 0 #d0d0d0 inset; + font-size: 0.875em; + letter-spacing: 0.065em; + font-family: sans-serif; + white-space: pre; + line-height: 1.5em; + height: 2.0714em; + background: #f5f5f5; + box-sizing: border-box; + -moz-box-sizing: border-box; +} +.h5p-embed-dialog .h5p-embed-code-container:focus { + height: 5em; +} +.h5p-embed-size { + width: 3.5em; + text-align: right; + margin: 0.5em 0; + line-height: 2em; } .h5p-popup-dialog .h5p-scroll-content { - border-top: 2.75em solid transparent; + border-top: 2.25em solid transparent; + padding: 1em; box-sizing: border-box; -moz-box-sizing: border-box; height: 100%; overflow: auto; overflow-x: hidden; overflow-y: auto; + color: #555555; } .h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar { width: 8px; @@ -281,22 +314,21 @@ div.h5p-fullscreen { content: "\e894"; font-size: 2em; position: absolute; - right: 0.5em; - top: 0.5em; + right: 0; + top: 0; + width: 1.125em; + height: 1.125em; + line-height: 1.125em; + color: #656565; cursor: pointer; - -webkit-transition: -webkit-transform 0.2s; - -moz-transition: -moz-transform 0.2s; - -o-transition: -o-transform 0.2s; - transition: transform 0.2s; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; + text-indent: -0.065em; } -.h5p-popup-dialog .h5p-close:hover:after { - -webkit-transform: scale(1.1, 1.1); - -moz-transform: scale(1.1, 1.1); - -ms-transform: scale(1.1, 1.1); - -o-transform: scale(1.1, 1.1); - transform: scale(1.1, 1.1); +.h5p-popup-dialog .h5p-close:hover:after, +.h5p-popup-dialog .h5p-close:focus:after { + color: #454545; +} +.h5p-popup-dialog .h5p-close:active:after { + color: #252525; } .h5p-poopup-dialog h2 { margin: 0.25em 0 0.5em; @@ -317,6 +349,35 @@ div.h5p-fullscreen { .h5p-popup-dialog dd { margin: 0; } +.h5p-expander { + cursor: default; + font-size: 1.125em; + outline: none; + margin: 0.5em 0 0; +} +.h5p-expander:before { + content: "+"; + width: 1em; + display: inline-block; + font-weight: bold; +} +.h5p-expander.h5p-open:before { + content: "-"; + text-indent: 0.125em; +} +.h5p-expander:hover, +.h5p-expander:focus { + color: #303030; +} +.h5p-expander:active { + color: #202020; +} +.h5p-expander-content { + display: none; +} +.h5p-expander-content p { + margin: 0.5em 0; +} .h5p-content-copyrights { border-left: 0.25em solid #d0d0d0; margin-left: 0.25em; From fabde2f1cea01544985f3cfaf92d27d8a7f17456 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 25 Feb 2015 13:05:45 +0100 Subject: [PATCH 063/149] Automatic positioning. --- js/h5p.js | 13 +++++++++++++ styles/h5p.css | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 5c26b6e..c44a55b 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -721,6 +721,16 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { // Selecting embed code when dialog is opened H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { + var $inner = $dialog.find('.h5p-inner'); + var positionInner = function () { + $inner.css('height', ''); + var h = $inner.height(); + if (Math.floor($dialog.height()) === h) { + $inner.css('height', '100%'); + } + $inner.css('marginTop', '-' + (h / 2) + 'px'); + }; + // Handle changing of width/height var $w = $dialog.find('.h5p-embed-size:eq(0)'); var $h = $dialog.find('.h5p-embed-size:eq(1)'); @@ -749,8 +759,10 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { // Select text and expand textareas $dialog.find('.h5p-embed-code-container').focus(function () { H5P.jQuery(this).select().css('height', this.scrollHeight + 'px'); + positionInner(); }).blur(function () { H5P.jQuery(this).css('height', ''); + positionInner(); }).select(); // Expand advanced embed @@ -765,6 +777,7 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { $expander.addClass('h5p-open').text(H5P.t('hideAdvanced')); $content.show(); } + positionInner(); }); }); diff --git a/styles/h5p.css b/styles/h5p.css index 57f40cc..1925892 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -252,8 +252,8 @@ div.h5p-fullscreen { width: 300px; left: 50%; top: 50%; - height: 180px; - margin: -90px 0 0 -150px; + height: auto; + margin: 0 0 0 -150px; } .h5p-embed-dialog .h5p-embed-code-container, .h5p-embed-size { From 3463a3c67165f7a445d094d0aa8f12891f9b8ab8 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Thu, 26 Feb 2015 16:16:20 +0100 Subject: [PATCH 064/149] Added support for multiple workers during content upgrade. --- js/h5p-content-upgrade-process.js | 275 ++++++++++++++++ js/h5p-content-upgrade-worker.js | 76 +++++ js/h5p-content-upgrade.js | 528 +++++++++++++----------------- js/h5p-version.js | 27 ++ 4 files changed, 599 insertions(+), 307 deletions(-) create mode 100644 js/h5p-content-upgrade-process.js create mode 100644 js/h5p-content-upgrade-worker.js create mode 100644 js/h5p-version.js diff --git a/js/h5p-content-upgrade-process.js b/js/h5p-content-upgrade-process.js new file mode 100644 index 0000000..a7cc2cc --- /dev/null +++ b/js/h5p-content-upgrade-process.js @@ -0,0 +1,275 @@ +/*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) { + 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) { + self.processField(subField, params[subField.name], 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); diff --git a/js/h5p-content-upgrade-worker.js b/js/h5p-content-upgrade-worker.js new file mode 100644 index 0000000..8b5f4f5 --- /dev/null +++ b/js/h5p-content-upgrade-worker.js @@ -0,0 +1,76 @@ +var H5P = H5P || {}; +importScripts('/wp-content/plugins/h5p/h5p-php-library/js/h5p-version.js'); +importScripts('/wp-content/plugins/h5p/h5p-php-library/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 + }); + + 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); + } +}; + + +// if (library.upgradesScript) { +// self.loadScript(library.upgradesScript, function (err) { +// if (err) { +// err = info.errorScript.replace('%lib', name + ' ' + version); +// } +// next(err, library); +// }); +// } +// else { +// next(null, library); +// } diff --git a/js/h5p-content-upgrade.js b/js/h5p-content-upgrade.js index ff3756f..e384f97 100644 --- a/js/h5p-content-upgrade.js +++ b/js/h5p-content-upgrade.js @@ -1,8 +1,7 @@ /*jshint -W083 */ -var H5PUpgrades = H5PUpgrades || {}; -(function ($) { - var info, $container, librariesCache = {}; +(function ($, Version) { + var info, $container, librariesCache = {}, scriptsCache = {}; // Initialize $(document).ready(function () { @@ -43,87 +42,6 @@ var H5PUpgrades = H5PUpgrades || {}; } }; - /** - * Private. Helps process each property on the given object asynchronously in serial order. - * - * @param {Object} obj - * @param {Function} process - * @param {Function} finished - */ - var asyncSerial = function (obj, process, finished) { - var id, isArray = obj instanceof Array; - - // Keep track of each property that belongs to this object. - if (!isArray) { - var ids = []; - for (id in obj) { - if (obj.hasOwnProperty(id)) { - ids.push(id); - } - } - } - - var i = -1; // Keeps track of the current property - - /** - * Private. Process the next property - */ - var next = function () { - id = isArray ? i : ids[i]; - process(id, obj[id], check); - }; - - /** - * Private. Check if we're done or have an error. - * - * @param {String} err - */ - var check = function (err) { - // We need to use a real async function in order for the stack to clear. - setTimeout(function () { - i++; - if (i === (isArray ? obj.length : ids.length) || (err !== undefined && err !== null)) { - finished(err); - } - else { - next(); - } - }, 0); - }; - - check(); // Start - }; - - /** - * Make it easy to keep track of version details. - * - * @param {String} version - * @param {Number} libraryId - * @returns {_L1.Version} - */ - function Version(version, libraryId) { - if (libraryId !== undefined) { - version = info.versions[libraryId]; - - // Public - this.libraryId = libraryId; - } - var versionSplit = version.split('.', 3); - - // Public - this.major = versionSplit[0]; - this.minor = versionSplit[1]; - - /** - * Public. Custom string for this object. - * - * @returns {String} - */ - this.toString = function () { - return version; - }; - } - /** * Displays a throbber in the status field. * @@ -154,18 +72,84 @@ var H5PUpgrades = H5PUpgrades || {}; var self = this; // Get selected version - self.version = new Version(null, libraryId); + self.version = new Version(info.versions[libraryId]); + self.version.libraryId = libraryId; // Create throbber with loading text and progress self.throbber = new Throbber(info.inProgress.replace('%ver', self.version)); - // Get the next batch - self.nextBatch({ - libraryId: libraryId, - token: info.token - }); +self.started = new Date().getTime(); +self.io = 0; + + // Track number of working + self.working = 0; + + var start = function () { + // Get the next batch + self.nextBatch({ + libraryId: libraryId, + token: info.token + }); + }; + + if (window.Worker !== undefined) { + // Prepare our workers + self.initWorkers(); + start(); + } + else { + // No workers, do the job our self + self.loadScript('/wp-content/plugins/h5p/h5p-php-library/js/h5p-content-upgrade-process.js', 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('/wp-content/plugins/h5p/h5p-php-library/js/h5p-content-upgrade-worker.js'); + self.workers[i].onmessage = function (event) { + if (event.data.action !== undefined && messageHandlers[event.data.action]) { + messageHandlers[event.data.action].call(this, event.data); + } + }; + } + }; + /** * Get the next batch and start processing it. * @@ -174,12 +158,21 @@ var H5PUpgrades = H5PUpgrades || {}; ContentUpgrade.prototype.nextBatch = function (outData) { var self = this; +var start = new Date().getTime(); $.post(info.infoUrl, outData, function (inData) { +self.io += new Date().getTime() - start; if (!(inData instanceof Object)) { // Print errors from backend return self.setStatus(inData); } if (inData.left === 0) { + var total = new Date().getTime() - self.started; + console.log('Upgrade took ' + total + 'ms'); + console.log((self.io/(total/100)) + ' % of the time went to IO (' + self.io + 'ms)'); + + // Terminate workers + self.terminate(); + // Nothing left to process return self.setStatus(info.done); } @@ -208,90 +201,125 @@ var H5PUpgrades = H5PUpgrades || {}; */ ContentUpgrade.prototype.processBatch = function (parameters) { var self = this; - var upgraded = {}; // Track upgraded params - var current = 0; // Track progress - asyncSerial(parameters, function (id, params, next) { + // Track upgraded params + self.upgraded = {}; - try { - // Make params possible to work with - params = JSON.parse(params); - if (!(params instanceof Object)) { - throw true; - } + // Track current batch + self.parameters = parameters; + + // Create id mapping + self.ids = []; + for (var id in parameters) { + if (parameters.hasOwnProperty(id)) { + self.ids.push(id); } - catch (event) { - return next(info.errorContent.replace('%id', id) + ' ' + info.errorParamsBroken); + } + + // Keep track of current content + self.current = -1; + + if (self.workers !== undefined) { + // Assign each worker content to upgrade + for (var i = 0; i < self.workers.length; i++) { + self.assignWork(self.workers[i]); } + } + else { - // Upgrade this content. - self.upgrade(info.library.name, new Version(info.library.version), self.version, params, function (err, params) { - if (err) { - return next(info.errorContent.replace('%id', id) + ' ' + err); - } - - upgraded[id] = JSON.stringify(params); - - current++; - self.throbber.setProgress(Math.round((info.total - self.left + current) / (info.total / 100)) + ' %'); - next(); - }); - - }, function (err) { - // Finished with all parameters that came in - if (err) { - return self.setStatus('

        ' + info.error + '
        ' + err + '

        '); - } - - // Save upgraded content and get next round of data to process - self.nextBatch({ - libraryId: self.version.libraryId, - token: self.token, - params: JSON.stringify(upgraded) - }); - }); + self.assignWork(); + } }; /** - * Upgade the given content. * - * @param {String} name - * @param {Version} oldVersion - * @param {Version} newVersion - * @param {Object} params - * @param {Function} next - * @returns {undefined} */ - ContentUpgrade.prototype.upgrade = function (name, oldVersion, newVersion, params, next) { + ContentUpgrade.prototype.assignWork = function (worker) { var self = this; - // Load library details and upgrade routines - self.loadLibrary(name, newVersion, function (err, library) { - if (err) { - return next(err); - } + var id = self.ids[self.current + 1]; + if (id === undefined) { + return false; // Out of work + } + self.current++; + self.working++; - // Run upgrade routines on params - self.processParams(library, oldVersion, newVersion, params, function (err, params) { + if (worker) { + worker.postMessage({ + action: 'newJob', + id: id, + name: info.library.name, + oldVersion: info.library.version, + newVersion: self.version.toString(), + params: self.parameters[id] + }); + } + else { + new H5P.ContentUpgradeProcess(info.library.name, new Version(info.library.version), self.version, self.parameters[id], id, function loadLibrary(name, version, next) { + self.loadLibrary(name, version, function (err, library) { + if (library.upgradesScript) { + self.loadScript(library.upgradesScript, function (err) { + if (err) { + err = info.errorScript.replace('%lib', name + ' ' + version); + } + next(err, library); + }); + } + else { + next(null, library); + } + }); + + }, function done(err, result) { if (err) { - return next(err); + self.printError(err); + return ; } - // Check if any of the sub-libraries need upgrading - asyncSerial(library.semantics, function (index, field, next) { - self.processField(field, params[field.name], function (err, upgradedParams) { - if (upgradedParams) { - params[field.name] = upgradedParams; - } - next(err); - }); - }, function (err) { - next(err, params); - }); + self.workDone(id, result); }); - }); + } }; + /** + * + */ + ContentUpgrade.prototype.workDone = function (id, result, worker) { + var self = this; + + self.working--; + self.upgraded[id] = result; + + // Update progress message + self.throbber.setProgress(Math.round((info.total - self.left + self.current) / (info.total / 100)) + ' %'); + + // Assign next job + if (self.assignWork(worker) === false && self.working === 0) { + // All workers have finsihed. + self.nextBatch({ + libraryId: self.version.libraryId, + token: self.token, + params: JSON.stringify(self.upgraded) + }); + } + }; + + /** + * + */ + ContentUpgrade.prototype.terminate = function () { + var self = this; + + if (self.workers) { + // Stop all workers + for (var i = 0; i < self.workers.length; i++) { + self.workers[i].terminate(); + } + } + }; + + var librariesLoadedCallbacks = {}; + /** * Load library data needed for content upgrade. * @@ -303,32 +331,42 @@ var H5PUpgrades = H5PUpgrades || {}; var self = this; var key = name + '/' + version.major + '/' + version.minor; - if (librariesCache[key] !== undefined) { + + if (librariesCache[key] === true) { + // Library is being loaded, que callback + if (librariesLoadedCallbacks[key] === undefined) { + librariesLoadedCallbacks[key] = [next]; + return; + } + librariesLoadedCallbacks[key].push(next); + return; + } + else if (librariesCache[key] !== undefined) { // Library has been loaded before. Return cache. next(null, librariesCache[key]); return; } +var start = new Date().getTime(); + librariesCache[key] = true; $.ajax({ dataType: 'json', cache: true, url: info.libraryBaseUrl + '/' + key }).fail(function () { +self.io += new Date().getTime() - start; next(info.errorData.replace('%lib', name + ' ' + version)); }).done(function (library) { +self.io += new Date().getTime() - start; librariesCache[key] = library; + next(null, library); - if (library.upgradesScript) { - self.loadScript(library.upgradesScript, function (err) { - if (err) { - err = info.errorScript.replace('%lib', name + ' ' + version); - } - next(err, library); - }); - } - else { - next(null, library); + if (librariesLoadedCallbacks[key] !== undefined) { + for (var i = 0; i < librariesLoadedCallbacks[key].length; i++) { + librariesLoadedCallbacks[key][i](null, library); + } } + delete librariesLoadedCallbacks[key]; }); }; @@ -339,162 +377,38 @@ var H5PUpgrades = H5PUpgrades || {}; * @param {Function} next */ ContentUpgrade.prototype.loadScript = function (url, next) { + if (scriptsCache[url] !== undefined) { + next(); + return; + } + +var start = new Date().getTime(); $.ajax({ dataType: 'script', cache: true, url: url }).fail(function () { +self.io += new Date().getTime() - start; next(true); }).done(function () { + scriptsCache[url] = true; +self.io += new Date().getTime() - start; next(); }); }; /** - * Run upgrade hooks on params. * - * @param {Object} library - * @param {Version} oldVersion - * @param {Version} newVersion - * @param {Object} params - * @param {Function} next */ - ContentUpgrade.prototype.processParams = function (library, oldVersion, newVersion, params, next) { - if (H5PUpgrades[library.name] === undefined) { - if (library.upgradesScript) { - // Upgrades script should be loaded so the upgrades should be here. - return next(info.errorScript.replace('%lib', library.name + ' ' + newVersion)); - } - - // No upgrades script. Move on - return next(null, params); + ContentUpgrade.prototype.printError = function (error) { + if (error.type === 'errorParamsBroken') { + error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken; // TODO: Translate! + } + else if (error.type === 'scriptMissing') { + error.err = info.errorScript.replace('%lib', error.library); } - // 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); - }); + self.setStatus('

        ' + info.error + '
        ' + error.err + '

        '); }; - /** - * 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; - - if (params === undefined) { - return next(); - } - - switch (field.type) { - case 'library': - if (params.library === undefined || params.params === undefined) { - return next(); - } - - // Look for available upgrades - var usedLib = params.library.split(' ', 2); - for (var i = 0; i < field.options.length; i++) { - var availableLib = field.options[i].split(' ', 2); - if (availableLib[0] === usedLib[0]) { - if (availableLib[1] === usedLib[1]) { - return next(); // Same version - } - - // We have different versions - var usedVer = new Version(usedLib[1]); - var availableVer = new Version(availableLib[1]); - if (usedVer.major > availableVer.major || (usedVer.major === availableVer.major && usedVer.minor >= availableVer.minor)) { - return next(); // Larger or same version that's available - } - - // A newer version is available, upgrade params - return self.upgrade(availableLib[0], usedVer, availableVer, params.params, function (err, upgraded) { - if (!err) { - params.library = availableLib[0] + ' ' + availableVer.major + '.' + availableVer.minor; - params.params = upgraded; - } - next(err, params); - }); - } - } - next(); - break; - - case 'group': - if (field.fields.length === 1) { - // Single field to process, wrapper will be skipped - self.processField(field.fields[0], params, function (err, upgradedParams) { - if (upgradedParams) { - params = upgradedParams; - } - next(err, params); - }); - } - else { - // Go through all fields in the group - asyncSerial(field.fields, function (index, subField, next) { - self.processField(subField, params[subField.name], function (err, upgradedParams) { - if (upgradedParams) { - params[subField.name] = upgradedParams; - } - next(err); - }); - }, function (err) { - next(err, params); - }); - } - break; - - case 'list': - // Go trough all params in the list - asyncSerial(params, function (index, subParams, next) { - self.processField(field.field, subParams, function (err, upgradedParams) { - if (upgradedParams) { - params[index] = upgradedParams; - } - next(err); - }); - }, function (err) { - next(err, params); - }); - break; - - default: - next(); - } - }; - -})(H5P.jQuery); +})(H5P.jQuery, H5P.Version); diff --git a/js/h5p-version.js b/js/h5p-version.js new file mode 100644 index 0000000..78275b1 --- /dev/null +++ b/js/h5p-version.js @@ -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; +})(); From 037e26f42ba8dc3c2c99565125fbfd55164dd403 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 08:57:02 +0100 Subject: [PATCH 065/149] Space support. --- js/h5p.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index c44a55b..15a9f97 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -766,7 +766,7 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { }).select(); // Expand advanced embed - $dialog.find('.h5p-expander').click(function () { + var expand = function () { var $expander = H5P.jQuery(this); var $content = $expander.next(); if ($content.is(':visible')) { @@ -778,6 +778,11 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { $content.show(); } positionInner(); + }; + $dialog.find('.h5p-expander').click(expand).keypress(function (event) { + if (event.keyCode === 32) { + expand(); + } }); }); From 4e594dd57345b0aec5402052f9c4cdb089862b43 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 10:26:57 +0100 Subject: [PATCH 066/149] Fixed dialog pos and expand. --- js/h5p.js | 20 ++++++++++++-------- styles/h5p.css | 1 - 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 15a9f97..4183e1c 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -159,8 +159,7 @@ H5P.init = function () { // When resize has been prepared tell parent window to resize H5P.communicator.on('resizePrepared', function (data) { H5P.communicator.send('resize', { - height: document.body.scrollHeight, - parentHeight: data.parentHeight + height: document.body.scrollHeight }); }); @@ -722,13 +721,18 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { // Selecting embed code when dialog is opened H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { var $inner = $dialog.find('.h5p-inner'); + var $scroll = $inner.find('.h5p-scroll-content'); + var diff = $scroll.outerHeight() - $scroll.innerHeight(); var positionInner = function () { - $inner.css('height', ''); - var h = $inner.height(); - if (Math.floor($dialog.height()) === h) { - $inner.css('height', '100%'); + var height = $inner.height(); + if ($scroll[0].scrollHeight + diff > height) { + $inner.css('height', ''); // 100% } - $inner.css('marginTop', '-' + (h / 2) + 'px'); + else { + $inner.css('height', 'auto'); + height = $inner.height(); + } + $inner.css('marginTop', '-' + (height / 2) + 'px'); }; // Handle changing of width/height @@ -781,7 +785,7 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { }; $dialog.find('.h5p-expander').click(expand).keypress(function (event) { if (event.keyCode === 32) { - expand(); + expand.apply(this); } }); }); diff --git a/styles/h5p.css b/styles/h5p.css index 1925892..b4478b9 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -252,7 +252,6 @@ div.h5p-fullscreen { width: 300px; left: 50%; top: 50%; - height: auto; margin: 0 0 0 -150px; } .h5p-embed-dialog .h5p-embed-code-container, From 1e4a7ff26f418fde8164053b865369638bfb0a0f Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 10:29:43 +0100 Subject: [PATCH 067/149] Removed unused code. --- js/h5p-resizer.js | 48 ++--------------------------------------------- 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js index 54ec5d8..d1a1905 100644 --- a/js/h5p-resizer.js +++ b/js/h5p-resizer.js @@ -47,14 +47,10 @@ actionHandlers.prepareResize = function (iframe, data, respond) { responseData = {}; - // Retain parent size to avoid jumping/scrolling - responseData.parentHeight = iframe.parentElement.style.height; - //iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px'; - // Reset iframe height, in case content has shrinked. iframe.style.height = '1px'; - respond('resizePrepared', responseData); + respond('resizePrepared'); }; /** @@ -68,9 +64,6 @@ actionHandlers.resize = function (iframe, data, respond) { // Resize iframe so all content is visible. iframe.style.height = data.height + 'px'; - - // Free parent - //iframe.parentElement.style.height = data.parentHeight; }; /** @@ -84,43 +77,6 @@ } }; - // /** - // * Enter semi full screen. - // * Expands the iframe so that it covers the whole page. - // * - // * @private - // * @param {Object} iframe Element - // * @param {Object} data Payload - // * @param {Function} respond Send a response to the iframe - // */ - // actionHandlers.fullScreen = function (iframe, data, respond) { - // iframe.style.position = 'fixed'; - // iframe.style.top = iframe.style.left = 0; - // iframe.style.zIndex = 101; - // iframe.style.width = iframe.style.height = '100%'; - // document.body.addEventListener('keyup', escape, false); - // respond('fullScreen'); - // }; - // - // /** - // * Exit semi full screen. - // * - // * @private - // * @param {Object} iframe Element - // * @param {Object} data Payload - // * @param {Function} respond Send a response to the iframe - // */ - // actionHandlers.exitFullScreen = function (iframe, data, respond) { - // iframe.style.position = ''; - // iframe.style.top = iframe.style.left = ''; - // iframe.style.zIndex = ''; - // iframe.style.width = '100%'; - // iframe.style.height = ''; - // document.body.removeEventListener('keyup', escape, false); - // respond('exitFullScreen'); - // }; - - // Listen for messages from iframes window.addEventListener('message', function receiveMessage(event) { if (event.data.context !== 'h5p') { @@ -142,7 +98,7 @@ // Find action handler handler if (actionHandlers[event.data.action]) { - actionHandlers[event.data.action](iframe, event.data, function (action, data) { + actionHandlers[event.data.action](iframe, event.data, function respond(action, data) { if (data === undefined) { data = {}; } From 77d589ec6868d834aa3b91ece1cf0c124dbada71 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 10:55:47 +0100 Subject: [PATCH 068/149] Fixed absolute URLs violating its parent. --- js/h5p.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 4183e1c..932477e 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -459,7 +459,8 @@ H5P.getPath = function (path, contentId) { } if (!hasProtocol(prefix)) { - prefix = window.parent.location.protocol + "//" + window.parent.location.host + prefix; + // Use absolute urls + prefix = window.location.protocol + "//" + window.location.host + prefix; } return prefix + '/' + path; From d1cc8bcb71c054b4dbfcca583d988b96b6105072 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 11:05:22 +0100 Subject: [PATCH 069/149] Moved embed template to core. --- embed.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 embed.php diff --git a/embed.php b/embed.php new file mode 100644 index 0000000..25b37f4 --- /dev/null +++ b/embed.php @@ -0,0 +1,27 @@ + + + + + <?php print $content['title']; ?> + + + + + + + + +
        + + + From f2595b2bce932eb5832effb4186f5a9cbee0b617 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 13:59:42 +0100 Subject: [PATCH 070/149] Change how H5Ps are integrated. --- embed.php | 7 +---- js/h5p-content-upgrade.js | 2 +- js/h5p-library-details.js | 4 +-- js/h5p-library-list.js | 10 +++--- js/h5p-utils.js | 2 +- js/h5p-x-api-event.js | 4 +-- js/h5p.js | 65 +++++++++++++++++++++++++++++++-------- 7 files changed, 65 insertions(+), 29 deletions(-) diff --git a/embed.php b/embed.php index 25b37f4..7e6250b 100644 --- a/embed.php +++ b/embed.php @@ -13,13 +13,8 @@
        diff --git a/js/h5p-content-upgrade.js b/js/h5p-content-upgrade.js index ff3756f..6828fdc 100644 --- a/js/h5p-content-upgrade.js +++ b/js/h5p-content-upgrade.js @@ -7,7 +7,7 @@ var H5PUpgrades = H5PUpgrades || {}; // Initialize $(document).ready(function () { // Get library info - info = H5PIntegration.getLibraryInfo(); + info = H5PAdminIntegration.libraryInfo; // Get and reset container $container = $('#h5p-admin-container').html('

        ' + info.message + '

        '); diff --git a/js/h5p-library-details.js b/js/h5p-library-details.js index 8605b3d..9d22167 100644 --- a/js/h5p-library-details.js +++ b/js/h5p-library-details.js @@ -7,8 +7,8 @@ var H5PLibraryDetails= H5PLibraryDetails || {}; * Initializing */ H5PLibraryDetails.init = function () { - H5PLibraryDetails.$adminContainer = H5PIntegration.getAdminContainer(); - H5PLibraryDetails.library = H5PIntegration.getLibraryInfo(); + H5PLibraryDetails.$adminContainer = H5P.jQuery(H5PAdminIntegration.containerSelector); + H5PLibraryDetails.library = H5PAdminIntegration.libraryInfo; // currentContent holds the current list if data (relevant for filtering) H5PLibraryDetails.currentContent = H5PLibraryDetails.library.content; diff --git a/js/h5p-library-list.js b/js/h5p-library-list.js index 4382b28..5f419d6 100644 --- a/js/h5p-library-list.js +++ b/js/h5p-library-list.js @@ -7,15 +7,15 @@ var H5PLibraryList = H5PLibraryList || {}; * Initializing */ H5PLibraryList.init = function () { - var $adminContainer = H5PIntegration.getAdminContainer(); + var $adminContainer = H5P.jQuery(H5PAdminIntegration.containerSelector); - var libraryList = H5PIntegration.getLibraryList(); + var libraryList = H5PAdminIntegration.libraryList; if (libraryList.notCached) { $adminContainer.append(H5PUtils.getRebuildCache(libraryList.notCached)); } // Create library list - $adminContainer.append(H5PLibraryList.createLibraryList(H5PIntegration.getLibraryList())); + $adminContainer.append(H5PLibraryList.createLibraryList(H5PAdminIntegration.libraryList)); }; /** @@ -24,7 +24,7 @@ var H5PLibraryList = H5PLibraryList || {}; * @param {object} libraries List of libraries and headers */ H5PLibraryList.createLibraryList = function (libraries) { - var t = H5PIntegration.i18n.H5P; + var t = H5PAdminIntegration.l10n; if(libraries.listData === undefined || libraries.listData.length === 0) { return $('
        ' + t.NA + '
        '); } @@ -123,7 +123,7 @@ var H5PLibraryList = H5PLibraryList || {}; } } }; - + // Initialize me: $(document).ready(function () { if (!H5PLibraryList.initialized) { diff --git a/js/h5p-utils.js b/js/h5p-utils.js index 00af7bc..d90ca58 100644 --- a/js/h5p-utils.js +++ b/js/h5p-utils.js @@ -7,7 +7,7 @@ var H5PUtils = H5PUtils || {}; * @param {array} headers List of headers */ H5PUtils.createTable = function (headers) { - var $table = $('
        '); + var $table = $('
        '); if(headers) { var $thead = $(''); diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 5e1a025..e199c28 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -99,8 +99,8 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { */ H5P.XAPIEvent.prototype.setActor = function() { this.data.statement.actor = { - 'name': H5P.user.name, - 'mbox': 'mailto:' + H5P.user.mail, + 'name': H5PIntegration.user.name, + 'mbox': 'mailto:' + H5PIntegration.user.mail, 'objectType': 'Agent' }; }; diff --git a/js/h5p.js b/js/h5p.js index 932477e..d193881 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -48,7 +48,7 @@ H5P.init = function () { var $element = H5P.jQuery(this); var $container = H5P.jQuery('
        ').appendTo($element); var contentId = $element.data('content-id'); - var contentData = H5P.contentDatas['cid-' + contentId]; + var contentData = H5PIntegration.contents['cid-' + contentId]; if (contentData === undefined) { return H5P.error('No data for content id ' + contentId + '. Perhaps the library is gone?'); } @@ -217,6 +217,47 @@ H5P.init = function () { }); }; +/** + * Loop through assets for iframe content and create a set of tags for head. + * + * @private + * @param {number} contentId + * @returns {string} HTML + */ +H5P.getHeadTags = function (contentId) { + var basePath = window.location.protocol + '//' + window.location.host + H5PIntegration.basePath; + + var createUrl = function (path) { + if (path.substring(0,7) !== 'http://' && path.substring(0,8) !== 'https://') { + // Not external, add base path. + path = basePath + path; + } + return path; + }; + + var createStyleTags = function (styles) { + var tags = ''; + for (var i = 0; i < styles.length; i++) { + tags += ''; + } + return tags; + }; + + var createScriptTags = function (scripts) { + var tags = ''; + for (var i = 0; i < scripts.length; i++) { + tags += ''; + } + return tags; + }; + + return createStyleTags(H5PIntegration.core.styles) + + createStyleTags(H5PIntegration.contents['cid-' + contentId].styles) + + createScriptTags(H5PIntegration.core.scripts) + + createScriptTags(H5PIntegration.contents['cid-' + contentId].scripts) + + ''; +}; + H5P.communicator = (function () { /** * @class @@ -449,7 +490,7 @@ H5P.getPath = function (path, contentId) { } if (contentId !== undefined) { - prefix = H5P.url + '/content/' + contentId; + prefix = H5PIntegration.url + '/content/' + contentId; } else if (window.H5PEditor !== undefined) { prefix = H5PEditor.filesPath; @@ -571,15 +612,15 @@ H5P.t = function (key, vars, ns) { ns = 'H5P'; } - if (H5P.l10n[ns] === undefined) { + if (H5PIntegration.l10n[ns] === undefined) { return '[Missing translation namespace "' + ns + '"]'; } - if (H5P.l10n[ns][key] === undefined) { + if (H5PIntegration.l10n[ns][key] === undefined) { return '[Missing translation "' + key + '" in "' + ns + '"]'; } - var translation = H5P.l10n[ns][key]; + var translation = H5PIntegration.l10n[ns][key]; if (vars !== undefined) { // Replace placeholder with variables. @@ -1156,7 +1197,7 @@ H5P.libraryFromString = function (library) { * @returns {String} The full path to the library. */ H5P.getLibraryPath = function (library) { - return H5P.url + '/libraries/' + library; + return H5PIntegration.url + '/libraries/' + library; }; /** @@ -1204,8 +1245,8 @@ H5P.trim = function (value) { * @returns {Boolean} */ H5P.jsLoaded = function (path) { - H5P.loadedJs = H5P.loadedJs || []; - return H5P.jQuery.inArray(path, H5P.loadedJs) !== -1; + H5PIntegration.loadedJs = H5PIntegration.loadedJs || []; + return H5P.jQuery.inArray(path, H5PIntegration.loadedJs) !== -1; }; /** @@ -1215,8 +1256,8 @@ H5P.jsLoaded = function (path) { * @returns {Boolean} */ H5P.cssLoaded = function (path) { - H5P.loadedCss = H5P.loadedCss || []; - return H5P.jQuery.inArray(path, H5P.loadedCss) !== -1; + H5PIntegration.loadedCss = H5PIntegration.loadedCss || []; + return H5P.jQuery.inArray(path, H5PIntegration.loadedCss) !== -1; }; /** @@ -1255,7 +1296,7 @@ H5P.shuffleArray = function (array) { * @param {Number} time optional reported time usage */ H5P.setFinished = function (contentId, score, maxScore, time) { - if (H5P.postUserStatistics === true) { + if (H5PIntegration.postUserStatistics === true) { /** * Return unix timestamp for the given JS Date. * @@ -1268,7 +1309,7 @@ H5P.setFinished = function (contentId, score, maxScore, time) { // Post the results // TODO: Should we use a variable with the complete path? - H5P.jQuery.post(H5P.ajaxPath + 'setFinished', { + H5P.jQuery.post(H5PIntegration.ajaxPath + 'setFinished', { contentId: contentId, score: score, maxScore: maxScore, From aac10b86fa3a42d2679af3bc5a71da946d1c0a2d Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 14:53:26 +0100 Subject: [PATCH 071/149] Make old embed code insert new. --- js/h5p-embed.js | 250 +++++++----------------------------------------- 1 file changed, 35 insertions(+), 215 deletions(-) diff --git a/js/h5p-embed.js b/js/h5p-embed.js index deeab96..3196fa1 100644 --- a/js/h5p-embed.js +++ b/js/h5p-embed.js @@ -1,69 +1,27 @@ /*jshint multistr: true */ /** - * + * Converts old script tag embed to iframe */ -var H5P = H5P || (function () { +var H5POldEmbed = H5POldEmbed || (function () { var head = document.getElementsByTagName('head')[0]; - var contentId = 0; - var contents = {}; + var resizer = false; /** - * Wraps multiple content between a prefix and a suffix. + * Loads the resizing script */ - var wrap = function (prefix, content, suffix) { - var result = ''; - for (var i = 0; i < content.length; i++) { - result += prefix + content[i] + suffix; - } - return result; - }; - - /** - * - */ - var loadContent = function (id, script) { - var url = script.getAttribute('data-h5p'); - var data, callback = 'H5P' + id; - - // Prevent duplicate loading. - script.removeAttribute('data-h5p'); + var loadResizer = function (url) { + var data, callback = 'H5POldEmbed'; + resizer = true; // Callback for when content data is loaded. window[callback] = function (content) { - contents[id] = content; - - var iframe = document.createElement('iframe'); - var parent = script.parentNode; - parent.insertBefore(iframe, script); - - iframe.id = 'h5p-iframe-' + id; - iframe.style.display = 'block'; - iframe.style.width = '100%'; - iframe.style.height = '1px'; - iframe.style.border = 'none'; - iframe.style.zIndex = 101; - iframe.style.top = 0; - iframe.style.left = 0; - iframe.className = 'h5p-iframe'; - iframe.setAttribute('frameBorder', '0'); - iframe.contentDocument.open(); - iframe.contentDocument.write('\ - \ - \ - \ - ' + wrap('') + '\ - ' + wrap('') + '\ - \ -
        \ - '); - iframe.contentDocument.close(); - iframe.contentDocument.documentElement.style.overflow = 'hidden'; + // Add resizing script to head + var resizer = document.createElement('script'); + resizer.src = content; + head.appendChild(resizer); // Clean up - parent.removeChild(script); head.removeChild(data); delete window[callback]; }; @@ -74,183 +32,45 @@ var H5P = H5P || (function () { head.appendChild(data); }; + /** + * Replaced script tag with iframe + */ + var addIframe = function (script) { + // Add iframe + var iframe = document.createElement('iframe'); + iframe.src = script.getAttribute('data-h5p'); + iframe.frameBorder = false; + iframe.allowFullscreen = true; + var parent = script.parentNode; + parent.insertBefore(iframe, script); + parent.removeChild(script); + }; + /** * Go throught all script tags with the data-h5p attribute and load content. */ - function H5P() { + function H5POldEmbed() { var scripts = document.getElementsByTagName('script'); var h5ps = []; // Use seperate array since scripts grow in size. for (var i = 0; i < scripts.length; i++) { var script = scripts[i]; - if (script.hasAttribute('data-h5p')) { + if (script.src.indexOf('/h5p-resizer.js') !== -1) { + resizer = true; + } + else if (script.hasAttribute('data-h5p')) { h5ps.push(script); } } for (i = 0; i < h5ps.length; i++) { - loadContent(contentId, h5ps[i]); - contentId++; - } - } - - /** - * Return integration object - */ - H5P.getIntegration = function (id) { - var content = contents[id]; - return { - getJsonContent: function () { - return content.params; - }, - getContentPath: function () { - return content.path + 'content/' + content.id + '/'; - }, - getFullscreen: function () { - return content.fullscreen; - }, - getLibraryPath: function (library) { - return content.path + 'libraries/' + library; - }, - getContentData: function () { - return { - library: content.library, - jsonContent: content.params, - fullScreen: content.fullscreen, - exportUrl: content.exportUrl, - embedCode: content.embedCode - }; - }, - i18n: content.i18n, - showH5PIconInActionBar: function () { - // Always show H5P-icon when embedding - return true; + if (!resizer) { + loadResizer(h5ps[i].getAttribute('data-h5p')); } - }; - }; + addIframe(h5ps[i]); - // 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'; - } - /** - * 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('').children(':first'); - var keyup, disableSemiFullscreen = function () { - $disable.remove(); - $body.unbind('keyup', keyup); - iframe.style.position = 'static'; - done('h5p-semi-fullscreen'); - return false; - }; - keyup = function (event) { - if (event.keyCode === 27) { - disableSemiFullscreen(); - } - }; - $disable.click(disableSemiFullscreen); - $body.keyup(keyup); // TODO: Does not work with iframe's $! - entered(); - } - else { - // Create real fullscreen. - - before('h5p-fullscreen'); - var first, eventName = (fullScreenBrowserPrefix === 'ms' ? 'MSFullscreenChange' : fullScreenBrowserPrefix + 'fullscreenchange'); - document.addEventListener(eventName, function () { - if (first === undefined) { - // We are entering fullscreen mode - first = false; - entered(); - return; - } - - // We are exiting fullscreen - done('h5p-fullscreen'); - document.removeEventListener(eventName, arguments.callee, false); - }); - - if (fullScreenBrowserPrefix === '') { - iframe.requestFullScreen(); - } - else { - var method = (fullScreenBrowserPrefix === 'ms' ? 'msRequestFullscreen' : fullScreenBrowserPrefix + 'RequestFullScreen'); - var params = (fullScreenBrowserPrefix === 'webkit' && safariBrowser === 0 ? Element.ALLOW_KEYBOARD_INPUT : undefined); - iframe[method](params); - } - } - }; - - return H5P; + return H5POldEmbed; })(); -new H5P(); +new H5POldEmbed(); From 1c1829b13b351327294f07306334e880508692d3 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 14:53:38 +0100 Subject: [PATCH 072/149] Fixed integration bug. --- js/h5p-x-api-event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index e199c28..940a5cb 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -79,7 +79,7 @@ H5P.XAPIEvent.prototype.getVerb = function(full) { H5P.XAPIEvent.prototype.setObject = function(instance) { if (instance.contentId) { this.data.statement.object = { - 'id': H5P.contentDatas['cid-' + instance.contentId].url, + 'id': H5PIntegration.contents['cid-' + instance.contentId].url, 'objectType': 'Activity', 'extensions': { 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId From 07cd5c0d426b6532fb7c8d04a00011c96111554e Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Feb 2015 15:37:16 +0100 Subject: [PATCH 073/149] Added a floating touch. --- js/h5p.js | 7 +++++-- styles/h5p.css | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index d193881..1a3aecc 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -807,8 +807,11 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { H5P.jQuery(this).select().css('height', this.scrollHeight + 'px'); positionInner(); }).blur(function () { - H5P.jQuery(this).css('height', ''); - positionInner(); + var $area = H5P.jQuery(this); + setTimeout(function () { + $area.css('height', ''); + positionInner(); + }, 100); }).select(); // Expand advanced embed diff --git a/styles/h5p.css b/styles/h5p.css index b4478b9..37a6b62 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -253,6 +253,7 @@ div.h5p-fullscreen { left: 50%; top: 50%; margin: 0 0 0 -150px; + transition: margin 250ms linear 100ms; } .h5p-embed-dialog .h5p-embed-code-container, .h5p-embed-size { @@ -353,6 +354,7 @@ div.h5p-fullscreen { font-size: 1.125em; outline: none; margin: 0.5em 0 0; + display: inline-block; } .h5p-expander:before { content: "+"; From 8e53f626791494ebd0848d06ab676380f1175626 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 2 Mar 2015 13:42:15 +0100 Subject: [PATCH 074/149] Make sure admin container is empty. --- js/h5p-library-list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-library-list.js b/js/h5p-library-list.js index 5f419d6..dcb0cc8 100644 --- a/js/h5p-library-list.js +++ b/js/h5p-library-list.js @@ -7,7 +7,7 @@ var H5PLibraryList = H5PLibraryList || {}; * Initializing */ H5PLibraryList.init = function () { - var $adminContainer = H5P.jQuery(H5PAdminIntegration.containerSelector); + var $adminContainer = H5P.jQuery(H5PAdminIntegration.containerSelector).html(''); var libraryList = H5PAdminIntegration.libraryList; if (libraryList.notCached) { From 553ae243d2e1fd648aada5a58d3064f9713537cc Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 2 Mar 2015 15:01:05 +0100 Subject: [PATCH 075/149] UX improvements to embed code system --- js/h5p.js | 35 +++++++++++++++-------------------- styles/h5p.css | 2 +- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 1a3aecc..8ade0e6 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -198,8 +198,9 @@ H5P.init = function () { H5P.trigger(instance, 'resize'); } }); - H5P.instances.push(instance); } + + H5P.instances.push(instance); // Resize content. H5P.trigger(instance, 'resize'); @@ -758,6 +759,7 @@ H5P.findCopyrights = function (info, parameters, contentId) { * @returns {undefined} */ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { + var fullEmbedCode = embedCode + resizeCode; var dialog = new H5P.Dialog('embed', H5P.t('embed'), '' + H5P.t('size') + ': × px
        ' + H5P.t('showAdvanced') + '

        ' + H5P.t('advancedHelp') + '

        ', $element); // Selecting embed code when dialog is opened @@ -788,31 +790,21 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { return Math.ceil(num); }; var updateEmbed = function () { - $dialog.find('.h5p-embed-code-container:first').val(embedCode.replace(':w', getNum($w, size.width)).replace(':h', getNum($h, size.height))); + $dialog.find('.h5p-embed-code-container:first').val(fullEmbedCode.replace(':w', getNum($w, size.width)).replace(':h', getNum($h, size.height))); }; - var w = size.width; - $w.change(function () { - // Keep aspect ratio when changing width - var newW = getNum($w, size.width); - $h.val(Math.ceil(newW * (getNum($h, size.height) / w))); - w = newW; - updateEmbed(); - }); + $w.change(updateEmbed); $h.change(updateEmbed); updateEmbed(); // Select text and expand textareas - $dialog.find('.h5p-embed-code-container').focus(function () { - H5P.jQuery(this).select().css('height', this.scrollHeight + 'px'); - positionInner(); - }).blur(function () { - var $area = H5P.jQuery(this); - setTimeout(function () { - $area.css('height', ''); - positionInner(); - }, 100); - }).select(); + $dialog.find('.h5p-embed-code-container').each(function(index, value) { + H5P.jQuery(this).css('height', this.scrollHeight + 'px').focus(function() { + H5P.jQuery(this).select(); + }); + }); + $dialog.find('.h5p-embed-code-container').eq(0).select(); + positionInner(); // Expand advanced embed var expand = function () { @@ -826,6 +818,9 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size) { $expander.addClass('h5p-open').text(H5P.t('hideAdvanced')); $content.show(); } + $dialog.find('.h5p-embed-code-container').each(function(index, value) { + H5P.jQuery(this).css('height', this.scrollHeight + 'px'); + }); positionInner(); }; $dialog.find('.h5p-expander').click(expand).keypress(function (event) { diff --git a/styles/h5p.css b/styles/h5p.css index 37a6b62..e3ef889 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -350,7 +350,7 @@ div.h5p-fullscreen { margin: 0; } .h5p-expander { - cursor: default; + cursor: pointer; font-size: 1.125em; outline: none; margin: 0.5em 0 0; From 99db5699fb39d5b5a040d9325eb94fb1ec9d3278 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 2 Mar 2015 15:49:27 +0100 Subject: [PATCH 076/149] Handle anonymous users --- js/h5p-x-api-event.js | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 940a5cb..141e232 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -98,11 +98,33 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { * Helper function to set the actor, email and name will be added automatically */ H5P.XAPIEvent.prototype.setActor = function() { - this.data.statement.actor = { - 'name': H5PIntegration.user.name, - 'mbox': 'mailto:' + H5PIntegration.user.mail, - 'objectType': 'Agent' - }; + 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 = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(char) { + var random = Math.random()*16|0, newChar = char === 'x' ? random : (random&0x3|0x8); + return newChar.toString(16); + }); + localStorage.H5PUserUUID = uuid; + } + this.data.statement.actor = { + 'account': { + 'name': uuid, + 'homePage': window.location.origin + H5PIntegration.basePath + }, + 'objectType': 'Agent' + }; + } }; /** From e961f614a427d19597d2928411d8d69e24c9e3ea Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 2 Mar 2015 15:53:29 +0100 Subject: [PATCH 077/149] Fixed so that iframes can be loaded before resizer script. --- js/h5p-embed.js | 1 - js/h5p-resizer.js | 13 +++++++++++++ js/h5p.js | 12 +++++++++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/js/h5p-embed.js b/js/h5p-embed.js index 3196fa1..c141456 100644 --- a/js/h5p-embed.js +++ b/js/h5p-embed.js @@ -66,7 +66,6 @@ var H5POldEmbed = H5POldEmbed || (function () { loadResizer(h5ps[i].getAttribute('data-h5p')); } addIframe(h5ps[i]); - } } diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js index d1a1905..6318fc8 100644 --- a/js/h5p-resizer.js +++ b/js/h5p-resizer.js @@ -108,4 +108,17 @@ }); } }, 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, '*'); + } + } + })(); diff --git a/js/h5p.js b/js/h5p.js index 8ade0e6..1937f43 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -145,6 +145,11 @@ H5P.init = function () { // External embed var parentIsFriendly = false; + // Handle that the resizer is loaded after the iframe + H5P.communicator.on('ready', function () { + H5P.communicator.send('hello'); + }); + // Handle hello message from our parent window H5P.communicator.on('hello', function () { // Initial setup/handshake is done @@ -153,7 +158,8 @@ H5P.init = function () { // Hide scrollbars for correct size document.body.style.overflow = 'hidden'; - H5P.communicator.send('prepareResize'); + // Content need to be resized to fit the new iframe size + H5P.trigger(instance, 'resize'); }); // When resize has been prepared tell parent window to resize @@ -192,14 +198,14 @@ H5P.init = function () { H5P.jQuery(window.top).resize(function () { if (window.parent.H5P.isFullscreen) { // Use timeout to avoid bug in certain browsers when exiting fullscreen. Some browser will trigger resize before the fullscreenchange event. - H5P.trigger(instance, 'resize'); + H5P.trigger(instance, 'resize'); } else { H5P.trigger(instance, 'resize'); } }); } - + H5P.instances.push(instance); // Resize content. From acca9627755f2f6780330c64c433e1c2887ea01e Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 3 Mar 2015 11:03:10 +0100 Subject: [PATCH 078/149] Fixed misplaced stash (after merge). --- js/h5p.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 1937f43..b93d9a7 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -108,8 +108,8 @@ H5P.init = function () { H5P.on(instance, 'xAPI', H5P.xAPICompletedListener); H5P.on(instance, 'xAPI', H5P.externalDispatcher.trigger); - if (H5P.isFramed) - var resizeDelay;{ + if (H5P.isFramed) { + var resizeDelay; if (H5P.externalEmbed === false) { // Internal embed // Make it possible to resize the iframe when the content changes size. This way we get no scrollbars. From 23f68d821ec1f5de2d446013dc3fad325333476e Mon Sep 17 00:00:00 2001 From: Thomas Marstrander Date: Wed, 4 Mar 2015 10:06:35 +0100 Subject: [PATCH 079/149] Added woff to css inline --- fonts/h5p.woff | Bin 4324 -> 0 bytes styles/h5p.css | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 fonts/h5p.woff diff --git a/fonts/h5p.woff b/fonts/h5p.woff deleted file mode 100644 index 66394c10c29d618c76bc5a15fffdc7f78a306179..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4324 zcmeHKdyHIF89(27&b{-zyEAil+wRWp%)1B3)oP=Y~BFd-7aD3O>bMBJ&rbMNf3TOcO> z?alqp`QG0--?`_U?|$K;$w@{GPwI6R4f^9DZSm#LMVAg%7^4WdpR(Yxkk+!F!sq*gF>9-MwwHaox-f96b}Z>c*2iq8CL zh4gP}*#*L&Ax6+mE{TO0Bj*O8KZyU|c=POCA^2T(?o={9mFSt9rk|ya)AZ0yJAO$U zcQB&+*|Xwaewr1alTYwWx|{p8LcTag^-7MidJSbpMQymu3;gKt)muv4^{s0!cci*q zCG}U8bS_`JwVoT;a&?WLMmt=-bkjQLc~?Jb%%j?U-dVTl(lX2jFe^dF*^}ZZe**dG zwBNu&ylQ2X2N6s)Q>e8UYSkJbQ_b-Etf_TdyUO-g)dr@gzx;Tma{B;(!jcPY+ppiW zsrl9FgVh(Sh{fp+_EYfy|26AoYZ&V@j?~q_b5&TOXw1Vv%!~xdI{js zJA%vo3aL=xD3J#-L9Yl!QPOh1RP31_j7@wGhe`)6qeu3Ax-Ytqj7e{tO`(f6D76qkG@`-({LGmE)oK{kS~FMB?f zGHJAx`bHA=iwd?#aSi#kVYGSOMIvIP+9%_3pOWUy2cxEH+>=XLCta|jyb)1fQ+Okh z$BgTm$C{6w#jq#lYsBOvKgB3*-RUYp}MD^n(J z?wRbXwRs+;QZuH*E0jE6X{}B352;SGt9lf;S+8c;z4%RwLk75o|-t8%|Cec)#o;*=`-F4ili zNl@kbDoJYVZ*MQQxd|mAk>2r~n~92u$c^PlitLcj1vMjy6;YQW$qXuuC{D9zN=Rvd zSiHye2hxd-m^LI7zJYi#3fK3oiuXl?SMac^*t=Mis2|0vQ;}#=VH`aZIWCDOFs7ah zdT_bMvz+&D?Z<|fh%Yo>SzF>!YHF8#(;UFZI25(@gi54H-buT_=?)fNYcSPL~k1g6W4wh zwns-79CT(J8eEGTP$Qi$IX{NJf-~eO=juH44STQBA@-d3A%7&e zS2wT=?YFvkffjl{xIq0phK@b0l_-C6)|era zHoIrh3>h=qqGxxvXgWP>GG;K83<8D5avE`wOTgg!Cm#81a>GAZRN7s|ej9t}wxvC) z2Ky&v|E|IZHo-p{Q>!gQw*GuuQropH!2>gRo>!~se!y|S9e@Eqms-tN!pm#G^8hQi z{5ilOz{`Oj7ymHbY=~XYzRCVbxAFnLm;YTJmcLhDR4?fb8h^FHT-qqc+`wXCq)Ug9 zV2`){@QCS_12OR4zDU z9{LRN&}WE;K0{o?w!dW?`fRWo+s`h;pUaJGj(wi(V|!T-ThGeKLLh6@_J;_&1WLQu wO>8%tfpTa#85kUZ$u3Cu25x590rb~`cMUs)_CmH3T2lb)yd2KowjcZa2h12PrvLx| diff --git a/styles/h5p.css b/styles/h5p.css index dfb92db..141be24 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -5,7 +5,7 @@ font-family: 'h5p'; src:url('../fonts/h5p.eot?-9qymee'); src:url('../fonts/h5p.eot?#iefix-9qymee') format('embedded-opentype'), - url('../fonts/h5p.woff?-9qymee') format('woff'), + url('data:application/font-woff;base64,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') format('woff'), url('../fonts/h5p.ttf?-9qymee') format('truetype'), url('../fonts/h5p.svg?-9qymee#h5p') format('svg'); font-weight: normal; From c91391e0bc03f6622d2ea83afba5eed1ae49387e Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 4 Mar 2015 11:13:32 +0100 Subject: [PATCH 080/149] Fixed URLs. Finishing touches on the time tracking. --- js/h5p-content-upgrade-worker.js | 16 +-------------- js/h5p-content-upgrade.js | 35 ++++++++++++++++++-------------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/js/h5p-content-upgrade-worker.js b/js/h5p-content-upgrade-worker.js index 8b5f4f5..c201b54 100644 --- a/js/h5p-content-upgrade-worker.js +++ b/js/h5p-content-upgrade-worker.js @@ -1,6 +1,5 @@ var H5P = H5P || {}; -importScripts('/wp-content/plugins/h5p/h5p-php-library/js/h5p-version.js'); -importScripts('/wp-content/plugins/h5p/h5p-php-library/js/h5p-content-upgrade-process.js'); +importScripts('h5p-version.js', 'h5p-content-upgrade-process.js'); var libraryLoadedCallback; @@ -61,16 +60,3 @@ onmessage = function (event) { messageHandlers[event.data.action].call(this, event.data); } }; - - -// if (library.upgradesScript) { -// self.loadScript(library.upgradesScript, function (err) { -// if (err) { -// err = info.errorScript.replace('%lib', name + ' ' + version); -// } -// next(err, library); -// }); -// } -// else { -// next(null, library); -// } diff --git a/js/h5p-content-upgrade.js b/js/h5p-content-upgrade.js index 63addce..05667d8 100644 --- a/js/h5p-content-upgrade.js +++ b/js/h5p-content-upgrade.js @@ -78,8 +78,8 @@ // Create throbber with loading text and progress self.throbber = new Throbber(info.inProgress.replace('%ver', self.version)); -self.started = new Date().getTime(); -self.io = 0; + self.started = new Date().getTime(); + self.io = 0; // Track number of working self.working = 0; @@ -98,8 +98,8 @@ self.io = 0; start(); } else { - // No workers, do the job our self - self.loadScript('/wp-content/plugins/h5p/h5p-php-library/js/h5p-content-upgrade-process.js', start); + // No workers, do the job ourselves + self.loadScript(info.scriptBaseUrl + '/h5p-content-upgrade-process.js' + info.buster, start); } } @@ -141,7 +141,7 @@ self.io = 0; }; for (var i = 0; i < numWorkers; i++) { - self.workers[i] = new Worker('/wp-content/plugins/h5p/h5p-php-library/js/h5p-content-upgrade-worker.js'); + 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); @@ -158,17 +158,20 @@ self.io = 0; ContentUpgrade.prototype.nextBatch = function (outData) { var self = this; -var start = new Date().getTime(); + // Track time spent on IO + var start = new Date().getTime(); $.post(info.infoUrl, outData, function (inData) { -self.io += new Date().getTime() - start; + self.io += new Date().getTime() - start; if (!(inData instanceof Object)) { // Print errors from backend return self.setStatus(inData); } if (inData.left === 0) { var total = new Date().getTime() - self.started; - console.log('Upgrade took ' + total + 'ms'); - console.log((self.io/(total/100)) + ' % of the time went to IO (' + self.io + 'ms)'); + + 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(); @@ -347,17 +350,18 @@ self.io += new Date().getTime() - start; return; } -var start = new Date().getTime(); + // 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; + self.io += new Date().getTime() - start; next(info.errorData.replace('%lib', name + ' ' + version)); }).done(function (library) { -self.io += new Date().getTime() - start; + self.io += new Date().getTime() - start; librariesCache[key] = library; next(null, library); @@ -382,17 +386,18 @@ self.io += new Date().getTime() - start; return; } -var start = new Date().getTime(); + // Track time spent loading + var start = new Date().getTime(); $.ajax({ dataType: 'script', cache: true, url: url }).fail(function () { -self.io += new Date().getTime() - start; + self.io += new Date().getTime() - start; next(true); }).done(function () { scriptsCache[url] = true; -self.io += new Date().getTime() - start; + self.io += new Date().getTime() - start; next(); }); }; From 6d4caf855d008adcb4deded897decbaa6c212be7 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 4 Mar 2015 11:30:20 +0100 Subject: [PATCH 081/149] Fixed error handling. --- js/h5p-content-upgrade-worker.js | 2 +- js/h5p-content-upgrade.js | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/js/h5p-content-upgrade-worker.js b/js/h5p-content-upgrade-worker.js index c201b54..4cd0047 100644 --- a/js/h5p-content-upgrade-worker.js +++ b/js/h5p-content-upgrade-worker.js @@ -23,7 +23,7 @@ var messageHandlers = { postMessage({ action: 'error', id: job.id, - err: err + err: err.message ? err.message : err }); return; diff --git a/js/h5p-content-upgrade.js b/js/h5p-content-upgrade.js index 05667d8..cd50b86 100644 --- a/js/h5p-content-upgrade.js +++ b/js/h5p-content-upgrade.js @@ -381,6 +381,8 @@ * @param {Function} next */ ContentUpgrade.prototype.loadScript = function (url, next) { + var self = this; + if (scriptsCache[url] !== undefined) { next(); return; @@ -406,14 +408,16 @@ * */ ContentUpgrade.prototype.printError = function (error) { + var self = this; + if (error.type === 'errorParamsBroken') { - error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken; // TODO: Translate! + error = info.errorContent.replace('%id', error.id) + ' ' + info.errorParamsBroken; } else if (error.type === 'scriptMissing') { - error.err = info.errorScript.replace('%lib', error.library); + error = info.errorScript.replace('%lib', error.library); } - self.setStatus('

        ' + info.error + '
        ' + error.err + '

        '); + self.setStatus('

        ' + info.error + '
        ' + error + '

        '); }; })(H5P.jQuery, H5P.Version); From b45bfe770bfa3236a2a35e750671fea43ae5132d Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Fri, 6 Mar 2015 12:56:41 +0100 Subject: [PATCH 082/149] Critical bug fix for safari --- js/h5p.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index b93d9a7..7ed4074 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -215,12 +215,11 @@ H5P.init = function () { // Insert H5Ps that should be in iframes. H5P.jQuery("iframe.h5p-iframe").each(function () { var contentId = H5P.jQuery(this).data('content-id'); + this.contentWindow.H5P = this.contentWindow.H5P || {}; + this.contentWindow.H5P.externalEmbed = false; this.contentDocument.open(); this.contentDocument.write('' + H5P.getHeadTags(contentId) + '
        '); this.contentDocument.close(); - this.contentWindow.H5P = { - externalEmbed: false - }; }); }; From 72db596bac97c8755e015447c051c1453fefd51d Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 10 Mar 2015 09:53:33 +0100 Subject: [PATCH 083/149] Make sure H5P.externalEmbed is set inside the iframe. Run H5P.init from external resource to ensure correct execution order in IE9. --- js/h5p.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 7ed4074..9a08a7a 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -215,8 +215,6 @@ H5P.init = function () { // Insert H5Ps that should be in iframes. H5P.jQuery("iframe.h5p-iframe").each(function () { var contentId = H5P.jQuery(this).data('content-id'); - this.contentWindow.H5P = this.contentWindow.H5P || {}; - this.contentWindow.H5P.externalEmbed = false; this.contentDocument.open(); this.contentDocument.write('' + H5P.getHeadTags(contentId) + '
        '); this.contentDocument.close(); @@ -261,7 +259,7 @@ H5P.getHeadTags = function (contentId) { createStyleTags(H5PIntegration.contents['cid-' + contentId].styles) + createScriptTags(H5PIntegration.core.scripts) + createScriptTags(H5PIntegration.contents['cid-' + contentId].scripts) + - ''; + ''; }; H5P.communicator = (function () { @@ -1387,3 +1385,11 @@ H5P.on = function(instance, eventType, handler) { instance.$.on(eventType, handler); } }; + + +H5P.jQuery(document).ready(function () { + if (!H5P.preventInit) { + // Start script need to be an external resource to load in correct order for IE9. + H5P.init(); + } +}); From 2f44edd65ad6db8bf1b9c5739443face290fa610 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 10 Mar 2015 10:08:35 +0100 Subject: [PATCH 084/149] Prevent double init. Ie9 fix. --- embed.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/embed.php b/embed.php index 7e6250b..430b5a0 100644 --- a/embed.php +++ b/embed.php @@ -14,9 +14,6 @@
        From efd79c9e796724ce259a6857e10d41b9b53a2262 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 10 Mar 2015 10:09:31 +0100 Subject: [PATCH 085/149] Support init target. --- js/h5p.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 9a08a7a..4223230 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -39,13 +39,15 @@ H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? (document * Initialize H5P content. * Scans for ".h5p-content" in the document and initializes H5P instances where found. */ -H5P.init = function () { +H5P.init = function (target) { // Useful jQuery object. - H5P.$body = H5P.jQuery(document.body); + if (H5P.$body === undefined) { + H5P.$body = H5P.jQuery(document.body); + } // H5Ps added in normal DIV. - var $containers = H5P.jQuery(".h5p-content").each(function () { - var $element = H5P.jQuery(this); + var $containers = H5P.jQuery('.h5p-content:not(.h5p-initialized)', target).each(function () { + var $element = H5P.jQuery(this).addClass('h5p-initialized'); var $container = H5P.jQuery('
        ').appendTo($element); var contentId = $element.data('content-id'); var contentData = H5PIntegration.contents['cid-' + contentId]; @@ -213,8 +215,8 @@ H5P.init = function () { }); // Insert H5Ps that should be in iframes. - H5P.jQuery("iframe.h5p-iframe").each(function () { - var contentId = H5P.jQuery(this).data('content-id'); + H5P.jQuery('iframe.h5p-iframe:not(.h5p-initialized)', target).each(function () { + var contentId = H5P.jQuery(this).addClass('h5p-initialized').data('content-id'); this.contentDocument.open(); this.contentDocument.write('' + H5P.getHeadTags(contentId) + '
        '); this.contentDocument.close(); @@ -1390,6 +1392,6 @@ H5P.on = function(instance, eventType, handler) { H5P.jQuery(document).ready(function () { if (!H5P.preventInit) { // Start script need to be an external resource to load in correct order for IE9. - H5P.init(); + H5P.init(document.body); } }); From 7ef9ffbf1f470082e15f276606750b57da46753b Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 11 Mar 2015 11:40:07 +0100 Subject: [PATCH 086/149] Added default sort option to data view. Improved buttons. --- js/h5p-data-view.js | 17 +++++++++-------- js/h5p-utils.js | 27 ++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/js/h5p-data-view.js b/js/h5p-data-view.js index 323290a..d856933 100644 --- a/js/h5p-data-view.js +++ b/js/h5p-data-view.js @@ -32,8 +32,9 @@ var H5PDataView = (function ($) { * search in column 2. * @param {Function} loaded * Callback for when data has been loaded. + * @param {Object} order */ - function H5PDataView(container, source, headers, l10n, classes, filters, loaded) { + function H5PDataView(container, source, headers, l10n, classes, filters, loaded, order) { var self = this; self.$container = $(container).addClass('h5p-data-view').html(''); @@ -44,6 +45,7 @@ var H5PDataView = (function ($) { self.classes = (classes === undefined ? {} : classes); self.filters = (filters === undefined ? [] : filters); self.loaded = loaded; + self.order = order; self.limit = 20; self.offset = 0; @@ -68,8 +70,8 @@ var H5PDataView = (function ($) { url += (url.indexOf('?') === -1 ? '?' : '&') + 'offset=' + self.offset + '&limit=' + self.limit; // Add sorting - if (self.sortBy !== undefined && self.sortDir !== undefined) { - url += '&sortBy=' + self.sortBy + '&sortDir=' + self.sortDir; + if (self.order !== undefined) { + url += '&sortBy=' + self.order.by + '&sortDir=' + self.order.dir; } // Add filters @@ -144,12 +146,11 @@ var H5PDataView = (function ($) { // Create new table self.table = new H5PUtils.Table(self.classes, self.headers); - self.table.setHeaders(self.headers, function (col, dir) { - // Sorting column or direction has changed callback. - self.sortBy = col; - self.sortDir = dir; + self.table.setHeaders(self.headers, function (order) { + // Sorting column or direction has changed. + self.order = order; self.loadData(); - }); + }, self.order); self.table.appendTo(self.$container); } diff --git a/js/h5p-utils.js b/js/h5p-utils.js index d90ca58..423eb30 100644 --- a/js/h5p-utils.js +++ b/js/h5p-utils.js @@ -182,18 +182,30 @@ var H5PUtils = H5PUtils || {}; if (sortByCol !== undefined && col.sortable === true) { // Make sortable options.role = 'button'; - options.tabIndex = 1; + options.tabIndex = 0; // This is the first sortable column, use as default sort if (sortCol === undefined) { sortCol = id; sortDir = 0; + } + + // This is the sort column + if (sortCol === id) { options['class'] = 'h5p-sort'; + if (sortDir === 1) { + options['class'] += ' h5p-reverse'; + } } options.on.click = function () { sort($th, id); }; + options.on.keypress = function (event) { + if ((event.charCode || event.keyCode) === 32) { // Space + sort($th, id); + } + }; } } @@ -232,7 +244,10 @@ var H5PUtils = H5PUtils || {}; sortDir = 0; } - sortByCol(sortCol, sortDir); + sortByCol({ + by: sortCol, + dir: sortDir + }); }; /** @@ -244,11 +259,17 @@ var H5PUtils = H5PUtils || {}; * "text" and "sortable". E.g. * [{text: 'Col 1', sortable: true}, 'Col 2', 'Col 3'] * @param {Function} sort Callback which is runned when sorting changes + * @param {Object} [order] */ - this.setHeaders = function (cols, sort) { + this.setHeaders = function (cols, sort, order) { numCols = cols.length; sortByCol = sort; + if (order) { + sortCol = order.by; + sortDir = order.dir; + } + // Create new head var $newThead = $(''); var $tr = $('').appendTo($newThead); From fb823285357bfd0f616d02f4d10766feb40208a0 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 13 Mar 2015 12:51:31 +0100 Subject: [PATCH 087/149] Fullscreen fix --- js/h5p.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 4223230..a9c2617 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -33,8 +33,6 @@ else if (document.documentElement.msRequestFullscreen) { // Keep track of when the H5Ps where started H5P.opened = {}; -H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? (document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled) : true; - /** * Initialize H5P content. * Scans for ".h5p-content" in the document and initializes H5P instances where found. @@ -45,6 +43,11 @@ H5P.init = function (target) { H5P.$body = H5P.jQuery(document.body); } + // Determine if we can use full screen + if (H5P.canHasFullScreen === undefined) { + H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? (document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled) : true; + } + // H5Ps added in normal DIV. var $containers = H5P.jQuery('.h5p-content:not(.h5p-initialized)', target).each(function () { var $element = H5P.jQuery(this).addClass('h5p-initialized'); From 1b28c7b128ea442ca7353b1884ddde69b2c92497 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 18 Mar 2015 14:25:55 +0100 Subject: [PATCH 088/149] Use different vars for URL and file path to avoid having allow_url_fopen = On. --- h5p.classes.php | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 47df2f2..6945ca1 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -14,7 +14,7 @@ interface H5PFrameworkInterface { * - h5pVersion: The version of the H5P plugin/module */ public function getPlatformInfo(); - + /** * Fetches a file from a remote server using HTTP GET @@ -74,12 +74,6 @@ interface H5PFrameworkInterface { */ public function getUploadedH5pFolderPath(); - /** - * @return string - * Path to the folder where all h5p files are stored - */ - public function getH5pPath(); - /** * Get the path to the last uploaded h5p file * @@ -1270,7 +1264,7 @@ class H5PStorage { $contentId = $this->h5pC->saveContent($content, $contentMainId); $this->contentId = $contentId; - $contents_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content'; + $contents_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content'; if (!is_dir($contents_path)) { mkdir($contents_path, 0777, true); } @@ -1298,7 +1292,7 @@ class H5PStorage { $oldOnes = 0; // Find libraries directory and make sure it exists - $libraries_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'libraries'; + $libraries_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'libraries'; if (!is_dir($libraries_path)) { mkdir($libraries_path, 0777, true); } @@ -1396,8 +1390,9 @@ class H5PStorage { * The content id */ public function deletePackage($contentId) { - H5PCore::deleteFileTree($this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId); + H5PCore::deleteFileTree($this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId); $this->h5pF->deleteContentData($contentId); + // TODO: Delete export? } /** @@ -1430,8 +1425,8 @@ class H5PStorage { * The main id of the new content (used in frameworks that support revisioning) */ public function copyPackage($contentId, $copyFromId, $contentMainId = NULL) { - $source_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $copyFromId; - $destination_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId; + $source_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $copyFromId; + $destination_path = $this->h5pC->path . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId; $this->h5pC->copyFileTree($source_path, $destination_path); $this->h5pF->copyLibraryUsage($contentId, $copyFromId, $contentMainId); @@ -1467,7 +1462,7 @@ Class H5PExport { * @return string */ public function createExportFile($content) { - $h5pDir = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR; + $h5pDir = $this->h5pC->path . DIRECTORY_SEPARATOR; $tempPath = $h5pDir . 'temp' . DIRECTORY_SEPARATOR . $content['id']; $zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $content['id'] . '.h5p'; @@ -1548,7 +1543,7 @@ Class H5PExport { * Identifier for the H5P */ public function deleteExport($contentId) { - $h5pDir = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR; + $h5pDir = $this->h5pC->path . DIRECTORY_SEPARATOR; $zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $contentId . '.h5p'; if (file_exists($zipPath)) { unlink($zipPath); @@ -1617,11 +1612,12 @@ class H5PCore { * @param boolean $export enabled? * @param int $development_mode mode. */ - public function __construct($H5PFramework, $path, $language = 'en', $export = FALSE, $development_mode = H5PDevelopment::MODE_NONE) { + public function __construct($H5PFramework, $path, $url, $language = 'en', $export = FALSE, $development_mode = H5PDevelopment::MODE_NONE) { $this->h5pF = $H5PFramework; $this->h5pF = $H5PFramework; $this->path = $path; + $this->url = $url; $this->exportEnabled = $export; $this->development_mode = $development_mode; @@ -1787,7 +1783,7 @@ class H5PCore { $urls = array(); foreach ($assets as $asset) { - $urls[] = $asset->path . $asset->version; + $urls[] = $this->url . $asset->path . $asset->version; } return $urls; @@ -1806,7 +1802,7 @@ class H5PCore { ); foreach ($dependencies as $dependency) { if (isset($dependency['path']) === FALSE) { - $dependency['path'] = $this->path . '/libraries/' . H5PCore::libraryToString($dependency, TRUE); + $dependency['path'] = '/libraries/' . H5PCore::libraryToString($dependency, TRUE); $dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']); $dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']); } From 51a1d333e2129ad314556ed52c9742d91ebb05e4 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Thu, 19 Mar 2015 16:58:48 +0100 Subject: [PATCH 089/149] Improved packaging. Made sure zip format is followed by always using forward slashes. This will make h5p export work on Windows. --- h5p.classes.php | 51 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 47df2f2..5a1884a 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -14,7 +14,7 @@ interface H5PFrameworkInterface { * - h5pVersion: The version of the H5P plugin/module */ public function getPlatformInfo(); - + /** * Fetches a file from a remote server using HTTP GET @@ -1519,28 +1519,53 @@ Class H5PExport { $results = print_r(json_encode($h5pJson), true); file_put_contents($tempPath . DIRECTORY_SEPARATOR . 'h5p.json', $results); + // Get a complete file list from our tmp dir + $files = array(); + self::populateFileList($tempPath, $files); + // Create new zip instance. $zip = new ZipArchive(); $zip->open($zipPath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE); - // Get all files and folders in $tempPath - $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($tempPath . DIRECTORY_SEPARATOR)); - // Add files to zip - foreach ($iterator as $key => $value) { - $test = '.'; - // Do not add the folders '.' and '..' to the zip. This will make zip invalid. - if (substr_compare($key, $test, -strlen($test), strlen($test)) !== 0) { - // Get files path in $tempPath - $filePath = explode($tempPath . DIRECTORY_SEPARATOR, $key); - // Add files to the zip with the intended file-structure - $zip->addFile($key, $filePath[1]); - } + // Add all the files from the tmp dir. + foreach ($files as $file) { + // Please note that the zip format has no concept of folders, we must + // use forward slashes to separate our directories. + $zip->addFile($file->absolutePath, $file->relativePath); } + // Close zip and remove temp dir $zip->close(); H5PCore::deleteFileTree($tempPath); } + /** + * Recursive function the will add the files of the given directory to the + * given files list. All files are objects with an absolute path and + * a relative path. The relative path is forward slashes only! Great for + * use in zip files and URLs. + * + * @param string $dir path + * @param array $files list + * @param string $relative prefix. Optional + */ + private static function populateFileList($dir, &$files, $relative = '') { + $strip = strlen($dir) + 1; + foreach (glob($dir . DIRECTORY_SEPARATOR . '*') as $file) { + $rel = $relative . substr($file, $strip); + if (is_dir($file)) { + self::populateFileList($file, $files, $rel . '/'); + } + else { + // TODO: Should we filter out files that aren't in the whitelist? + $files[] = (object) array( + 'absolutePath' => $file, + 'relativePath' => $rel + ); + } + } + } + /** * Delete .h5p file * From 8494e85c3e572e366d4c650c7a3b882e9eb22e5f Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Thu, 19 Mar 2015 19:27:44 +0100 Subject: [PATCH 090/149] Remove todo items --- h5p.classes.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 5a1884a..b6df5af 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1487,8 +1487,6 @@ Class H5PExport { // Build h5p.json $h5pJson = array ( 'title' => $content['title'], - // TODO - stop using 'und', this is not the preferred way. - // Either remove language from the json if not existing, or use "language": null 'language' => (isset($content['language']) && strlen(trim($content['language'])) !== 0) ? $content['language'] : 'und', 'mainLibrary' => $content['library']['name'], 'embedTypes' => $embedTypes, @@ -1557,7 +1555,6 @@ Class H5PExport { self::populateFileList($file, $files, $rel . '/'); } else { - // TODO: Should we filter out files that aren't in the whitelist? $files[] = (object) array( 'absolutePath' => $file, 'relativePath' => $rel @@ -1742,8 +1739,6 @@ class H5PCore { // Recreate export file $exporter = new H5PExport($this->h5pF, $this); $exporter->createExportFile($content); - - // TODO: Should we rather create the file once first accessed, like imagecache? } // Cache. @@ -2248,8 +2243,6 @@ class H5PCore { /** * Helper function for creating markup for the unsupported libraries list * - * TODO: Make help text translatable - * * @return string Html * */ public function createMarkupForUnsupportedLibraryList($libraries) { @@ -2350,7 +2343,6 @@ class H5PContentValidator { // Keep track of the libraries we load to avoid loading it multiple times. $this->libraries = array(); - // TODO: Should this possible be done in core's loadLibrary? This might be done multiple places. // Keep track of all dependencies for the given content. $this->dependencies = array(); From e6e3ee75597b4cd8d3ad375013c33f728b8a8e17 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 20 Mar 2015 11:28:21 +0100 Subject: [PATCH 091/149] Allow assets to be relative to another location. Allow assets to contain external URLs. --- h5p.classes.php | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 6945ca1..13c196d 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1753,8 +1753,9 @@ class H5PCore { * @param array $dependency * @param string $type * @param array $assets + * @param string $prefix Optional. Make paths relative to another dir. */ - private function getDependencyAssets($dependency, $type, &$assets) { + private function getDependencyAssets($dependency, $type, &$assets, $prefix = '') { // Check if dependency has any files of this type if (empty($dependency[$type]) || $dependency[$type][0] === '') { return; @@ -1767,7 +1768,7 @@ class H5PCore { foreach ($dependency[$type] as $file) { $assets[] = (object) array( - 'path' => $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), + 'path' => $prefix . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), 'version' => $dependency['version'] ); } @@ -1783,7 +1784,19 @@ class H5PCore { $urls = array(); foreach ($assets as $asset) { - $urls[] = $this->url . $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; @@ -1793,9 +1806,10 @@ class H5PCore { * Return file paths for all dependecies files. * * @param array $dependencies + * @param string $prefix Optional. Make paths relative to another dir. * @return array files. */ - public function getDependenciesFiles($dependencies) { + public function getDependenciesFiles($dependencies, $prefix = '') { $files = array( 'scripts' => array(), 'styles' => array() @@ -1808,8 +1822,8 @@ class H5PCore { } $dependency['version'] = "?ver={$dependency['majorVersion']}.{$dependency['minorVersion']}.{$dependency['patchVersion']}"; - $this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts']); - $this->getDependencyAssets($dependency, 'preloadedCss', $files['styles']); + $this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts'], $prefix); + $this->getDependencyAssets($dependency, 'preloadedCss', $files['styles'], $prefix); } return $files; } From 76b0fc04f8c9f3d33ba9c95f38e9bb2fa6d5751d Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sat, 21 Mar 2015 14:16:31 +0100 Subject: [PATCH 092/149] Make UUID creator available for everyone --- js/h5p-x-api-event.js | 5 +---- js/h5p.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 141e232..abc7cca 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -111,10 +111,7 @@ H5P.XAPIEvent.prototype.setActor = function() { uuid = localStorage.H5PUserUUID; } else { - uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(char) { - var random = Math.random()*16|0, newChar = char === 'x' ? random : (random&0x3|0x8); - return newChar.toString(16); - }); + uuid = H5P.createUUID(); localStorage.H5PUserUUID = uuid; } this.data.statement.actor = { diff --git a/js/h5p.js b/js/h5p.js index a9c2617..c80d2a5 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1391,6 +1391,18 @@ H5P.on = function(instance, eventType, handler) { } }; +/** + * Create UUID + * + * @returns {String} UUID + */ +H5P.createUUID = function() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(char) { + var random = Math.random()*16|0, newChar = char === 'x' ? random : (random&0x3|0x8); + return newChar.toString(16); + }); +}; + H5P.jQuery(document).ready(function () { if (!H5P.preventInit) { From 313bb757ba77246ea63820533d6d3944e2999b8d Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sat, 21 Mar 2015 16:45:38 +0100 Subject: [PATCH 093/149] Add bubble system and xAPI context --- h5p.classes.php | 5 ++++- js/h5p-event-dispatcher.js | 11 +++++++++++ js/h5p-x-api-event.js | 40 +++++++++++++++++++++++++++++++++++++- js/h5p.js | 22 +++++++++++++++++++-- 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index b6df5af..c4c703d 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2731,11 +2731,14 @@ class H5PContentValidator { 'type' => 'group', 'fields' => $library['semantics'], ), FALSE); - $validkeys = array('library', 'params'); + $validkeys = array('library', 'params', 'uuid'); if (isset($semantics->extraAttributes)) { $validkeys = array_merge($validkeys, $semantics->extraAttributes); } $this->filterParams($value, $validkeys); + if (isset($value->uuid) && ! preg_match('/^\{?[A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12}\}?$/', $value->uuid)) { + unset($value->uuid); + } // Find all dependencies for this library $depkey = 'preloaded-' . $library['machineName']; diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index cb40cd6..e164564 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -8,6 +8,13 @@ var H5P = H5P || {}; H5P.Event = function(type, data) { this.type = type; this.data = data; + var bubbles = true; + this.preventBubbling = function() { + bubbles = false; + }; + this.getBubbles = function() { + return bubbles; + }; }; H5P.EventDispatcher = (function () { @@ -132,6 +139,7 @@ H5P.EventDispatcher = (function () { * argument */ this.trigger = function (event, eventData) { + console.log(event); if (event === undefined) { return; } @@ -148,6 +156,9 @@ H5P.EventDispatcher = (function () { for (var i = 0; i < triggers[event.type].length; i++) { triggers[event.type][i].listener.call(triggers[event.type][i].thisArg, event); } + if (event.getBubbles() && typeof self.parent === 'function' && typeof self.parent.trigger === 'function') { + self.parent.trigger(event); + } }; } diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index abc7cca..0d18d84 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -79,12 +79,20 @@ H5P.XAPIEvent.prototype.getVerb = function(full) { H5P.XAPIEvent.prototype.setObject = function(instance) { if (instance.contentId) { this.data.statement.object = { - 'id': H5PIntegration.contents['cid-' + instance.contentId].url, + 'id': this.getContentXAPIId(instance), 'objectType': 'Activity', 'extensions': { 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId } }; + if (instance.h5pUUID) { + this.data.statement.object.extensions['http://h5p.org/x-api/h5p-uuid'] = instance.h5pUUID; + } + if (typeof instance.getH5PTitle === 'function') { + this.data.statement.object.description = { + "en-US": instance.getH5PTitle() + }; + } } else { // Not triggered by an H5P content type... @@ -94,6 +102,25 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { } }; +/** + * 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.uuid) { + var parentId = instance.parent.uuid === undefined ? instance.parent.contentId : instance.parent.uuid; + this.data.statement.context = { + "parent": [ + { + "id": getContentXAPIId(instance.parent), + "objectType": "Activity" + } + ] + }; + } +}; + /** * Helper function to set the actor, email and name will be added automatically */ @@ -142,6 +169,17 @@ H5P.XAPIEvent.prototype.getScore = function() { return this.getVerifiedStatementValue(['result', 'score', 'raw']); }; +H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) { + var xAPIId; + if (instance.contentId) { + xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url; + if (instance.h5pUUID) { + xAPIId += '?uuid=' + instance.h5pUUID; + } + } + return xAPIId; +} + /** * Figure out if a property exists in the statement and return it * diff --git a/js/h5p.js b/js/h5p.js index c80d2a5..0370476 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -544,7 +544,7 @@ H5P.classFromName = function (name) { * @param {Object} The parent of this H5P * @return {Object} Instance. */ -H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { +H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { var nameSplit, versionSplit; try { nameSplit = library.library.split(' ', 2); @@ -575,7 +575,16 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { return H5P.error('Unable to find constructor for: ' + library.library); } - var instance = new constructor(library.params, contentId); + var extras = {}; + + if (library.uuid) { + extras.uuid = library.uuid; + } + if (parent) { + extras.parent = parent; + } + + var instance = new constructor(library.params, contentId, extras); if (instance.$ === undefined) { instance.$ = H5P.jQuery(instance); @@ -584,6 +593,12 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { if (instance.contentId === undefined) { instance.contentId = contentId; } + if (instance.uuid === undefined && library.uuid) { + instance.uuid = library.uuid; + } + if (instance.parent === undefined && parent) { + instance.parent = parent; + } if ($attachTo !== undefined) { instance.attach($attachTo); @@ -1403,6 +1418,9 @@ H5P.createUUID = function() { }); }; +H5P.createH5PTitle = function(rawTitle) { + return H5P.jQuery('
        ').text(rawTitle).text().substr(0, 60); +}; H5P.jQuery(document).ready(function () { if (!H5P.preventInit) { From 3da0de46264895c946db1b0955618e6dc230cfd3 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 11:17:58 +0100 Subject: [PATCH 094/149] Fix bubble system --- js/h5p-event-dispatcher.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index e164564..31043f9 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -5,10 +5,16 @@ var H5P = H5P || {}; * The Event class for the EventDispatcher * @class */ -H5P.Event = function(type, data) { +H5P.Event = function(type, data, extras) { this.type = type; this.data = data; var bubbles = true; + if (extras === undefined) { + extras = {}; + } + if (extras.bubbles === false) { + bubbles = false; + } this.preventBubbling = function() { bubbles = false; }; @@ -139,7 +145,6 @@ H5P.EventDispatcher = (function () { * argument */ this.trigger = function (event, eventData) { - console.log(event); if (event === undefined) { return; } @@ -156,7 +161,7 @@ H5P.EventDispatcher = (function () { for (var i = 0; i < triggers[event.type].length; i++) { triggers[event.type][i].listener.call(triggers[event.type][i].thisArg, event); } - if (event.getBubbles() && typeof self.parent === 'function' && typeof self.parent.trigger === 'function') { + if (event.getBubbles() && self.parent instanceof H5P.EventDispatcher && typeof self.parent.trigger === 'function') { self.parent.trigger(event); } }; From b79a5b61b83d28faf425a0b080fac7bbeb2d85bf Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 12:42:07 +0100 Subject: [PATCH 095/149] uuid consists of lowercase letters --- h5p.classes.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index c4c703d..2fba0f2 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2736,8 +2736,8 @@ class H5PContentValidator { $validkeys = array_merge($validkeys, $semantics->extraAttributes); } $this->filterParams($value, $validkeys); - if (isset($value->uuid) && ! preg_match('/^\{?[A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12}\}?$/', $value->uuid)) { - unset($value->uuid); + if (isset($value->uuid) && ! preg_match('/^\{?[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}?$/', $value->uuid)) { + unset($value->uuid); } // Find all dependencies for this library From e14c24cc070a1c197fecefa14ccecdcf49f8a076 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 12:43:07 +0100 Subject: [PATCH 096/149] Fix xAPI statement structure to follow the standard better --- js/h5p-x-api-event.js | 28 +++++++++++++--------------- js/h5p-x-api.js | 5 ++++- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 0d18d84..837b219 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -81,25 +81,21 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { this.data.statement.object = { 'id': this.getContentXAPIId(instance), 'objectType': 'Activity', - 'extensions': { - 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId + 'definition': { + 'extensions': { + 'http://h5p.org/x-api/h5p-local-content-id': instance.contentId + } } }; if (instance.h5pUUID) { this.data.statement.object.extensions['http://h5p.org/x-api/h5p-uuid'] = instance.h5pUUID; } if (typeof instance.getH5PTitle === 'function') { - this.data.statement.object.description = { + this.data.statement.object.definition.name = { "en-US": instance.getH5PTitle() }; } } - else { - // Not triggered by an H5P content type... - this.data.statement.object = { - 'objectType': 'Activity' - }; - } }; /** @@ -111,12 +107,14 @@ H5P.XAPIEvent.prototype.setContext = function(instance) { if (instance.parent && instance.parent.contentId || instance.parent.uuid) { var parentId = instance.parent.uuid === undefined ? instance.parent.contentId : instance.parent.uuid; this.data.statement.context = { - "parent": [ - { - "id": getContentXAPIId(instance.parent), - "objectType": "Activity" - } - ] + "contextActivities": { + "parent": [ + { + "id": this.getContentXAPIId(instance.parent), + "objectType": "Activity" + } + ] + } }; } }; diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index e4f4edb..f18cfd3 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -39,9 +39,12 @@ H5P.EventDispatcher.prototype.createXAPIEventTemplate = function(verb, extra) { event.data.statement[i] = extra[i]; } } - if (!('object' in event)) { + if (!('object' in event.data.statement)) { event.setObject(this); } + if (!('context' in event.data.statement)) { + event.setContext(this); + } return event; }; From 93f2bcc01a1c06ee6ac30477d17b9f71a5193e32 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 12:44:55 +0100 Subject: [PATCH 097/149] Use uuid instead of h5pUUID --- js/h5p-x-api-event.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 837b219..12bfb36 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -171,8 +171,8 @@ H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) { var xAPIId; if (instance.contentId) { xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url; - if (instance.h5pUUID) { - xAPIId += '?uuid=' + instance.h5pUUID; + if (instance.uuid) { + xAPIId += '?uuid=' + instance.uuid; } } return xAPIId; From 5a882e4d55cc85bfec06bf80039a5fe022e997c6 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 12:45:03 +0100 Subject: [PATCH 098/149] Improve title generator --- js/h5p.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 0370476..5123091 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -576,7 +576,6 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { } var extras = {}; - if (library.uuid) { extras.uuid = library.uuid; } @@ -1418,8 +1417,20 @@ H5P.createUUID = function() { }); }; -H5P.createH5PTitle = function(rawTitle) { - return H5P.jQuery('
        ').text(rawTitle).text().substr(0, 60); +H5P.createH5PTitle = function(rawTitle, maxLength) { + if (maxLength === undefined) { + maxLength = 60; + } + var title = H5P.jQuery('
        ') + .text( + // Strip tags + rawTitle.replace(/(<([^>]+)>)/ig,"") + // Escape + ).text(); + if (title.length > maxLength) { + title = title.substr(0, maxLength - 3) + '...'; + } + return title; }; H5P.jQuery(document).ready(function () { From 1c4a5e014b11cb957d2f2e4e5e564dedc2c82fb8 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 13:12:44 +0100 Subject: [PATCH 099/149] Comment out prefixing that isn't working --- h5p.classes.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 955dab0..4073c71 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1785,10 +1785,9 @@ class H5PCore { if ($type === 'preloadedCss' && (isset($dependency['dropCss']) && $dependency['dropCss'] === '1')) { return; } - foreach ($dependency[$type] as $file) { $assets[] = (object) array( - 'path' => $prefix . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), + 'path' => /*$prefix . */$dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), 'version' => $dependency['version'] ); } @@ -1808,7 +1807,7 @@ class H5PCore { // Add URL prefix if not external if (strpos($asset->path, '://') === FALSE) { - $url = $this->url . $url; + $url = /*$this->url .*/ $url; } // Add version/cache buster if set From c4e488f9c8a4c1c58f0c36da40cd5d54480307e5 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 13:27:40 +0100 Subject: [PATCH 100/149] Get contentId from the right place in the statement --- js/h5p-x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index f18cfd3..cc18943 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -71,7 +71,7 @@ H5P.xAPICompletedListener = function(event) { if (statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { var score = statement.result.score.raw; var maxScore = statement.result.score.max; - var contentId = statement.object.extensions['http://h5p.org/x-api/h5p-local-content-id']; + var contentId = statement.object.definition.extensions['http://h5p.org/x-api/h5p-local-content-id']; H5P.setFinished(contentId, score, maxScore); } } From 9a204b3d8f7dceaa80c71224401e93c0a8de37a0 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 15:14:28 +0100 Subject: [PATCH 101/149] Make sure title are added --- js/h5p-x-api-event.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 12bfb36..3ef34e6 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -87,13 +87,21 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { } } }; - if (instance.h5pUUID) { - this.data.statement.object.extensions['http://h5p.org/x-api/h5p-uuid'] = instance.h5pUUID; + if (instance.uuid) { + this.data.statement.object.definition.extensions['http://h5p.org/x-api/h5p-uuid'] = instance.uuid; + // 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() + }; + } } - if (typeof instance.getH5PTitle === 'function') { - this.data.statement.object.definition.name = { - "en-US": instance.getH5PTitle() - }; + else { + if (H5PIntegration.contents['cid-' + instance.contentId].title) { + this.data.statement.object.definition.name = { + "en-US": H5P.createH5PTitle(H5PIntegration.contents['cid-' + instance.contentId].title) + }; + } } } }; From 431cff0196e1aee4cd1a50c493d8f5c80db9c38d Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Sun, 22 Mar 2015 19:28:16 +0100 Subject: [PATCH 102/149] New API for saving user data per content. --- js/h5p.js | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 139 insertions(+), 7 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index a9c2617..ee2bca0 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -59,7 +59,8 @@ H5P.init = function (target) { } var library = { library: contentData.library, - params: JSON.parse(contentData.jsonContent) + params: JSON.parse(contentData.jsonContent), + userDatas: contentData.contentUserDatas }; // Create new instance. @@ -575,7 +576,18 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { return H5P.error('Unable to find constructor for: ' + library.library); } - var instance = new constructor(library.params, contentId); + var contentExtrasWrapper; + if (library.userDatas && library.userDatas.state) { + try { + contentExtrasWrapper = { + previousState: JSON.parse(library.userDatas.state) + }; + } + catch (err) {} + } + console.log(contentExtrasWrapper); + + var instance = new constructor(library.params, contentId, contentExtrasWrapper); if (instance.$ === undefined) { instance.$ = H5P.jQuery(instance); @@ -1391,10 +1403,130 @@ H5P.on = function(instance, eventType, handler) { } }; +// Wrap in privates +(function ($) { -H5P.jQuery(document).ready(function () { - if (!H5P.preventInit) { - // Start script need to be an external resource to load in correct order for IE9. - H5P.init(document.body); + /** + * Creates ajax requests for inserting, updateing and deleteing + * content user data. + * + * @private + * @param {number} contentId What content to store the data for. + * @param {string} dataType Identifies the set of data for this content. + * @param {function} [done] Callback when ajax is done. + * @param {object} [data] To be stored for future use. + * @param {boolean} [preload=false] Data is loaded when content is loaded. + * @param {boolean} [invalidate=false] Data is invalidated when content changes. + * @param {boolean} [async=true] + */ + function contentUserDataAjax(contentId, dataType, done, data, preload, invalidate, async) { + var options = { + url: H5PIntegration.ajaxPath + 'content-user-data/' + contentId + '/' + dataType, + dataType: 'json', + async: async === undefined ? true : async + }; + if (data !== undefined) { + options.type = 'POST'; + options.data = { + data: (data === null ? 0 : JSON.stringify(data)), + preload: (preload ? 1 : 0), + invalidate: (invalidate ? 1 : 0) + }; + } + else { + options.type = 'GET'; + } + if (done !== undefined) { + options.error = function (xhr, error) { + done(error); + }; + options.success = function (response) { + if (!response.success) { + done(response.error); + return; + } + + if (response.data === false || response.data === undefined) { + done(); + return; + } + + try { + done(undefined, JSON.parse(response.data)); + } + catch (error) { + done('Unable to decode data.'); + } + }; + } + + $.ajax(options); } -}); + + /** + * Get user data for given content. + * + * @public + * @param {number} contentId What content to get data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {function} [done] Callback with error and data parameters. + */ + H5P.getUserData = function (contentId, dataId, done) { + contentUserDataAjax(contentId, dataId, done); + }; + + /** + * Set user data for given content. + * + * @public + * @param {number} contentId What content to get data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {object} data The data that is to be stored. + * @param {boolean} [preloaded=false] If the data should be loaded when content is loaded. + * @param {boolean} [deleteOnChange=false] If the data should be invalidated when the content changes. + * @param {function} [errorCallback] Callback with error as parameters. + */ + H5P.setUserData = function (contentId, dataId, data, preloaded, deleteOnChange, errorCallback) { + contentUserDataAjax(contentId, dataId, function (error, data) { + if (errorCallback && error) { + errorCallback(error); + } + }, data, preloaded, deleteOnChange); + }; + + /** + * Delete user data for given content. + * + * @public + * @param {number} contentId What content to remove data for. + * @param {string} dataId Identifies the set of data for this content. + */ + H5P.deleteUserData = function (contentId, dataId) { + contentUserDataAjax(contentId, dataId, undefined, null); + }; + + // Init H5P when page is fully loadded + $(document).ready(function () { + if (!H5P.preventInit) { + // Note that this start script has to be an external resource for it to + // load in correct order in IE9. + H5P.init(document.body); + } + + // Store the current state of the H5P when leaving the page. + H5P.$window.on('unload', function () { + for (var i = 0; i < H5P.instances.length; i++) { + var instance = H5P.instances[i]; + if (instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function') { + var state = instance.getCurrentState(); + if (state !== undefined) { + // Async is not used to prevent the request from being cancelled. + contentUserDataAjax(instance.contentId, 'state', undefined, state, true, true, false); + } + } + } + }); + }); + +})(H5P.jQuery); From a712d6ed61a8a618d049df9c600981e882098076 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Sun, 22 Mar 2015 19:31:35 +0100 Subject: [PATCH 103/149] Removed debug. --- js/h5p.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index ee2bca0..a416d26 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -585,7 +585,6 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { } catch (err) {} } - console.log(contentExtrasWrapper); var instance = new constructor(library.params, contentId, contentExtrasWrapper); From 6b3e550a48287a980caad57b0d314b1985541f22 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:37:10 +0100 Subject: [PATCH 104/149] Don't bubble by default --- js/h5p-event-dispatcher.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index 31043f9..d67ee10 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -8,12 +8,12 @@ var H5P = H5P || {}; H5P.Event = function(type, data, extras) { this.type = type; this.data = data; - var bubbles = true; + var bubbles = false; if (extras === undefined) { extras = {}; } - if (extras.bubbles === false) { - bubbles = false; + if (extras.bubbles === true) { + bubbles = true; } this.preventBubbling = function() { bubbles = false; @@ -161,6 +161,7 @@ H5P.EventDispatcher = (function () { for (var i = 0; i < triggers[event.type].length; i++) { triggers[event.type][i].listener.call(triggers[event.type][i].thisArg, event); } + // Bubble if (event.getBubbles() && self.parent instanceof H5P.EventDispatcher && typeof self.parent.trigger === 'function') { self.parent.trigger(event); } From 46c92607550ace97f50160f5c609991c8a575cd0 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:37:28 +0100 Subject: [PATCH 105/149] Don't bubble by default --- js/h5p-x-api-event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 3ef34e6..7d85d90 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -6,7 +6,7 @@ var H5P = H5P || {}; * @class */ H5P.XAPIEvent = function() { - H5P.Event.call(this, 'xAPI', {'statement': {}}); + H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true}); }; H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype); From 5d6d22a13fc65a10129b20ed3016e97aea33b731 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:37:48 +0100 Subject: [PATCH 106/149] Accept other verbs than the adl verbs --- js/h5p-x-api-event.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 7d85d90..79504f0 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -44,8 +44,8 @@ H5P.XAPIEvent.prototype.setVerb = function(verb) { } }; } - else { - H5P.error('illegal verb'); + else if (verb.id !== undefined) { + this.data.statement.verb = verb; } }; From d3453b86375e56a053af15b446225ba059e961b0 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:38:17 +0100 Subject: [PATCH 107/149] Context bug fix --- js/h5p-x-api-event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 79504f0..19bc211 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -112,7 +112,7 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { * @param {object} instance - the H5P instance */ H5P.XAPIEvent.prototype.setContext = function(instance) { - if (instance.parent && instance.parent.contentId || instance.parent.uuid) { + if (instance.parent && (instance.parent.contentId || instance.parent.uuid)) { var parentId = instance.parent.uuid === undefined ? instance.parent.contentId : instance.parent.uuid; this.data.statement.context = { "contextActivities": { From eb02a5942d8d9da697ab1f04abf5dd17c7df4822 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:38:33 +0100 Subject: [PATCH 108/149] Make externalDispatcher from framed content work again --- js/h5p-x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index cc18943..c3efcf2 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -3,7 +3,7 @@ var H5P = H5P || {}; // Create object where external code may register and listen for H5P Events H5P.externalDispatcher = new H5P.EventDispatcher(); -if (H5P.isFramed && H5P.externalEmbed === false) { +if (H5P.isFramed && H5P.externalEmbed !== true) { H5P.externalDispatcher.on('xAPI', window.top.H5P.externalDispatcher.trigger); } From 9d38f838867c31edc6da1223abf27ffe6e7b923b Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:38:57 +0100 Subject: [PATCH 109/149] Refactor completed listener to use more of the new xAPI api functions --- js/h5p-x-api.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index c3efcf2..9d0e7cd 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -66,13 +66,10 @@ H5P.EventDispatcher.prototype.triggerXAPICompleted = function(score, maxScore) { * @param {function} event - xAPI event */ H5P.xAPICompletedListener = function(event) { - var statement = event.data.statement; - if ('verb' in statement) { - if (statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed') { - var score = statement.result.score.raw; - var maxScore = statement.result.score.max; - var contentId = statement.object.definition.extensions['http://h5p.org/x-api/h5p-local-content-id']; - H5P.setFinished(contentId, score, maxScore); - } + 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); } }; From 8e113ff792f7cc82d1b51605ec8527141432584e Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Sun, 22 Mar 2015 20:39:16 +0100 Subject: [PATCH 110/149] Add parameter set as parameter 5 to new runnable, and blacklist libraries that already have a custom third parameter for their constructor --- js/h5p.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 5123091..7ac82d0 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -541,10 +541,10 @@ H5P.classFromName = function (name) { * @param {Number} contentId * @param {jQuery} $attachTo An optional element to attach the instance to. * @param {Boolean} skipResize Optionally skip triggering of the resize event after attaching. - * @param {Object} The parent of this H5P + * @param {Object} extras - extra params for the H5P content constructor * @return {Object} Instance. */ -H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { +H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { var nameSplit, versionSplit; try { nameSplit = library.library.split(' ', 2); @@ -575,15 +575,20 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { return H5P.error('Unable to find constructor for: ' + library.library); } - var extras = {}; + if (extras === undefined) { + extras = {}; + } if (library.uuid) { extras.uuid = library.uuid; } - if (parent) { - extras.parent = parent; + + // Some old library versions have their own custom third parameter. Make sure we don't send them the extras. They'll interpret it as something else + if (H5P.jQuery.inArray(library.library, ['H5P.CoursePresentation 1.0', 'H5P.CoursePresentation 1.1', 'H5P.CoursePresentation 1.2', 'H5P.CoursePresentation 1.3']) > -1) { + var instance = new constructor(library.params, contentId); + } + else { + var instance = new constructor(library.params, contentId, extras); } - - var instance = new constructor(library.params, contentId, extras); if (instance.$ === undefined) { instance.$ = H5P.jQuery(instance); @@ -595,8 +600,8 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, parent) { if (instance.uuid === undefined && library.uuid) { instance.uuid = library.uuid; } - if (instance.parent === undefined && parent) { - instance.parent = parent; + if (instance.parent === undefined && extras && extras.parent) { + instance.parent = extras.parent; } if ($attachTo !== undefined) { From 67288c2a0b7d13e4e025986973019e6e2481057c Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Sun, 22 Mar 2015 20:44:35 +0100 Subject: [PATCH 111/149] Added auto save loop. Added save after xAPI events. Made it possible to disable saving. --- js/h5p.js | 57 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index a416d26..ab30799 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -114,6 +114,37 @@ H5P.init = function (target) { H5P.on(instance, 'xAPI', H5P.xAPICompletedListener); H5P.on(instance, 'xAPI', H5P.externalDispatcher.trigger); + // Auto save current state if supported + if (H5PIntegration.saveFreq !== false && ( + instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function')) { + + var saveTimer, save = function () { + var state = instance.getCurrentState(); + if (state !== undefined) { + H5P.setUserData(contentId, 'state', state, true, true); + } + saveTimer = null; + }; + + if (H5PIntegration.saveFreq) { + // Only run the loop when there's stuff happening (reduces load) + H5P.$body.on('mousedown keydown touchstart', function () { + if (!saveTimer) { + saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000); + } + }); + } + + // xAPI events will schedule a save in three seconds. + H5P.on(instance, 'xAPI', function () { + if (saveTimer) { + clearTimeout(saveTimer); + } + saveTimer = setTimeout(save, 3000); + }); + } + if (H5P.isFramed) { var resizeDelay; if (H5P.externalEmbed === false) { @@ -1512,20 +1543,22 @@ H5P.on = function(instance, eventType, handler) { H5P.init(document.body); } - // Store the current state of the H5P when leaving the page. - H5P.$window.on('unload', function () { - for (var i = 0; i < H5P.instances.length; i++) { - var instance = H5P.instances[i]; - if (instance.getCurrentState instanceof Function || - typeof instance.getCurrentState === 'function') { - var state = instance.getCurrentState(); - if (state !== undefined) { - // Async is not used to prevent the request from being cancelled. - contentUserDataAjax(instance.contentId, 'state', undefined, state, true, true, false); + if (H5PIntegration.saveFreq !== false) { + // Store the current state of the H5P when leaving the page. + H5P.$window.on('unload', function () { + for (var i = 0; i < H5P.instances.length; i++) { + var instance = H5P.instances[i]; + if (instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function') { + var state = instance.getCurrentState(); + if (state !== undefined) { + // Async is not used to prevent the request from being cancelled. + contentUserDataAjax(instance.contentId, 'state', undefined, state, true, true, false); + } } } - } - }); + }); + } }); })(H5P.jQuery); From eee77519b714fd41c2d7355859c1e0614306d05b Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 23 Mar 2015 09:45:02 +0100 Subject: [PATCH 112/149] Always use complete URLs. --- js/h5p-x-api-event.js | 2 +- js/h5p.js | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 141e232..24cb783 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -120,7 +120,7 @@ H5P.XAPIEvent.prototype.setActor = function() { this.data.statement.actor = { 'account': { 'name': uuid, - 'homePage': window.location.origin + H5PIntegration.basePath + 'homePage': H5PIntegration.siteUrl }, 'objectType': 'Agent' }; diff --git a/js/h5p.js b/js/h5p.js index a9c2617..d507f4c 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -234,20 +234,10 @@ H5P.init = function (target) { * @returns {string} HTML */ H5P.getHeadTags = function (contentId) { - var basePath = window.location.protocol + '//' + window.location.host + H5PIntegration.basePath; - - var createUrl = function (path) { - if (path.substring(0,7) !== 'http://' && path.substring(0,8) !== 'https://') { - // Not external, add base path. - path = basePath + path; - } - return path; - }; - var createStyleTags = function (styles) { var tags = ''; for (var i = 0; i < styles.length; i++) { - tags += ''; + tags += ''; } return tags; }; @@ -255,7 +245,7 @@ H5P.getHeadTags = function (contentId) { var createScriptTags = function (scripts) { var tags = ''; for (var i = 0; i < scripts.length; i++) { - tags += ''; + tags += ''; } return tags; }; From cfe73006a21554c94c6ac191ac220528bb8da47c Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 23 Mar 2015 10:53:21 +0100 Subject: [PATCH 113/149] Added back deprecated function. --- js/h5p.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/js/h5p.js b/js/h5p.js index 4223230..01b32d4 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -513,6 +513,19 @@ H5P.getPath = function (path, contentId) { return prefix + '/' + path; }; +/** + * THIS FUNCTION IS DEPRECATED, USE getPath INSTEAD + * Will be remove march 2016. + * + * Find the path to the content files folder based on the id of the content + * + * @param contentId + * Id of the content requesting a path + */ +H5P.getContentPath = function (contentId) { + return H5PIntegration.url + '/content/' + contentId; +}; + /** * Get library class constructor from H5P by classname. * Note that this class will only work for resolve "H5P.NameWithoutDot". From 24fa34f9c29381ad55f72c33457cbeb68e4c37b2 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 24 Mar 2015 11:01:23 +0100 Subject: [PATCH 114/149] Avoid parsing state multiple times. --- js/h5p.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 99c3b1c..d473f66 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -57,6 +57,12 @@ H5P.init = function (target) { if (contentData === undefined) { return H5P.error('No data for content id ' + contentId + '. Perhaps the library is gone?'); } + if (contentData.contentUserDatas && contentData.contentUserDatas.state) { + try { + contentData.contentUserDatas.state = JSON.parse(contentData.contentUserDatas.state); + } + catch (err) {} + } var library = { library: contentData.library, params: JSON.parse(contentData.jsonContent), @@ -612,12 +618,9 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize) { var contentExtrasWrapper; if (library.userDatas && library.userDatas.state) { - try { - contentExtrasWrapper = { - previousState: JSON.parse(library.userDatas.state) - }; - } - catch (err) {} + contentExtrasWrapper = { + previousState: library.userDatas.state + }; } var instance = new constructor(library.params, contentId, contentExtrasWrapper); From c4c2f6b16ab0cde0a7eec24e0cbb52799c29711b Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 24 Mar 2015 16:17:26 +0100 Subject: [PATCH 115/149] Display dialog when content user data is reset. --- js/h5p.js | 23 ++++++++++++++++++++--- styles/h5p.css | 15 +++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index d473f66..7902733 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -58,10 +58,27 @@ H5P.init = function (target) { return H5P.error('No data for content id ' + contentId + '. Perhaps the library is gone?'); } if (contentData.contentUserDatas && contentData.contentUserDatas.state) { - try { - contentData.contentUserDatas.state = JSON.parse(contentData.contentUserDatas.state); + if (contentData.contentUserDatas.state === 'RESET') { + // Content has been reset. Display dialog. + delete contentData.contentUserDatas; + var dialog = new H5P.Dialog('content-user-data-reset', 'Data Reset', '

        ' + H5P.t('contentChanged') + '

        ' + H5P.t('startingOver') + '

        OK
        ', $container); + H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { + $dialog.find('.h5p-dialog-ok-button').click(function () { + dialog.close(); + }).keypress(function (event) { + if (event.which === 32) { + dialog.close(); + } + }); + }); + dialog.open(); + } + else { + try { + contentData.contentUserDatas.state = JSON.parse(contentData.contentUserDatas.state); + } + catch (err) {} } - catch (err) {} } var library = { library: contentData.library, diff --git a/styles/h5p.css b/styles/h5p.css index e6bd6fd..a9ecaf9 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -390,3 +390,18 @@ div.h5p-fullscreen { min-height: 30px; line-height: 30px; } +.h5p-dialog-ok-button { + cursor: default; + float: right; + outline: none; + border: 2px solid #ccc; + padding: 0.25em 0.75em 0.125em; + background: #eee; +} +.h5p-dialog-ok-button:hover, +.h5p-dialog-ok-button:focus { + background: #fafafa; +} +.h5p-dialog-ok-button:active { + background: #eeffee; +} From c746457a872b1ccc20af7e464e27b8d45a50bf7e Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 24 Mar 2015 18:24:07 +0100 Subject: [PATCH 116/149] Make xAPI work in editor --- js/h5p-x-api-event.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 19bc211..0d0290b 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -97,7 +97,7 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { } } else { - if (H5PIntegration.contents['cid-' + instance.contentId].title) { + if (H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + instance.contentId].title) { this.data.statement.object.definition.name = { "en-US": H5P.createH5PTitle(H5PIntegration.contents['cid-' + instance.contentId].title) }; @@ -177,7 +177,7 @@ H5P.XAPIEvent.prototype.getScore = function() { H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) { var xAPIId; - if (instance.contentId) { + if (instance.contentId && H5PIntegration && H5PIntegration.contents) { xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url; if (instance.uuid) { xAPIId += '?uuid=' + instance.uuid; From ef7a31d2e16bd8d667b50d495668e80d23c0859b Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 24 Mar 2015 18:55:13 +0100 Subject: [PATCH 117/149] Try to get back the no url branch --- h5p.classes.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index daa6ead..3bc78d1 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1785,9 +1785,11 @@ class H5PCore { if ($type === 'preloadedCss' && (isset($dependency['dropCss']) && $dependency['dropCss'] === '1')) { return; } + dpm($prefix); + dpm($dependency['path']); foreach ($dependency[$type] as $file) { $assets[] = (object) array( - 'path' => /*$prefix . */$dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), + 'path' => $prefix . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), 'version' => $dependency['version'] ); } @@ -1807,7 +1809,7 @@ class H5PCore { // Add URL prefix if not external if (strpos($asset->path, '://') === FALSE) { - $url = /*$this->url .*/ $url; + $url = $this->url . $url; } // Add version/cache buster if set @@ -1839,7 +1841,6 @@ class H5PCore { $dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']); $dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']); } - $dependency['version'] = "?ver={$dependency['majorVersion']}.{$dependency['minorVersion']}.{$dependency['patchVersion']}"; $this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts'], $prefix); $this->getDependencyAssets($dependency, 'preloadedCss', $files['styles'], $prefix); From 065ee4e8a2bdeb63554dd59c9287f185319d29ef Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 24 Mar 2015 19:29:28 +0100 Subject: [PATCH 118/149] Make editor work again --- h5p-development.class.php | 3 +-- h5p.classes.php | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/h5p-development.class.php b/h5p-development.class.php index 7f33689..491c456 100644 --- a/h5p-development.class.php +++ b/h5p-development.class.php @@ -86,7 +86,7 @@ class H5PDevelopment { $library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']); $this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE); - $library['path'] = $libraryPath; + $library['path'] = $path . '/' . $contents[$i]; $this->libraries[H5PDevelopment::libraryToString($library['machineName'], $library['majorVersion'], $library['minorVersion'])] = $library; } @@ -139,7 +139,6 @@ class H5PDevelopment { */ public function getSemantics($name, $majorVersion, $minorVersion) { $library = H5PDevelopment::libraryToString($name, $majorVersion, $minorVersion); - if (isset($this->libraries[$library]) === FALSE) { return NULL; } diff --git a/h5p.classes.php b/h5p.classes.php index 3bc78d1..6a2795e 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1785,8 +1785,6 @@ class H5PCore { if ($type === 'preloadedCss' && (isset($dependency['dropCss']) && $dependency['dropCss'] === '1')) { return; } - dpm($prefix); - dpm($dependency['path']); foreach ($dependency[$type] as $file) { $assets[] = (object) array( 'path' => $prefix . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), From 61a8e7e9e935d128bf977be8dac19eb3d1e558ba Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 24 Mar 2015 19:36:13 +0100 Subject: [PATCH 119/149] Make view work again as well --- h5p.classes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h5p.classes.php b/h5p.classes.php index 6a2795e..1bef34d 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1787,7 +1787,7 @@ class H5PCore { } foreach ($dependency[$type] as $file) { $assets[] = (object) array( - 'path' => $prefix . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), + 'path' => /*$prefix .*/ $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), 'version' => $dependency['version'] ); } From bf227bdae09ab61dfd26f354a5d86921b13e34cf Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Mar 2015 10:29:34 +0100 Subject: [PATCH 120/149] Moved in changes from Drupal. --- h5p-development.class.php | 10 +- h5p.classes.php | 24 ++- js/h5p-event-dispatcher.js | 68 ++++++++- js/h5p-x-api-event.js | 14 +- js/h5p-x-api.js | 2 +- js/h5p.js | 302 ++++++++++++++++++++++++++++++++++--- 6 files changed, 374 insertions(+), 46 deletions(-) diff --git a/h5p-development.class.php b/h5p-development.class.php index 491c456..1eb793f 100644 --- a/h5p-development.class.php +++ b/h5p-development.class.php @@ -9,7 +9,7 @@ class H5PDevelopment { const MODE_CONTENT = 1; const MODE_LIBRARY = 2; - private $h5pF, $libraries, $language; + private $h5pF, $libraries, $language, $filesPath; /** * Constructor. @@ -23,6 +23,7 @@ class H5PDevelopment { public function __construct($H5PFramework, $filesPath, $language, $libraries = NULL) { $this->h5pF = $H5PFramework; $this->language = $language; + $this->filesPath = $filesPath; if ($libraries !== NULL) { $this->libraries = $libraries; } @@ -86,7 +87,7 @@ class H5PDevelopment { $library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']); $this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE); - $library['path'] = $path . '/' . $contents[$i]; + $library['path'] = 'development/' . $contents[$i]; $this->libraries[H5PDevelopment::libraryToString($library['machineName'], $library['majorVersion'], $library['minorVersion'])] = $library; } @@ -142,8 +143,7 @@ class H5PDevelopment { if (isset($this->libraries[$library]) === FALSE) { return NULL; } - - return $this->getFileContents($this->libraries[$library]['path'] . '/semantics.json'); + return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/semantics.json'); } /** @@ -161,7 +161,7 @@ class H5PDevelopment { return NULL; } - return $this->getFileContents($this->libraries[$library]['path'] . '/language/' . $language . '.json'); + return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/language/' . $language . '.json'); } /** diff --git a/h5p.classes.php b/h5p.classes.php index 1bef34d..9a70cc2 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -247,6 +247,13 @@ interface H5PFrameworkInterface { */ public function updateContent($content, $contentMainId = NULL); + /** + * Resets marked user data for the given content. + * + * @param int $contentId + */ + public function resetContentUserData($contentId); + /** * Save what libraries a library is dependending on * @@ -1599,7 +1606,7 @@ class H5PCore { public static $coreApi = array( 'majorVersion' => 1, - 'minorVersion' => 4 + 'minorVersion' => 5 ); public static $styles = array( 'styles/h5p.css', @@ -1644,7 +1651,7 @@ class H5PCore { $this->development_mode = $development_mode; if ($development_mode & H5PDevelopment::MODE_LIBRARY) { - $this->h5pD = new H5PDevelopment($this->h5pF, $path, $language); + $this->h5pD = new H5PDevelopment($this->h5pF, $path . '/', $language); } } @@ -1662,6 +1669,9 @@ class H5PCore { $content['id'] = $this->h5pF->insertContent($content, $contentMainId); } + // Some user data for content has to be reset when the content changes. + $this->h5pF->resetContentUserData($contentMainId ? $contentMainId : $content['id']); + return $content['id']; } @@ -1787,7 +1797,7 @@ class H5PCore { } foreach ($dependency[$type] as $file) { $assets[] = (object) array( - 'path' => /*$prefix .*/ $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), + 'path' => $prefix . '/' . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), 'version' => $dependency['version'] ); } @@ -1835,7 +1845,7 @@ class H5PCore { ); foreach ($dependencies as $dependency) { if (isset($dependency['path']) === FALSE) { - $dependency['path'] = '/libraries/' . H5PCore::libraryToString($dependency, TRUE); + $dependency['path'] = 'libraries/' . H5PCore::libraryToString($dependency, TRUE); $dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']); $dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']); } @@ -2739,13 +2749,13 @@ class H5PContentValidator { 'type' => 'group', 'fields' => $library['semantics'], ), FALSE); - $validkeys = array('library', 'params', 'uuid'); + $validkeys = array('library', 'params', 'subContentId'); if (isset($semantics->extraAttributes)) { $validkeys = array_merge($validkeys, $semantics->extraAttributes); } $this->filterParams($value, $validkeys); - if (isset($value->uuid) && ! preg_match('/^\{?[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}?$/', $value->uuid)) { - unset($value->uuid); + 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 diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index d67ee10..1d52ef7 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -9,18 +9,55 @@ 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 () { @@ -144,27 +181,42 @@ H5P.EventDispatcher = (function () { * Custom event data(used when event type as string is used as first * argument */ - this.trigger = function (event, eventData) { + this.trigger = function (event, eventData, extras) { if (event === undefined) { return; } if (typeof event === 'string') { - event = new H5P.Event(event, eventData); + event = new H5P.Event(event, eventData, extras); } else if (eventData !== undefined) { event.data = eventData; } - if (triggers[event.type] === undefined) { - return; + + // Check to see if this event should go externally after all triggering and bubbling is done + var scheduledForExternal = event.scheduleForExternal(); + + if (triggers[event.type] !== undefined) { + // Call all listeners + for (var i = 0; i < triggers[event.type].length; i++) { + triggers[event.type][i].listener.call(triggers[event.type][i].thisArg, event); + } } - // Call all listeners - for (var i = 0; i < triggers[event.type].length; i++) { - 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); + } }; } diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 9b30675..a8513a4 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -6,7 +6,7 @@ var H5P = H5P || {}; * @class */ H5P.XAPIEvent = function() { - H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true}); + H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true, external: true}); }; H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype); @@ -87,8 +87,8 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { } } }; - if (instance.uuid) { - this.data.statement.object.definition.extensions['http://h5p.org/x-api/h5p-uuid'] = instance.uuid; + 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 = { @@ -112,8 +112,8 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { * @param {object} instance - the H5P instance */ H5P.XAPIEvent.prototype.setContext = function(instance) { - if (instance.parent && (instance.parent.contentId || instance.parent.uuid)) { - var parentId = instance.parent.uuid === undefined ? instance.parent.contentId : instance.parent.uuid; + 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": [ @@ -179,8 +179,8 @@ H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) { var xAPIId; if (instance.contentId && H5PIntegration && H5PIntegration.contents) { xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url; - if (instance.uuid) { - xAPIId += '?uuid=' + instance.uuid; + if (instance.subContentId) { + xAPIId += '?subContentId=' + instance.subContentId; } } return xAPIId; diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index 9d0e7cd..fa5e51a 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -4,7 +4,7 @@ var H5P = H5P || {}; H5P.externalDispatcher = new H5P.EventDispatcher(); if (H5P.isFramed && H5P.externalEmbed !== true) { - H5P.externalDispatcher.on('xAPI', window.top.H5P.externalDispatcher.trigger); + H5P.externalDispatcher.on('*', window.top.H5P.externalDispatcher.trigger); } // EventDispatcher extensions diff --git a/js/h5p.js b/js/h5p.js index 3425a03..c359c1b 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -62,6 +62,29 @@ H5P.init = function (target) { params: JSON.parse(contentData.jsonContent) }; + H5P.getUserData(contentId, 'state', function (err, previousState) { + if (previousState) { + library.userDatas = { + state: previousState + }; + } + else if (previousState === null) { + // Content has been reset. Display dialog. + delete contentData.contentUserData; + var dialog = new H5P.Dialog('content-user-data-reset', 'Data Reset', '

        ' + H5P.t('contentChanged') + '

        ' + H5P.t('startingOver') + '

        OK
        ', $container); + H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { + $dialog.find('.h5p-dialog-ok-button').click(function () { + dialog.close(); + }).keypress(function (event) { + if (event.which === 32) { + dialog.close(); + } + }); + }); + dialog.open(); + } + }); + // Create new instance. var instance = H5P.newRunnable(library, contentId, $container, true); @@ -111,7 +134,37 @@ H5P.init = function (target) { // Listen for xAPI events. H5P.on(instance, 'xAPI', H5P.xAPICompletedListener); - H5P.on(instance, 'xAPI', H5P.externalDispatcher.trigger); + + // Auto save current state if supported + if (H5PIntegration.saveFreq !== false && ( + instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function')) { + + var saveTimer, save = function () { + var state = instance.getCurrentState(); + if (state !== undefined) { + H5P.setUserData(contentId, 'state', state, undefined, true, true); + } + if (H5PIntegration.saveFreq) { + // Continue autosave + saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000); + } + }; + + if (H5PIntegration.saveFreq) { + // Start autosave + saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000); + } + + // xAPI events will schedule a save in three seconds. + H5P.on(instance, 'xAPI', function (event) { + var verb = event.getVerb(); + if (verb === 'completed' || verb === 'progressed') { + clearTimeout(saveTimer); + saveTimer = setTimeout(save, 3000); + } + }); + } if (H5P.isFramed) { var resizeDelay; @@ -548,9 +601,10 @@ H5P.classFromName = function (name) { * @return {Object} Instance. */ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { - var nameSplit, versionSplit; + var nameSplit, versionSplit, machineName; try { nameSplit = library.library.split(' ', 2); + machineName = nameSplit[0]; versionSplit = nameSplit[1].split('.', 2); } catch (err) { @@ -581,16 +635,23 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { if (extras === undefined) { extras = {}; } - if (library.uuid) { - extras.uuid = library.uuid; + if (library.subContentId) { + extras.subContentId = library.subContentId; } - - // Some old library versions have their own custom third parameter. Make sure we don't send them the extras. They'll interpret it as something else + + if (library.userDatas && library.userDatas.state) { + extras.previousState = library.userDatas.state; + } + + var instance; + // Some old library versions have their own custom third parameter. + // Make sure we don't send them the extras. + // (they will interpret it as something else) if (H5P.jQuery.inArray(library.library, ['H5P.CoursePresentation 1.0', 'H5P.CoursePresentation 1.1', 'H5P.CoursePresentation 1.2', 'H5P.CoursePresentation 1.3']) > -1) { - var instance = new constructor(library.params, contentId); + instance = new constructor(library.params, contentId); } else { - var instance = new constructor(library.params, contentId, extras); + instance = new constructor(library.params, contentId, extras); } if (instance.$ === undefined) { @@ -600,8 +661,8 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { if (instance.contentId === undefined) { instance.contentId = contentId; } - if (instance.uuid === undefined && library.uuid) { - instance.uuid = library.uuid; + if (instance.subContentId === undefined && library.subContentId) { + instance.subContentId = library.subContentId; } if (instance.parent === undefined && extras && extras.parent) { instance.parent = extras.parent; @@ -609,6 +670,11 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { if ($attachTo !== undefined) { instance.attach($attachTo); + H5P.trigger(instance, 'domChanged', { + '$target': $attachTo, + 'library': machineName, + 'key': 'newLibrary' + }, {'bubbles': true, 'external': true}); if (skipResize === undefined || !skipResize) { // Resize content. @@ -1378,10 +1444,10 @@ if (String.prototype.trim === undefined) { * @param {string} eventType * The event type */ -H5P.trigger = function(instance, eventType) { +H5P.trigger = function(instance, eventType, data, extras) { // Try new event system first if (instance.trigger !== undefined) { - instance.trigger(eventType); + instance.trigger(eventType, data, extras); } // Try deprecated event system else if (instance.$ !== undefined && instance.$.trigger !== undefined) { @@ -1415,7 +1481,7 @@ H5P.on = function(instance, eventType, handler) { /** * Create UUID - * + * * @returns {String} UUID */ H5P.createUUID = function() { @@ -1441,9 +1507,209 @@ H5P.createH5PTitle = function(rawTitle, maxLength) { return title; }; -H5P.jQuery(document).ready(function () { - if (!H5P.preventInit) { - // Start script need to be an external resource to load in correct order for IE9. - H5P.init(document.body); +// Wrap in privates +(function ($) { + + /** + * Creates ajax requests for inserting, updateing and deleteing + * content user data. + * + * @private + * @param {number} contentId What content to store the data for. + * @param {string} dataType Identifies the set of data for this content. + * @param {string} subContentId Identifies sub content + * @param {function} [done] Callback when ajax is done. + * @param {object} [data] To be stored for future use. + * @param {boolean} [preload=false] Data is loaded when content is loaded. + * @param {boolean} [invalidate=false] Data is invalidated when content changes. + * @param {boolean} [async=true] + */ + function contentUserDataAjax(contentId, dataType, subContentId, done, data, preload, invalidate, async) { + var options = { + url: H5PIntegration.ajaxPath + 'content-user-data/' + contentId + '/' + dataType + '/' + (subContentId ? subContentId : 0), + dataType: 'json', + async: async === undefined ? true : async + }; + if (data !== undefined) { + options.type = 'POST'; + options.data = { + data: (data === null ? 0 : data), + preload: (preload ? 1 : 0), + invalidate: (invalidate ? 1 : 0) + }; + } + else { + options.type = 'GET'; + } + if (done !== undefined) { + options.error = function (xhr, error) { + done(error); + }; + options.success = function (response) { + if (!response.success) { + done(response.error); + return; + } + + if (response.data === false || response.data === undefined) { + done(); + return; + } + + done(undefined, response.data); + }; + } + + $.ajax(options); } -}); + + /** + * Get user data for given content. + * + * @public + * @param {number} contentId What content to get data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {function} done Callback with error and data parameters. + * @param {string} [subContentId] Identifies which data belongs to sub content. + */ + H5P.getUserData = function (contentId, dataId, done, subContentId) { + if (!subContentId) { + subContentId = 0; // Default + } + + var content = H5PIntegration.contents['cid-' + contentId]; + var preloadedData = content.contentUserData; + if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId]) { + if (preloadedData[subContentId][dataId] === 'RESET') { + done(undefined, null); + return; + } + try { + done(undefined, JSON.parse(preloadedData[subContentId][dataId])); + } + catch (err) { + done(err); + } + } + else { + contentUserDataAjax(contentId, dataId, subContentId, function (err, data) { + if (err || data === undefined) { + done(err, data); + return; // Error or no data + } + + // Cache in preloaded + if (content.contentUserData === undefined) { + content.contentUserData = preloaded = {}; + } + if (preloadedData[subContentId] === undefined) { + preloadedData[subContentId] = {}; + } + preloadedData[subContentId][dataId] = data; + + // Done. Try to decode JSON + try { + done(undefined, JSON.parse(data)); + } + catch (e) { + done(e); + } + }); + } + }; + + /** + * Set user data for given content. + * + * @public + * @param {number} contentId What content to get data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {object} data The data that is to be stored. + * @param {string} [subContentId] Identifies which data belongs to sub content. + * @param {boolean} [preloaded=false] If the data should be loaded when content is loaded. + * @param {boolean} [deleteOnChange=false] If the data should be invalidated when the content changes. + * @param {function} [errorCallback] Callback with error as parameters. + */ + H5P.setUserData = function (contentId, dataId, data, subContentId, preloaded, deleteOnChange, errorCallback, async) { + if (!subContentId) { + subContentId = 0; // Default + } + + try { + data = JSON.stringify(data); + } + catch (err) { + errorCallback(err); + return; // Failed to serialize. + } + + var content = H5PIntegration.contents['cid-' + contentId]; + if (!content.contentUserData) { + content.contentUserData = {}; + } + var preloadedData = content.contentUserData; + if (preloadedData[subContentId] === undefined) { + preloadedData[subContentId] = {}; + } + if (data === preloadedData[subContentId][dataId]) { + return; // No need to save this twice. + } + + preloadedData[subContentId][dataId] = data; + contentUserDataAjax(contentId, dataId, subContentId, function (error, data) { + if (errorCallback && error) { + errorCallback(error); + } + }, data, preloaded, deleteOnChange, async); + }; + + /** + * Delete user data for given content. + * + * @public + * @param {number} contentId What content to remove data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {string} [subContentId] Identifies which data belongs to sub content. + */ + H5P.deleteUserData = function (contentId, dataId, subContentId) { + if (!subContentId) { + subContentId = 0; // Default + } + + // Remove from preloaded/cache + var preloadedData = H5PIntegration.contents['cid-' + contentId].contentUserData; + if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId]) { + delete preloadedData[subContentId][dataId]; + } + + contentUserDataAjax(contentId, dataId, subContentId, undefined, null); + }; + + // Init H5P when page is fully loadded + $(document).ready(function () { + if (!H5P.preventInit) { + // Note that this start script has to be an external resource for it to + // load in correct order in IE9. + H5P.init(document.body); + } + + if (H5PIntegration.saveFreq !== false) { + // Store the current state of the H5P when leaving the page. + H5P.$window.on('beforeunload', function () { + for (var i = 0; i < H5P.instances.length; i++) { + var instance = H5P.instances[i]; + if (instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function') { + var state = instance.getCurrentState(); + if (state !== undefined) { + // Async is not used to prevent the request from being cancelled. + H5P.setUserData(instance.contentId, 'state', state, undefined, true, true, undefined, false); + + } + } + } + }); + } + }); + +})(H5P.jQuery); From 2dfdc04217de662434d73d2864972bb329a4999b Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 27 Mar 2015 11:51:17 +0100 Subject: [PATCH 121/149] Fixed exports with development mode enabled. --- h5p.classes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h5p.classes.php b/h5p.classes.php index 9a70cc2..62f3011 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1499,7 +1499,7 @@ Class H5PExport { $library = $dependency['library']; // Copy library to h5p - $source = isset($library['path']) ? $library['path'] : $h5pDir . 'libraries' . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE); + $source = $h5pDir . (isset($library['path']) ? $library['path'] : 'libraries' . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE)); $destination = $tempPath . DIRECTORY_SEPARATOR . $library['machineName']; $this->h5pC->copyFileTree($source, $destination); From cfa747f20cb45058e395d044f3c284ca1d0fe658 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 7 Apr 2015 19:30:46 +0200 Subject: [PATCH 122/149] Add path fixes --- h5p-development.class.php | 10 +++++----- h5p.classes.php | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/h5p-development.class.php b/h5p-development.class.php index 491c456..1eb793f 100644 --- a/h5p-development.class.php +++ b/h5p-development.class.php @@ -9,7 +9,7 @@ class H5PDevelopment { const MODE_CONTENT = 1; const MODE_LIBRARY = 2; - private $h5pF, $libraries, $language; + private $h5pF, $libraries, $language, $filesPath; /** * Constructor. @@ -23,6 +23,7 @@ class H5PDevelopment { public function __construct($H5PFramework, $filesPath, $language, $libraries = NULL) { $this->h5pF = $H5PFramework; $this->language = $language; + $this->filesPath = $filesPath; if ($libraries !== NULL) { $this->libraries = $libraries; } @@ -86,7 +87,7 @@ class H5PDevelopment { $library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']); $this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE); - $library['path'] = $path . '/' . $contents[$i]; + $library['path'] = 'development/' . $contents[$i]; $this->libraries[H5PDevelopment::libraryToString($library['machineName'], $library['majorVersion'], $library['minorVersion'])] = $library; } @@ -142,8 +143,7 @@ class H5PDevelopment { if (isset($this->libraries[$library]) === FALSE) { return NULL; } - - return $this->getFileContents($this->libraries[$library]['path'] . '/semantics.json'); + return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/semantics.json'); } /** @@ -161,7 +161,7 @@ class H5PDevelopment { return NULL; } - return $this->getFileContents($this->libraries[$library]['path'] . '/language/' . $language . '.json'); + return $this->getFileContents($this->filesPath . $this->libraries[$library]['path'] . '/language/' . $language . '.json'); } /** diff --git a/h5p.classes.php b/h5p.classes.php index 1bef34d..0d28aa0 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1492,7 +1492,7 @@ Class H5PExport { $library = $dependency['library']; // Copy library to h5p - $source = isset($library['path']) ? $library['path'] : $h5pDir . 'libraries' . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE); + $source = $h5pDir . (isset($library['path']) ? $library['path'] : 'libraries' . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE)); $destination = $tempPath . DIRECTORY_SEPARATOR . $library['machineName']; $this->h5pC->copyFileTree($source, $destination); @@ -1644,7 +1644,7 @@ class H5PCore { $this->development_mode = $development_mode; if ($development_mode & H5PDevelopment::MODE_LIBRARY) { - $this->h5pD = new H5PDevelopment($this->h5pF, $path, $language); + $this->h5pD = new H5PDevelopment($this->h5pF, $path . '/', $language); } } @@ -1787,7 +1787,7 @@ class H5PCore { } foreach ($dependency[$type] as $file) { $assets[] = (object) array( - 'path' => /*$prefix .*/ $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), + 'path' => $prefix . '/' . $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file), 'version' => $dependency['version'] ); } @@ -1835,7 +1835,7 @@ class H5PCore { ); foreach ($dependencies as $dependency) { if (isset($dependency['path']) === FALSE) { - $dependency['path'] = '/libraries/' . H5PCore::libraryToString($dependency, TRUE); + $dependency['path'] = 'libraries/' . H5PCore::libraryToString($dependency, TRUE); $dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']); $dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']); } From efbe56001ddb332e674f2122374e07ad8d4f0ed0 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 7 Apr 2015 19:32:44 +0200 Subject: [PATCH 123/149] User data fixes --- h5p.classes.php | 10 ++ js/h5p.js | 302 +++++++++++++++++++++++++++++++++++++++++++++--- styles/h5p.css | 15 +++ 3 files changed, 309 insertions(+), 18 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 0d28aa0..681cb23 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -247,6 +247,13 @@ interface H5PFrameworkInterface { */ public function updateContent($content, $contentMainId = NULL); + /** + * Resets marked user data for the given content. + * + * @param int $contentId + */ + public function resetContentUserData($contentId); + /** * Save what libraries a library is dependending on * @@ -1662,6 +1669,9 @@ class H5PCore { $content['id'] = $this->h5pF->insertContent($content, $contentMainId); } + // Some user data for content has to be reset when the content changes. + $this->h5pF->resetContentUserData($contentMainId ? $contentMainId : $content['id']); + return $content['id']; } diff --git a/js/h5p.js b/js/h5p.js index 3425a03..c359c1b 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -62,6 +62,29 @@ H5P.init = function (target) { params: JSON.parse(contentData.jsonContent) }; + H5P.getUserData(contentId, 'state', function (err, previousState) { + if (previousState) { + library.userDatas = { + state: previousState + }; + } + else if (previousState === null) { + // Content has been reset. Display dialog. + delete contentData.contentUserData; + var dialog = new H5P.Dialog('content-user-data-reset', 'Data Reset', '

        ' + H5P.t('contentChanged') + '

        ' + H5P.t('startingOver') + '

        OK
        ', $container); + H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { + $dialog.find('.h5p-dialog-ok-button').click(function () { + dialog.close(); + }).keypress(function (event) { + if (event.which === 32) { + dialog.close(); + } + }); + }); + dialog.open(); + } + }); + // Create new instance. var instance = H5P.newRunnable(library, contentId, $container, true); @@ -111,7 +134,37 @@ H5P.init = function (target) { // Listen for xAPI events. H5P.on(instance, 'xAPI', H5P.xAPICompletedListener); - H5P.on(instance, 'xAPI', H5P.externalDispatcher.trigger); + + // Auto save current state if supported + if (H5PIntegration.saveFreq !== false && ( + instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function')) { + + var saveTimer, save = function () { + var state = instance.getCurrentState(); + if (state !== undefined) { + H5P.setUserData(contentId, 'state', state, undefined, true, true); + } + if (H5PIntegration.saveFreq) { + // Continue autosave + saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000); + } + }; + + if (H5PIntegration.saveFreq) { + // Start autosave + saveTimer = setTimeout(save, H5PIntegration.saveFreq * 1000); + } + + // xAPI events will schedule a save in three seconds. + H5P.on(instance, 'xAPI', function (event) { + var verb = event.getVerb(); + if (verb === 'completed' || verb === 'progressed') { + clearTimeout(saveTimer); + saveTimer = setTimeout(save, 3000); + } + }); + } if (H5P.isFramed) { var resizeDelay; @@ -548,9 +601,10 @@ H5P.classFromName = function (name) { * @return {Object} Instance. */ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { - var nameSplit, versionSplit; + var nameSplit, versionSplit, machineName; try { nameSplit = library.library.split(' ', 2); + machineName = nameSplit[0]; versionSplit = nameSplit[1].split('.', 2); } catch (err) { @@ -581,16 +635,23 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { if (extras === undefined) { extras = {}; } - if (library.uuid) { - extras.uuid = library.uuid; + if (library.subContentId) { + extras.subContentId = library.subContentId; } - - // Some old library versions have their own custom third parameter. Make sure we don't send them the extras. They'll interpret it as something else + + if (library.userDatas && library.userDatas.state) { + extras.previousState = library.userDatas.state; + } + + var instance; + // Some old library versions have their own custom third parameter. + // Make sure we don't send them the extras. + // (they will interpret it as something else) if (H5P.jQuery.inArray(library.library, ['H5P.CoursePresentation 1.0', 'H5P.CoursePresentation 1.1', 'H5P.CoursePresentation 1.2', 'H5P.CoursePresentation 1.3']) > -1) { - var instance = new constructor(library.params, contentId); + instance = new constructor(library.params, contentId); } else { - var instance = new constructor(library.params, contentId, extras); + instance = new constructor(library.params, contentId, extras); } if (instance.$ === undefined) { @@ -600,8 +661,8 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { if (instance.contentId === undefined) { instance.contentId = contentId; } - if (instance.uuid === undefined && library.uuid) { - instance.uuid = library.uuid; + if (instance.subContentId === undefined && library.subContentId) { + instance.subContentId = library.subContentId; } if (instance.parent === undefined && extras && extras.parent) { instance.parent = extras.parent; @@ -609,6 +670,11 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { if ($attachTo !== undefined) { instance.attach($attachTo); + H5P.trigger(instance, 'domChanged', { + '$target': $attachTo, + 'library': machineName, + 'key': 'newLibrary' + }, {'bubbles': true, 'external': true}); if (skipResize === undefined || !skipResize) { // Resize content. @@ -1378,10 +1444,10 @@ if (String.prototype.trim === undefined) { * @param {string} eventType * The event type */ -H5P.trigger = function(instance, eventType) { +H5P.trigger = function(instance, eventType, data, extras) { // Try new event system first if (instance.trigger !== undefined) { - instance.trigger(eventType); + instance.trigger(eventType, data, extras); } // Try deprecated event system else if (instance.$ !== undefined && instance.$.trigger !== undefined) { @@ -1415,7 +1481,7 @@ H5P.on = function(instance, eventType, handler) { /** * Create UUID - * + * * @returns {String} UUID */ H5P.createUUID = function() { @@ -1441,9 +1507,209 @@ H5P.createH5PTitle = function(rawTitle, maxLength) { return title; }; -H5P.jQuery(document).ready(function () { - if (!H5P.preventInit) { - // Start script need to be an external resource to load in correct order for IE9. - H5P.init(document.body); +// Wrap in privates +(function ($) { + + /** + * Creates ajax requests for inserting, updateing and deleteing + * content user data. + * + * @private + * @param {number} contentId What content to store the data for. + * @param {string} dataType Identifies the set of data for this content. + * @param {string} subContentId Identifies sub content + * @param {function} [done] Callback when ajax is done. + * @param {object} [data] To be stored for future use. + * @param {boolean} [preload=false] Data is loaded when content is loaded. + * @param {boolean} [invalidate=false] Data is invalidated when content changes. + * @param {boolean} [async=true] + */ + function contentUserDataAjax(contentId, dataType, subContentId, done, data, preload, invalidate, async) { + var options = { + url: H5PIntegration.ajaxPath + 'content-user-data/' + contentId + '/' + dataType + '/' + (subContentId ? subContentId : 0), + dataType: 'json', + async: async === undefined ? true : async + }; + if (data !== undefined) { + options.type = 'POST'; + options.data = { + data: (data === null ? 0 : data), + preload: (preload ? 1 : 0), + invalidate: (invalidate ? 1 : 0) + }; + } + else { + options.type = 'GET'; + } + if (done !== undefined) { + options.error = function (xhr, error) { + done(error); + }; + options.success = function (response) { + if (!response.success) { + done(response.error); + return; + } + + if (response.data === false || response.data === undefined) { + done(); + return; + } + + done(undefined, response.data); + }; + } + + $.ajax(options); } -}); + + /** + * Get user data for given content. + * + * @public + * @param {number} contentId What content to get data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {function} done Callback with error and data parameters. + * @param {string} [subContentId] Identifies which data belongs to sub content. + */ + H5P.getUserData = function (contentId, dataId, done, subContentId) { + if (!subContentId) { + subContentId = 0; // Default + } + + var content = H5PIntegration.contents['cid-' + contentId]; + var preloadedData = content.contentUserData; + if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId]) { + if (preloadedData[subContentId][dataId] === 'RESET') { + done(undefined, null); + return; + } + try { + done(undefined, JSON.parse(preloadedData[subContentId][dataId])); + } + catch (err) { + done(err); + } + } + else { + contentUserDataAjax(contentId, dataId, subContentId, function (err, data) { + if (err || data === undefined) { + done(err, data); + return; // Error or no data + } + + // Cache in preloaded + if (content.contentUserData === undefined) { + content.contentUserData = preloaded = {}; + } + if (preloadedData[subContentId] === undefined) { + preloadedData[subContentId] = {}; + } + preloadedData[subContentId][dataId] = data; + + // Done. Try to decode JSON + try { + done(undefined, JSON.parse(data)); + } + catch (e) { + done(e); + } + }); + } + }; + + /** + * Set user data for given content. + * + * @public + * @param {number} contentId What content to get data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {object} data The data that is to be stored. + * @param {string} [subContentId] Identifies which data belongs to sub content. + * @param {boolean} [preloaded=false] If the data should be loaded when content is loaded. + * @param {boolean} [deleteOnChange=false] If the data should be invalidated when the content changes. + * @param {function} [errorCallback] Callback with error as parameters. + */ + H5P.setUserData = function (contentId, dataId, data, subContentId, preloaded, deleteOnChange, errorCallback, async) { + if (!subContentId) { + subContentId = 0; // Default + } + + try { + data = JSON.stringify(data); + } + catch (err) { + errorCallback(err); + return; // Failed to serialize. + } + + var content = H5PIntegration.contents['cid-' + contentId]; + if (!content.contentUserData) { + content.contentUserData = {}; + } + var preloadedData = content.contentUserData; + if (preloadedData[subContentId] === undefined) { + preloadedData[subContentId] = {}; + } + if (data === preloadedData[subContentId][dataId]) { + return; // No need to save this twice. + } + + preloadedData[subContentId][dataId] = data; + contentUserDataAjax(contentId, dataId, subContentId, function (error, data) { + if (errorCallback && error) { + errorCallback(error); + } + }, data, preloaded, deleteOnChange, async); + }; + + /** + * Delete user data for given content. + * + * @public + * @param {number} contentId What content to remove data for. + * @param {string} dataId Identifies the set of data for this content. + * @param {string} [subContentId] Identifies which data belongs to sub content. + */ + H5P.deleteUserData = function (contentId, dataId, subContentId) { + if (!subContentId) { + subContentId = 0; // Default + } + + // Remove from preloaded/cache + var preloadedData = H5PIntegration.contents['cid-' + contentId].contentUserData; + if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId]) { + delete preloadedData[subContentId][dataId]; + } + + contentUserDataAjax(contentId, dataId, subContentId, undefined, null); + }; + + // Init H5P when page is fully loadded + $(document).ready(function () { + if (!H5P.preventInit) { + // Note that this start script has to be an external resource for it to + // load in correct order in IE9. + H5P.init(document.body); + } + + if (H5PIntegration.saveFreq !== false) { + // Store the current state of the H5P when leaving the page. + H5P.$window.on('beforeunload', function () { + for (var i = 0; i < H5P.instances.length; i++) { + var instance = H5P.instances[i]; + if (instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function') { + var state = instance.getCurrentState(); + if (state !== undefined) { + // Async is not used to prevent the request from being cancelled. + H5P.setUserData(instance.contentId, 'state', state, undefined, true, true, undefined, false); + + } + } + } + }); + } + }); + +})(H5P.jQuery); diff --git a/styles/h5p.css b/styles/h5p.css index e6bd6fd..a9ecaf9 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -390,3 +390,18 @@ div.h5p-fullscreen { min-height: 30px; line-height: 30px; } +.h5p-dialog-ok-button { + cursor: default; + float: right; + outline: none; + border: 2px solid #ccc; + padding: 0.25em 0.75em 0.125em; + background: #eee; +} +.h5p-dialog-ok-button:hover, +.h5p-dialog-ok-button:focus { + background: #fafafa; +} +.h5p-dialog-ok-button:active { + background: #eeffee; +} From 5c3620c637085a5dcdcaa6ddc8e845a905a4b5ec Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 7 Apr 2015 19:33:21 +0200 Subject: [PATCH 124/149] Bubbling for event system --- h5p.classes.php | 8 ++--- js/h5p-event-dispatcher.js | 68 +++++++++++++++++++++++++++++++++----- js/h5p-x-api-event.js | 14 ++++---- js/h5p-x-api.js | 2 +- 4 files changed, 72 insertions(+), 20 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 681cb23..62f3011 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1606,7 +1606,7 @@ class H5PCore { public static $coreApi = array( 'majorVersion' => 1, - 'minorVersion' => 4 + 'minorVersion' => 5 ); public static $styles = array( 'styles/h5p.css', @@ -2749,13 +2749,13 @@ class H5PContentValidator { 'type' => 'group', 'fields' => $library['semantics'], ), FALSE); - $validkeys = array('library', 'params', 'uuid'); + $validkeys = array('library', 'params', 'subContentId'); if (isset($semantics->extraAttributes)) { $validkeys = array_merge($validkeys, $semantics->extraAttributes); } $this->filterParams($value, $validkeys); - if (isset($value->uuid) && ! preg_match('/^\{?[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}?$/', $value->uuid)) { - unset($value->uuid); + 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 diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index d67ee10..1d52ef7 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -9,18 +9,55 @@ 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 () { @@ -144,27 +181,42 @@ H5P.EventDispatcher = (function () { * Custom event data(used when event type as string is used as first * argument */ - this.trigger = function (event, eventData) { + this.trigger = function (event, eventData, extras) { if (event === undefined) { return; } if (typeof event === 'string') { - event = new H5P.Event(event, eventData); + event = new H5P.Event(event, eventData, extras); } else if (eventData !== undefined) { event.data = eventData; } - if (triggers[event.type] === undefined) { - return; + + // Check to see if this event should go externally after all triggering and bubbling is done + var scheduledForExternal = event.scheduleForExternal(); + + if (triggers[event.type] !== undefined) { + // Call all listeners + for (var i = 0; i < triggers[event.type].length; i++) { + triggers[event.type][i].listener.call(triggers[event.type][i].thisArg, event); + } } - // Call all listeners - for (var i = 0; i < triggers[event.type].length; i++) { - 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); + } }; } diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index 9b30675..a8513a4 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -6,7 +6,7 @@ var H5P = H5P || {}; * @class */ H5P.XAPIEvent = function() { - H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true}); + H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true, external: true}); }; H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype); @@ -87,8 +87,8 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { } } }; - if (instance.uuid) { - this.data.statement.object.definition.extensions['http://h5p.org/x-api/h5p-uuid'] = instance.uuid; + 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 = { @@ -112,8 +112,8 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { * @param {object} instance - the H5P instance */ H5P.XAPIEvent.prototype.setContext = function(instance) { - if (instance.parent && (instance.parent.contentId || instance.parent.uuid)) { - var parentId = instance.parent.uuid === undefined ? instance.parent.contentId : instance.parent.uuid; + 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": [ @@ -179,8 +179,8 @@ H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) { var xAPIId; if (instance.contentId && H5PIntegration && H5PIntegration.contents) { xAPIId = H5PIntegration.contents['cid-' + instance.contentId].url; - if (instance.uuid) { - xAPIId += '?uuid=' + instance.uuid; + if (instance.subContentId) { + xAPIId += '?subContentId=' + instance.subContentId; } } return xAPIId; diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index 9d0e7cd..fa5e51a 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -4,7 +4,7 @@ var H5P = H5P || {}; H5P.externalDispatcher = new H5P.EventDispatcher(); if (H5P.isFramed && H5P.externalEmbed !== true) { - H5P.externalDispatcher.on('xAPI', window.top.H5P.externalDispatcher.trigger); + H5P.externalDispatcher.on('*', window.top.H5P.externalDispatcher.trigger); } // EventDispatcher extensions From 5b55b78ded471d670f7145bf10eb4017c3e50b7b Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 7 Apr 2015 19:52:18 +0200 Subject: [PATCH 125/149] createH5PTitle->createTitle --- js/h5p-x-api-event.js | 2 +- js/h5p.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p-x-api-event.js b/js/h5p-x-api-event.js index a8513a4..d26a3b7 100644 --- a/js/h5p-x-api-event.js +++ b/js/h5p-x-api-event.js @@ -99,7 +99,7 @@ H5P.XAPIEvent.prototype.setObject = function(instance) { else { if (H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + instance.contentId].title) { this.data.statement.object.definition.name = { - "en-US": H5P.createH5PTitle(H5PIntegration.contents['cid-' + instance.contentId].title) + "en-US": H5P.createTitle(H5PIntegration.contents['cid-' + instance.contentId].title) }; } } diff --git a/js/h5p.js b/js/h5p.js index c359c1b..918b0c5 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1491,7 +1491,7 @@ H5P.createUUID = function() { }); }; -H5P.createH5PTitle = function(rawTitle, maxLength) { +H5P.createTitle = function(rawTitle, maxLength) { if (maxLength === undefined) { maxLength = 60; } From 5b36e468acc3fa9d77258a5ba6ed5aef3f473396 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Thu, 9 Apr 2015 14:00:00 +0200 Subject: [PATCH 126/149] Rewrite so that we add options as an object instead of a very long list of parameters --- js/h5p.js | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 918b0c5..15889b7 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -143,7 +143,7 @@ H5P.init = function (target) { var saveTimer, save = function () { var state = instance.getCurrentState(); if (state !== undefined) { - H5P.setUserData(contentId, 'state', state, undefined, true, true); + H5P.setUserData(contentId, 'state', state, {deleteOnChange: true}); } if (H5PIntegration.saveFreq) { // Continue autosave @@ -1625,21 +1625,28 @@ H5P.createTitle = function(rawTitle, maxLength) { * @param {number} contentId What content to get data for. * @param {string} dataId Identifies the set of data for this content. * @param {object} data The data that is to be stored. - * @param {string} [subContentId] Identifies which data belongs to sub content. - * @param {boolean} [preloaded=false] If the data should be loaded when content is loaded. - * @param {boolean} [deleteOnChange=false] If the data should be invalidated when the content changes. - * @param {function} [errorCallback] Callback with error as parameters. + * @param {object} extras - object holding the following properties: + * - {string} [subContentId] Identifies which data belongs to sub content. + * - {boolean} [preloaded=true] If the data should be loaded when content is loaded. + * - {boolean} [deleteOnChange=false] If the data should be invalidated when the content changes. + * - {function} [errorCallback] Callback with error as parameters. + * - {boolean} [async=true] */ - H5P.setUserData = function (contentId, dataId, data, subContentId, preloaded, deleteOnChange, errorCallback, async) { - if (!subContentId) { - subContentId = 0; // Default - } + H5P.setUserData = function (contentId, dataId, data, extras) { + var options = H5P.jQuery.extend(true, {}, { + subContentId: 0, + preloaded: true, + deleteOnChange: false, + async: true + }, extras); try { data = JSON.stringify(data); } catch (err) { - errorCallback(err); + if (options.errorCallback) { + options.errorCallback(err); + } return; // Failed to serialize. } @@ -1648,19 +1655,19 @@ H5P.createTitle = function(rawTitle, maxLength) { content.contentUserData = {}; } var preloadedData = content.contentUserData; - if (preloadedData[subContentId] === undefined) { - preloadedData[subContentId] = {}; + if (preloadedData[options.subContentId] === undefined) { + preloadedData[options.subContentId] = {}; } - if (data === preloadedData[subContentId][dataId]) { + if (data === preloadedData[options.subContentId][dataId]) { return; // No need to save this twice. } - preloadedData[subContentId][dataId] = data; - contentUserDataAjax(contentId, dataId, subContentId, function (error, data) { - if (errorCallback && error) { - errorCallback(error); + preloadedData[options.subContentId][dataId] = data; + contentUserDataAjax(contentId, dataId, options.subContentId, function (error, data) { + if (options.errorCallback && error) { + options.errorCallback(error); } - }, data, preloaded, deleteOnChange, async); + }, data, options.preloaded, options.deleteOnChange, options.async); }; /** @@ -1703,7 +1710,7 @@ H5P.createTitle = function(rawTitle, maxLength) { var state = instance.getCurrentState(); if (state !== undefined) { // Async is not used to prevent the request from being cancelled. - H5P.setUserData(instance.contentId, 'state', state, undefined, true, true, undefined, false); + H5P.setUserData(instance.contentId, 'state', state, {deleteOnChange: true, async: false}); } } From 497301d6adc34088244d5f8269739c0494de0bc9 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Thu, 9 Apr 2015 19:42:05 +0200 Subject: [PATCH 127/149] Add triggerXAPIScored and deprecate triggerXAPICompleted --- js/h5p-x-api.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index fa5e51a..ef7ccbd 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -51,11 +51,25 @@ H5P.EventDispatcher.prototype.createXAPIEventTemplate = function(verb, extra) { /** * 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) { - var event = this.createXAPIEventTemplate('completed'); + this.triggerXAPIScored(score, maxScore, 'completed'); +}; + +/** + * Helper function to create scored xAPI events + * + * + * @param {int} score - will be set as the 'raw' value of the score object + * @param {int} maxScore - will be set as the "max" value of the score object + * @param {string} verb - short form of adl verb + */ +H5P.EventDispatcher.prototype.triggerXAPIScored = function(score, maxScore, verb) { + var event = this.createXAPIEventTemplate(verb); event.setScoredResult(score, maxScore); this.trigger(event); }; From 868c06929db2504f8e801d919f1b9b5383d8fb49 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 13 Apr 2015 18:41:53 +0200 Subject: [PATCH 128/149] Whitespace changes --- h5p.classes.php | 1 + js/h5p.js | 1 + 2 files changed, 2 insertions(+) diff --git a/h5p.classes.php b/h5p.classes.php index 76ef4f4..f63d706 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -15,6 +15,7 @@ interface H5PFrameworkInterface { */ public function getPlatformInfo(); + /** * Fetches a file from a remote server using HTTP GET * diff --git a/js/h5p.js b/js/h5p.js index 7b0e1d3..8f06199 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -141,6 +141,7 @@ H5P.init = function (target) { }); }); } + if (!(contentData.disable & H5P.DISABLE_ABOUT)) { // Add about H5P button icon H5P.jQuery('
      • ').appendTo($actions); From 0b609e882de6e34f945fa514c81faf8c29d059e5 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 13 Apr 2015 19:58:41 +0200 Subject: [PATCH 129/149] Add about to disable sources and make sure each source is set --- h5p.classes.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index f63d706..ecff6b4 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2351,18 +2351,21 @@ class H5PCore { */ public static function getDisable(&$sources) { $disable = H5PCore::DISABLE_NONE; - if (!$sources['frame']) { + if (!isset($sources['frame']) || !$sources['frame']) { $disable |= H5PCore::DISABLE_FRAME; } - if (!$sources['download']) { + if (!isset($sources['download']) || !$sources['download']) { $disable |= H5PCore::DISABLE_DOWNLOAD; } - if (!$sources['copyright']) { + if (!isset($sources['copyright']) || !$sources['copyright']) { $disable |= H5PCore::DISABLE_COPYRIGHT; } - if (!$sources['embed']) { + if (!isset($sources['embed']) || !$sources['embed']) { $disable |= H5PCore::DISABLE_EMBED; } + if (!isset($sources['about']) || !$sources['about']) { + $disable |= H5PCore::DISABLE_ABOUT; + } return $disable; } From ba2355c2ecb8a48b294d4bbeea04246f803aee6a Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 13 Apr 2015 20:30:40 +0200 Subject: [PATCH 130/149] Make sure state is ignored when disabled --- js/h5p.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 8f06199..4fa1486 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -90,7 +90,7 @@ H5P.init = function (target) { state: previousState }; } - else if (previousState === null) { + else if (previousState === null && H5PIntegration.saveFreq) { // Content has been reset. Display dialog. delete contentData.contentUserData; var dialog = new H5P.Dialog('content-user-data-reset', 'Data Reset', '

        ' + H5P.t('contentChanged') + '

        ' + H5P.t('startingOver') + '

        OK
        ', $container); From 9a92a6148401bf65ce16b0a181c97cee255d895c Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Mon, 13 Apr 2015 20:31:21 +0200 Subject: [PATCH 131/149] Make sure state is ignored when disabled --- js/h5p.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 4fa1486..146a686 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -672,7 +672,7 @@ H5P.newRunnable = function (library, contentId, $attachTo, skipResize, extras) { extras.subContentId = library.subContentId; } - if (library.userDatas && library.userDatas.state) { + if (library.userDatas && library.userDatas.state && H5PIntegration.saveFreq) { extras.previousState = library.userDatas.state; } From 80a80aef7a66816a6454c0f8b3953eb054481f7f Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 14 Apr 2015 14:01:14 +0200 Subject: [PATCH 132/149] Better debugging --- js/h5p-content-upgrade-process.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/js/h5p-content-upgrade-process.js b/js/h5p-content-upgrade-process.js index a7cc2cc..e86a66a 100644 --- a/js/h5p-content-upgrade-process.js +++ b/js/h5p-content-upgrade-process.js @@ -115,6 +115,11 @@ H5P.ContentUpgradeProcess = (function (Version) { }); } catch (err) { + if (console && console.log) { + console.log("Error", err.stack); + console.log("Error", err.name); + console.log("Error", err.message); + } next(err); } } From 09c6d5c4f8ad305c787a9c2208a0501b32c2304e Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Tue, 14 Apr 2015 14:01:28 +0200 Subject: [PATCH 133/149] Handle cases where a field don't have params --- js/h5p-content-upgrade-process.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/h5p-content-upgrade-process.js b/js/h5p-content-upgrade-process.js index e86a66a..94a6809 100644 --- a/js/h5p-content-upgrade-process.js +++ b/js/h5p-content-upgrade-process.js @@ -193,12 +193,14 @@ H5P.ContentUpgradeProcess = (function (Version) { else { // Go through all fields in the group asyncSerial(field.fields, function (index, subField, next) { - self.processField(subField, params[subField.name], function (err, upgradedParams) { + 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); }); From cf7ae066ade8384ba29f50efea30ec715b9fa4f2 Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Wed, 15 Apr 2015 13:54:46 +0200 Subject: [PATCH 134/149] Add disable support for uploads as well --- h5p.classes.php | 10 +++++++--- js/h5p.js | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index ecff6b4..95ec6ff 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1240,7 +1240,7 @@ class H5PStorage { * TRUE if one or more libraries were updated * FALSE otherwise */ - public function savePackage($content = NULL, $contentMainId = NULL, $skipContent = FALSE) { + public function savePackage($content = NULL, $contentMainId = NULL, $skipContent = FALSE, $options = array()) { if ($this->h5pF->mayUpdateLibraries()) { // Save the libraries we processed during validation $this->saveLibraries(); @@ -1268,6 +1268,10 @@ class H5PStorage { } $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; @@ -1413,9 +1417,9 @@ class H5PStorage { * TRUE if one or more libraries were updated * FALSE otherwise */ - public function updatePackage($contentId, $contentMainId = NULL) { + public function updatePackage($contentId, $contentMainId = NULL, $options) { $this->deletePackage($contentId); - return $this->savePackage($contentId, $contentMainId); + return $this->savePackage($contentId, $contentMainId, FALSE, $options); } /** diff --git a/js/h5p.js b/js/h5p.js index 146a686..f8efddb 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1525,6 +1525,9 @@ H5P.createUUID = function() { }; H5P.createTitle = function(rawTitle, maxLength) { + if (!rawTitle) { + return ''; + } if (maxLength === undefined) { maxLength = 60; } From 165db6ff6c86e05513f085d3751dee61eebe440c Mon Sep 17 00:00:00 2001 From: Svein-Tore Griff With Date: Thu, 16 Apr 2015 20:14:53 +0200 Subject: [PATCH 135/149] externalEmbed is either false or undefined it seems --- js/h5p-x-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index ef7ccbd..14cbb7e 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -3,7 +3,7 @@ var H5P = H5P || {}; // Create object where external code may register and listen for H5P Events H5P.externalDispatcher = new H5P.EventDispatcher(); -if (H5P.isFramed && H5P.externalEmbed !== true) { +if (H5P.isFramed && H5P.externalEmbed === false) { H5P.externalDispatcher.on('*', window.top.H5P.externalDispatcher.trigger); } From fba8badf1a303329b9498c3e06ea7da535e21b60 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 20 Apr 2015 10:30:52 +0200 Subject: [PATCH 136/149] Use URL template to support URLs of different formatting. --- js/h5p.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index f8efddb..42b28f9 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1562,7 +1562,7 @@ H5P.createTitle = function(rawTitle, maxLength) { */ function contentUserDataAjax(contentId, dataType, subContentId, done, data, preload, invalidate, async) { var options = { - url: H5PIntegration.ajaxPath + 'content-user-data/' + contentId + '/' + dataType + '/' + (subContentId ? subContentId : 0), + url: H5PIntegration.ajax.contentUserData.replace(':contentId', contentId).replace(':dataType', dataType).replace(':subContentId', subContentId ? subContentId : 0), dataType: 'json', async: async === undefined ? true : async }; @@ -1680,7 +1680,7 @@ H5P.createTitle = function(rawTitle, maxLength) { data = JSON.stringify(data); } catch (err) { - if (options.errorCallback) { + if (options.errorCallback) { options.errorCallback(err); } return; // Failed to serialize. @@ -1747,7 +1747,7 @@ H5P.createTitle = function(rawTitle, maxLength) { if (state !== undefined) { // Async is not used to prevent the request from being cancelled. H5P.setUserData(instance.contentId, 'state', state, {deleteOnChange: true, async: false}); - + } } } From 8c68f23823c81cea341b67a7bab1f65fe51272bf Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 20 Apr 2015 15:10:31 +0200 Subject: [PATCH 137/149] Remove empty params. --- h5p.classes.php | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 95ec6ff..1a3544b 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1268,7 +1268,7 @@ class H5PStorage { } $content['params'] = file_get_contents($current_path . DIRECTORY_SEPARATOR . 'content.json'); - + if (isset($options['disable'])) { $content['disable'] = $options['disable']; } @@ -2652,10 +2652,17 @@ class H5PContentValidator { // Validate each element in list. foreach ($list as $key => &$value) { if (!is_int($key)) { - unset($list[$key]); + array_splice($list, $key, 1); continue; } $this->$function($value, $field); + if ($value === NULL) { + array_splice($list, $key, 1); + } + } + + if (count($list) === 0) { + $list = NULL; } } @@ -2765,6 +2772,9 @@ class H5PContentValidator { if ($found) { if ($function) { $this->$function($value, $field); + if ($value === NULL) { + unset($group->$key); + } } else { // We have a field type in semantics for which we don't have a @@ -2800,9 +2810,13 @@ class H5PContentValidator { * Will recurse into validating the library's semantics too. */ public function validateLibrary(&$value, $semantics) { - if (!isset($value->library) || !in_array($value->library, $semantics->options)) { + if (!isset($value->library)) { + $value = NULL; + return; + } + if (!in_array($value->library, $semantics->options)) { $this->h5pF->setErrorMessage($this->h5pF->t('Library used in content is not a valid library according to semantics')); - $value = new stdClass(); + $value = NULL; return; } From 59bb32fa44066efec0e33ab8feed569c2c78ff71 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 20 Apr 2015 15:37:49 +0200 Subject: [PATCH 138/149] Create a spaceholder when resizing iframes. --- js/h5p-resizer.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js index 6318fc8..3d83db6 100644 --- a/js/h5p-resizer.js +++ b/js/h5p-resizer.js @@ -46,6 +46,11 @@ */ 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'; @@ -64,6 +69,7 @@ actionHandlers.resize = function (iframe, data, respond) { // Resize iframe so all content is visible. iframe.style.height = data.height + 'px'; + iframe.nextSibling.remove(); }; /** From 28f8a8dc8a4b15446c486c1b438dd2f078cdbbd9 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 20 Apr 2015 15:37:49 +0200 Subject: [PATCH 139/149] Create a spaceholder when resizing iframes. --- js/h5p-resizer.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js index 6318fc8..3d83db6 100644 --- a/js/h5p-resizer.js +++ b/js/h5p-resizer.js @@ -46,6 +46,11 @@ */ 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'; @@ -64,6 +69,7 @@ actionHandlers.resize = function (iframe, data, respond) { // Resize iframe so all content is visible. iframe.style.height = data.height + 'px'; + iframe.nextSibling.remove(); }; /** From 8433f654af1ff9fee495d492d66e6de98f9d7e75 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 21 Apr 2015 13:56:24 +0200 Subject: [PATCH 140/149] Remove element from DOM the correct way. --- js/h5p-resizer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js index 3d83db6..894951b 100644 --- a/js/h5p-resizer.js +++ b/js/h5p-resizer.js @@ -46,7 +46,7 @@ */ 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'; @@ -69,7 +69,7 @@ actionHandlers.resize = function (iframe, data, respond) { // Resize iframe so all content is visible. iframe.style.height = data.height + 'px'; - iframe.nextSibling.remove(); + iframe.parentNode.removeChild(iframe.nextSibling); }; /** From 27a0a332988155f1846aae8cefc407bf8275cc59 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 21 Apr 2015 15:08:59 +0200 Subject: [PATCH 141/149] Prevent running multiple resizer. --- js/h5p-resizer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js index 894951b..bbfb009 100644 --- a/js/h5p-resizer.js +++ b/js/h5p-resizer.js @@ -1,8 +1,9 @@ // H5P iframe Resizer (function () { - if (!window.postMessage || !window.addEventListener) { + if (!window.postMessage || !window.addEventListener || window.h5pResizerInitialized) { return; // Not supported } + window.h5pResizerInitialized = true; // Map actions to handlers var actionHandlers = {}; From 10918905966344220c8832ec30ed9303422b83f2 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 21 Apr 2015 16:22:51 +0200 Subject: [PATCH 142/149] Fixed fullscreen checker. --- js/h5p.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 42b28f9..2978239 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -67,7 +67,7 @@ H5P.init = function (target) { // Determine if we can use full screen if (H5P.canHasFullScreen === undefined) { - H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? (document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled) : true; + H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? ((document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled) ? true : false) : true; } // H5Ps added in normal DIV. @@ -111,7 +111,7 @@ H5P.init = function (target) { var instance = H5P.newRunnable(library, contentId, $container, true); // Check if we should add and display a fullscreen button for this H5P. - if (contentData.fullScreen == 1) { + if (contentData.fullScreen == 1 && H5P.canHasFullScreen) { H5P.jQuery('
        ').prependTo($container).children().click(function () { H5P.fullScreen($container, instance); }); From b3dea65fd8cd548760e578b58fa796f68ca9c03f Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 21 Apr 2015 13:56:24 +0200 Subject: [PATCH 143/149] Remove element from DOM the correct way. --- js/h5p-resizer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js index 3d83db6..894951b 100644 --- a/js/h5p-resizer.js +++ b/js/h5p-resizer.js @@ -46,7 +46,7 @@ */ 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'; @@ -69,7 +69,7 @@ actionHandlers.resize = function (iframe, data, respond) { // Resize iframe so all content is visible. iframe.style.height = data.height + 'px'; - iframe.nextSibling.remove(); + iframe.parentNode.removeChild(iframe.nextSibling); }; /** From cdceb41d743ac254718ee1cae0bd0b764bd17007 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 21 Apr 2015 15:08:59 +0200 Subject: [PATCH 144/149] Prevent running multiple resizer. --- js/h5p-resizer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/h5p-resizer.js b/js/h5p-resizer.js index 894951b..bbfb009 100644 --- a/js/h5p-resizer.js +++ b/js/h5p-resizer.js @@ -1,8 +1,9 @@ // H5P iframe Resizer (function () { - if (!window.postMessage || !window.addEventListener) { + if (!window.postMessage || !window.addEventListener || window.h5pResizerInitialized) { return; // Not supported } + window.h5pResizerInitialized = true; // Map actions to handlers var actionHandlers = {}; From c13e0a0a9a2230dddd226b04c3a83afdf74a2d0a Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 21 Apr 2015 16:22:51 +0200 Subject: [PATCH 145/149] Fixed fullscreen checker. --- js/h5p.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index f8efddb..9c3dbac 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -67,7 +67,7 @@ H5P.init = function (target) { // Determine if we can use full screen if (H5P.canHasFullScreen === undefined) { - H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? (document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled) : true; + H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? ((document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled) ? true : false) : true; } // H5Ps added in normal DIV. @@ -111,7 +111,7 @@ H5P.init = function (target) { var instance = H5P.newRunnable(library, contentId, $container, true); // Check if we should add and display a fullscreen button for this H5P. - if (contentData.fullScreen == 1) { + if (contentData.fullScreen == 1 && H5P.canHasFullScreen) { H5P.jQuery('
        ').prependTo($container).children().click(function () { H5P.fullScreen($container, instance); }); From af8e9e8f304230c4357a52d6930376131bac166e Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 22 Apr 2015 09:49:58 +0200 Subject: [PATCH 146/149] Relay events to top window. --- js/h5p-x-api.js | 4 ---- js/h5p.js | 6 +++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/js/h5p-x-api.js b/js/h5p-x-api.js index 14cbb7e..87a6267 100644 --- a/js/h5p-x-api.js +++ b/js/h5p-x-api.js @@ -3,10 +3,6 @@ var H5P = H5P || {}; // Create object where external code may register and listen for H5P Events H5P.externalDispatcher = new H5P.EventDispatcher(); -if (H5P.isFramed && H5P.externalEmbed === false) { - H5P.externalDispatcher.on('*', window.top.H5P.externalDispatcher.trigger); -} - // EventDispatcher extensions /** diff --git a/js/h5p.js b/js/h5p.js index 9c3dbac..f257f34 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1747,12 +1747,16 @@ H5P.createTitle = function(rawTitle, maxLength) { if (state !== undefined) { // Async is not used to prevent the request from being cancelled. H5P.setUserData(instance.contentId, 'state', state, {deleteOnChange: true, async: false}); - } } } }); } + + // Relay events to top window. + if (H5P.isFramed && H5P.externalEmbed === false) { + H5P.externalDispatcher.on('*', window.top.H5P.externalDispatcher.trigger); + } }); })(H5P.jQuery); From 31ee0bd51a6e2b25081e57b98b3376912d26e8af Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 22 Apr 2015 10:43:18 +0200 Subject: [PATCH 147/149] Removed "Remove empty params." from release. This reverts commit 8c68f23823c81cea341b67a7bab1f65fe51272bf. --- h5p.classes.php | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/h5p.classes.php b/h5p.classes.php index 1a3544b..95ec6ff 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1268,7 +1268,7 @@ class H5PStorage { } $content['params'] = file_get_contents($current_path . DIRECTORY_SEPARATOR . 'content.json'); - + if (isset($options['disable'])) { $content['disable'] = $options['disable']; } @@ -2652,17 +2652,10 @@ class H5PContentValidator { // Validate each element in list. foreach ($list as $key => &$value) { if (!is_int($key)) { - array_splice($list, $key, 1); + unset($list[$key]); continue; } $this->$function($value, $field); - if ($value === NULL) { - array_splice($list, $key, 1); - } - } - - if (count($list) === 0) { - $list = NULL; } } @@ -2772,9 +2765,6 @@ class H5PContentValidator { if ($found) { if ($function) { $this->$function($value, $field); - if ($value === NULL) { - unset($group->$key); - } } else { // We have a field type in semantics for which we don't have a @@ -2810,13 +2800,9 @@ class H5PContentValidator { * Will recurse into validating the library's semantics too. */ public function validateLibrary(&$value, $semantics) { - if (!isset($value->library)) { - $value = NULL; - return; - } - if (!in_array($value->library, $semantics->options)) { + if (!isset($value->library) || !in_array($value->library, $semantics->options)) { $this->h5pF->setErrorMessage($this->h5pF->t('Library used in content is not a valid library according to semantics')); - $value = NULL; + $value = new stdClass(); return; } From 886c631461f0b0d36506b027309559ec776cf8d1 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 22 Apr 2015 10:58:59 +0200 Subject: [PATCH 148/149] Use correct variable. --- js/h5p.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index b77a113..16648c1 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1636,7 +1636,7 @@ H5P.createTitle = function(rawTitle, maxLength) { // Cache in preloaded if (content.contentUserData === undefined) { - content.contentUserData = preloaded = {}; + content.contentUserData = preloadedData = {}; } if (preloadedData[subContentId] === undefined) { preloadedData[subContentId] = {}; From 260b6fe044a0488a30ceae2a63f8cc1e2df7721b Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 22 Apr 2015 12:55:18 +0200 Subject: [PATCH 149/149] Restrict IE FullScreen. --- js/h5p.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 16648c1..9b38d5e 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -67,7 +67,11 @@ H5P.init = function (target) { // Determine if we can use full screen if (H5P.canHasFullScreen === undefined) { - H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? ((document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled) ? true : false) : true; + // Restricts fullscreen when embedded. + // (embedded doesn't support semi-fullscreen solution) + H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? ((document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled) ? true : false) : true; + // We should consider document.msFullscreenEnabled when they get their + // element sizing corrected. Ref. https://connect.microsoft.com/IE/feedback/details/838286/ie-11-incorrectly-reports-dom-element-sizes-in-fullscreen-mode-when-fullscreened-element-is-within-an-iframe } // H5Ps added in normal DIV.