diff --git a/h5p.classes.php b/h5p.classes.php index a610431..c1c648f 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -1981,6 +1981,13 @@ abstract class H5PDisplayOptionBehaviour { const CONTROLLED_BY_PERMISSIONS = 4; } +abstract class H5PSaveContentStorages { + const NONE = 0; + const DATABASE = 1; + const LOCALSTORAGE = 2; + const DATABASE_LOCALSTORAGE = 3; +} + abstract class H5PHubEndpoints { const CONTENT_TYPES = 'api.h5p.org/v1/content-types/'; const SITES = 'api.h5p.org/v1/sites'; diff --git a/js/h5p.js b/js/h5p.js index ee1888a..556776d 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -80,6 +80,16 @@ H5P.init = function (target) { // Update: Seems to be no need as they've moved on to Webkit } + // Determine if we can use local storage + if (H5P.localStorageSupported === undefined) { + try { + H5P.localStorageSupported = (window.localStorage) ? true : false; + } + catch (error) { + H5P.localStorageSupported = false; + } + } + // Deprecated variable, kept to maintain backwards compatability if (H5P.canHasFullScreen === undefined) { /** @@ -2319,7 +2329,7 @@ H5P.createTitle = function (rawTitle, maxLength) { function contentUserDataAjax(contentId, dataType, subContentId, done, data, preload, invalidate, async) { if (H5PIntegration.user === undefined) { // Not logged in, no use in saving. - done('Not signed in.'); + done('Not signed in.'); // Return value used when storing state in localStorage return; } @@ -2381,6 +2391,44 @@ H5P.createTitle = function (rawTitle, maxLength) { H5PIntegration.contents = H5PIntegration.contents || {}; var content = H5PIntegration.contents['cid-' + contentId] || {}; var preloadedData = content.contentUserData; + + /* + * If previous state in DB is empty (user might not be logged in), + * alternatively try to preload state from localStorage + */ + if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId] === '{}') { + if (H5PIntegration.saveContentStorages && H5PIntegration.saveContentStorages.localStorage && H5P.localStorageSupported) { + const localStorageData = window.localStorage.getItem('H5P-cid-' + contentId + '-sid-' + subContentId); + if (localStorageData) { + let data = {}; + + try { + data = JSON.parse(localStorageData); + } + catch (err) { + console.error('Unable to parse JSON from state in localStorage.', err); + } + + if (data.state && data.checksum) { + // Detect whether content parameters changed meanwhile + if (data.checksum === H5P.getNumericalHash(content.jsonContent)) { + try { + data = JSON.stringify(data.state); + preloadedData[subContentId][dataId] = data; + } + catch (err) { + console.error('Unable to stringify JSON for state in localStorage.', err); + } + } + else { + // Content has been changed + preloadedData[subContentId][dataId] = 'RESET'; + } + } + } + } + } + if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId] !== undefined) { if (preloadedData[subContentId][dataId] === 'RESET') { done(undefined, null); @@ -2486,6 +2534,18 @@ H5P.createTitle = function (rawTitle, maxLength) { if (options.errorCallback && error) { options.errorCallback(error); } + + // Additionally store state in localStorage if requested + if ((!error || error === 'Not signed in.') && + H5PIntegration.saveContentStorages && H5PIntegration.saveContentStorages.localStorage && H5P.localStorageSupported + ) { + // Add checksum of params to detect changes for resetting localStorage + window.localStorage.setItem( + 'H5P-cid-' + contentId + '-sid-' + options.subContentId, + '{"checksum":' + H5P.getNumericalHash(content.jsonContent) + ',"state":' + data + '}' + ); + } + }, data, options.preloaded, options.deleteOnChange, options.async); }; @@ -2510,7 +2570,12 @@ H5P.createTitle = function (rawTitle, maxLength) { delete preloadedData[subContentId][dataId]; } - contentUserDataAjax(contentId, dataId, subContentId, undefined, null); + contentUserDataAjax(contentId, dataId, subContentId, function (error) { + // When done deleting user data in DB, delete in localStorage + if ((!error || error === 'Not signed in.') && H5P.localStorageSupported) { + window.localStorage.removeItem('H5P-cid-' + contentId + '-sid-' + subContentId); + } + }, null); }; /** @@ -2616,6 +2681,20 @@ H5P.createTitle = function (rawTitle, maxLength) { H5P.externalDispatcher.trigger('datainclipboard', {reset: false}); }; + /** + * Get numerical hash for a text. + * + * @param {string} text - Text to be hashed. + */ + H5P.getNumericalHash = function (text) { + text = text || ''; + return text + .split('') + .reduce(function (result, current) { + return (((result << 5) - result) + current.charCodeAt(0)) | 0; + }, 0); + }; + /** * Get config for a library *