From dc8b70748fd4654debc9746ec12fe42e10b76510 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 9 Feb 2016 13:53:17 +0100 Subject: [PATCH 1/6] Fixed once() HFJ-1646 --- js/h5p-event-dispatcher.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/h5p-event-dispatcher.js b/js/h5p-event-dispatcher.js index 5300e6d..fb8dd66 100644 --- a/js/h5p-event-dispatcher.js +++ b/js/h5p-event-dispatcher.js @@ -139,8 +139,8 @@ H5P.EventDispatcher = (function () { } var once = function (event) { - self.off(event, once); - listener.apply(this, event); + self.off(event.type, once); + listener.call(this, event); }; self.on(type, once, thisArg); From 3849f3a0544e83b3da8b5f232d119179951e15be Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Fri, 19 Feb 2016 12:57:53 +0100 Subject: [PATCH 2/6] Improved results and contentuserdata saving by queueing requests --- js/h5p.js | 161 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 129 insertions(+), 32 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 934d8f6..0c17a8e 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1580,46 +1580,97 @@ H5P.shuffleArray = function (array) { return array; }; -/** - * Post finished results for user. - * - * @deprecated - * Do not use this function directly, trigger the finish event instead. - * Will be removed march 2016 - * @param {number} contentId - * Identifies the content - * @param {number} score - * Achieved score/points - * @param {number} maxScore - * The maximum score/points that can be achieved - * @param {number} [time] - * Reported time consumption/usage - */ -H5P.setFinished = function (contentId, score, maxScore, time) { - if (H5PIntegration.postUserStatistics === true) { - /** - * Return unix timestamp for the given JS Date. - * - * @private - * @param {Date} date - * @returns {Number} - */ - var toUnix = function (date) { - return Math.round(date.getTime() / 1000); - }; +(function ($) { + var token; + var queue = []; + + /** + * Return unix timestamp for the given JS Date. + * + * @private + * @param {Date} date + * @returns {Number} + */ + var toUnix = function (date) { + return Math.round(date.getTime() / 1000); + }; + + /** + * Post results to server + * + * @param Object result + */ + var post = function (result) { + // Add token to post + result.token = token; + token = null; // Post the results // TODO: Should we use a variable with the complete path? - H5P.jQuery.post(H5PIntegration.ajaxPath + 'setFinished', { + H5P.jQuery.post(H5PIntegration.ajaxPath + 'setFinished', result).fail(function() { + // Reuse token + token = result.token; + }).done(function(data) { + token = data.token; + }).always(function() { + // Check for more requests to run + if (queue[0] !== undefined) { + post(queue.splice(0, 1)); + } + }); + }; + + /** + * Creates the finished result object for the user and schedules it for + * posting to the server. + * + * @deprecated + * Do not use this function directly, trigger the finish event instead. + * Will be removed march 2016 + * @param {number} contentId + * Identifies the content + * @param {number} score + * Achieved score/points + * @param {number} maxScore + * The maximum score/points that can be achieved + * @param {number} [time] + * Reported time consumption/usage + */ + H5P.setFinished = function (contentId, score, maxScore, time) { + if (H5PIntegration.postUserStatistics !== true) { + return; + } + + var result = { contentId: contentId, score: score, maxScore: maxScore, opened: toUnix(H5P.opened[contentId]), finished: toUnix(new Date()), time: time - }); - } -}; + }; + + if (token === undefined) { + if (H5PIntegration.tokens === undefined || + H5PIntegration.tokens.result === undefined) { + token = 'canHasDummyToken'; + } + else { + token = H5PIntegration.tokens.result; + } + } + + if (token === null) { + // Already in progress, add to queue + queue.push(result); + } + else { + post(result); + } + }; + +})(H5P.jQuery); + // Add indexOf to browsers that lack them. (IEs) if (!Array.prototype.indexOf) { @@ -1728,6 +1779,8 @@ H5P.createTitle = function (rawTitle, maxLength) { // Wrap in privates (function ($) { + var token; + var queue = []; /** * Creates ajax requests for inserting, updateing and deleteing @@ -1749,6 +1802,16 @@ H5P.createTitle = function (rawTitle, maxLength) { done('Not signed in.'); return; } + if (token === undefined) { + // Load initial token + if (H5PIntegration.tokens === undefined || + H5PIntegration.tokens.contentUserData === undefined) { + token = 'canHasDummyToken'; + } + else { + token = H5PIntegration.tokens.contentUserData; + } + } var options = { url: H5PIntegration.ajax.contentUserData.replace(':contentId', contentId).replace(':dataType', dataType).replace(':subContentId', subContentId ? subContentId : 0), @@ -1784,10 +1847,44 @@ H5P.createTitle = function (rawTitle, maxLength) { done(undefined, response.data); }; } + if (options.type === 'POST') { + if (token === null) { + // We must queue and wait for a new token + queue.push(options); + } + else { + // Use token + options.data.token = token; + token = null; + } + } - $.ajax(options); + runAjaxRequest(options); } + /** + * + * + * @param Object options Details for request + */ + var runAjaxRequest = function (options) { + var $req = $.ajax(options); + if (options.type === 'POST') { + $req.fail(function() { + // Reuse token + token = options.data.token; + }).done(function(data) { + // Set new token + token = data.token; + }).always(function() { + // Check for more requests to run + if (queue[0] !== undefined) { + runAjaxRequest(queue.splice(0, 1)); + } + }); + } + }; + /** * Get user data for given content. * From 256bf1b6d29a0886e30d6304002d8e6f33bb334c Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 22 Feb 2016 11:56:52 +0100 Subject: [PATCH 3/6] Revert "Improved results and contentuserdata saving by queueing requests" This reverts commit 3849f3a0544e83b3da8b5f232d119179951e15be. --- js/h5p.js | 161 +++++++++++------------------------------------------- 1 file changed, 32 insertions(+), 129 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 0c17a8e..934d8f6 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1580,97 +1580,46 @@ H5P.shuffleArray = function (array) { return array; }; -(function ($) { - var token; - var queue = []; - - /** - * Return unix timestamp for the given JS Date. - * - * @private - * @param {Date} date - * @returns {Number} - */ - var toUnix = function (date) { - return Math.round(date.getTime() / 1000); - }; - - /** - * Post results to server - * - * @param Object result - */ - var post = function (result) { - // Add token to post - result.token = token; - token = null; +/** + * Post finished results for user. + * + * @deprecated + * Do not use this function directly, trigger the finish event instead. + * Will be removed march 2016 + * @param {number} contentId + * Identifies the content + * @param {number} score + * Achieved score/points + * @param {number} maxScore + * The maximum score/points that can be achieved + * @param {number} [time] + * Reported time consumption/usage + */ +H5P.setFinished = function (contentId, score, maxScore, time) { + if (H5PIntegration.postUserStatistics === true) { + /** + * Return unix timestamp for the given JS Date. + * + * @private + * @param {Date} date + * @returns {Number} + */ + 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(H5PIntegration.ajaxPath + 'setFinished', result).fail(function() { - // Reuse token - token = result.token; - }).done(function(data) { - token = data.token; - }).always(function() { - // Check for more requests to run - if (queue[0] !== undefined) { - post(queue.splice(0, 1)); - } - }); - }; - - /** - * Creates the finished result object for the user and schedules it for - * posting to the server. - * - * @deprecated - * Do not use this function directly, trigger the finish event instead. - * Will be removed march 2016 - * @param {number} contentId - * Identifies the content - * @param {number} score - * Achieved score/points - * @param {number} maxScore - * The maximum score/points that can be achieved - * @param {number} [time] - * Reported time consumption/usage - */ - H5P.setFinished = function (contentId, score, maxScore, time) { - if (H5PIntegration.postUserStatistics !== true) { - return; - } - - var result = { + H5P.jQuery.post(H5PIntegration.ajaxPath + 'setFinished', { contentId: contentId, score: score, maxScore: maxScore, opened: toUnix(H5P.opened[contentId]), finished: toUnix(new Date()), time: time - }; - - if (token === undefined) { - if (H5PIntegration.tokens === undefined || - H5PIntegration.tokens.result === undefined) { - token = 'canHasDummyToken'; - } - else { - token = H5PIntegration.tokens.result; - } - } - - if (token === null) { - // Already in progress, add to queue - queue.push(result); - } - else { - post(result); - } - }; - -})(H5P.jQuery); - + }); + } +}; // Add indexOf to browsers that lack them. (IEs) if (!Array.prototype.indexOf) { @@ -1779,8 +1728,6 @@ H5P.createTitle = function (rawTitle, maxLength) { // Wrap in privates (function ($) { - var token; - var queue = []; /** * Creates ajax requests for inserting, updateing and deleteing @@ -1802,16 +1749,6 @@ H5P.createTitle = function (rawTitle, maxLength) { done('Not signed in.'); return; } - if (token === undefined) { - // Load initial token - if (H5PIntegration.tokens === undefined || - H5PIntegration.tokens.contentUserData === undefined) { - token = 'canHasDummyToken'; - } - else { - token = H5PIntegration.tokens.contentUserData; - } - } var options = { url: H5PIntegration.ajax.contentUserData.replace(':contentId', contentId).replace(':dataType', dataType).replace(':subContentId', subContentId ? subContentId : 0), @@ -1847,44 +1784,10 @@ H5P.createTitle = function (rawTitle, maxLength) { done(undefined, response.data); }; } - if (options.type === 'POST') { - if (token === null) { - // We must queue and wait for a new token - queue.push(options); - } - else { - // Use token - options.data.token = token; - token = null; - } - } - runAjaxRequest(options); + $.ajax(options); } - /** - * - * - * @param Object options Details for request - */ - var runAjaxRequest = function (options) { - var $req = $.ajax(options); - if (options.type === 'POST') { - $req.fail(function() { - // Reuse token - token = options.data.token; - }).done(function(data) { - // Set new token - token = data.token; - }).always(function() { - // Check for more requests to run - if (queue[0] !== undefined) { - runAjaxRequest(queue.splice(0, 1)); - } - }); - } - }; - /** * Get user data for given content. * From 0aebdb345bde830bb10851ddda5f13b3d47194fe Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 22 Feb 2016 11:58:25 +0100 Subject: [PATCH 4/6] Lets use a simpler solution --- js/h5p.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 934d8f6..2b0a862 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1616,7 +1616,8 @@ H5P.setFinished = function (contentId, score, maxScore, time) { maxScore: maxScore, opened: toUnix(H5P.opened[contentId]), finished: toUnix(new Date()), - time: time + time: time, + token: H5PIntegration.tokens.result }); } }; @@ -1760,7 +1761,8 @@ H5P.createTitle = function (rawTitle, maxLength) { options.data = { data: (data === null ? 0 : data), preload: (preload ? 1 : 0), - invalidate: (invalidate ? 1 : 0) + invalidate: (invalidate ? 1 : 0), + token: H5PIntegration.tokens.contentUserData }; } else { From 4e06733ffbd0c31b5b861e00f9d79431299c2960 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 22 Feb 2016 12:01:18 +0100 Subject: [PATCH 5/6] Use correct message --- js/h5p.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/h5p.js b/js/h5p.js index 2b0a862..dd36a7a 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1774,7 +1774,7 @@ H5P.createTitle = function (rawTitle, maxLength) { }; options.success = function (response) { if (!response.success) { - done(response.error); + done(response.message); return; } From 0430e6ba28a33365f465afd6ee5849bb0f930703 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Thu, 25 Feb 2016 13:46:05 +0100 Subject: [PATCH 6/6] Put AJAX response functions in core --- h5p.classes.php | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/h5p.classes.php b/h5p.classes.php index 3ccfd5f..af78c0e 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2535,6 +2535,91 @@ class H5PCore { return TRUE; } + + /** + * Makes it easier to print response when AJAX request succeeds. + * + * @param mixed $data + * @since 1.6.0 + */ + public static function ajaxSuccess($data = NULL) { + $response = array( + 'success' => TRUE + ); + if ($message !== NULL) { + $response['data'] = $data; + } + self::printJson($response); + } + + /** + * Makes it easier to print response when AJAX request fails. + * Will exit after printing error. + * + * @param string $message + * @since 1.6.0 + */ + public static function ajaxError($message = NULL) { + $response = array( + 'success' => FALSE + ); + if ($message !== NULL) { + $response['message'] = $message; + } + self::printJson($response); + } + + /** + * Print JSON headers with UTF-8 charset and json encode response data. + * Makes it easier to respond using JSON. + * + * @param mixed $data + */ + private static function printJson($data) { + header('Cache-Control: no-cache'); + header('Content-type: application/json; charset=utf-8'); + print json_encode($data); + } + + /** + * Get a new H5P security token for the given action. + * + * @param string $action + * @return string token + */ + public static function createToken($action) { + if (!isset($_SESSION['h5p_token'])) { + // Create an unique key which is used to create action tokens for this session. + $_SESSION['h5p_token'] = uniqid(); + } + + // Timefactor + $time_factor = self::getTimeFactor(); + + // Create and return token + return substr(hash('md5', $action . $time_factor . $_SESSION['h5p_token']), -16, 13); + } + + /** + * Create a time based number which is unique for each 12 hour. + * @return int + */ + private static function getTimeFactor() { + return ceil(time() / (86400 / 2)); + } + + /** + * Verify if the given token is valid for the given action. + * + * @param string $action + * @param string $token + * @return boolean valid token + */ + public static function validToken($action, $token) { + $time_factor = self::getTimeFactor(); + return $token === substr(hash('md5', $action . $time_factor . $_SESSION['h5p_token']), -16, 13) || // Under 12 hours + $token === substr(hash('md5', $action . ($time_factor - 1) . $_SESSION['h5p_token']), -16, 13); // Between 12-24 hours + } } /**