From 1b079d36f17e299dd8ee5873847dec59ee762083 Mon Sep 17 00:00:00 2001 From: Thomas Horn Sivertsen Date: Fri, 19 Jan 2018 14:42:43 +0100 Subject: [PATCH 1/7] HFP-1806 Load presave script if present in library. --- h5p-default-storage.class.php | 11 +++++++++++ h5p-file-storage.interface.php | 7 +++++++ h5p.classes.php | 14 +++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/h5p-default-storage.class.php b/h5p-default-storage.class.php index 38af307..ba41eb9 100644 --- a/h5p-default-storage.class.php +++ b/h5p-default-storage.class.php @@ -451,6 +451,17 @@ class H5PDefaultStorage implements \H5PFileStorage { return self::dirReady($this->path); } + public function hasPresave($name) { + $filePath = implode(DIRECTORY_SEPARATOR, [ + $this->path, + 'libraries', + $name, + 'presave.js', + ]); + + return file_exists($filePath); + } + /** * Recursive function for copying directories. * diff --git a/h5p-file-storage.interface.php b/h5p-file-storage.interface.php index 394210b..953365c 100644 --- a/h5p-file-storage.interface.php +++ b/h5p-file-storage.interface.php @@ -190,4 +190,11 @@ interface H5PFileStorage { * @return bool True if server has the proper write access */ public function hasWriteAccess(); + + /** + * Check if the library has a presave.js in the root folder + * + * @return string|null Path to script or null if missing + */ + public function hasPresave($name); } diff --git a/h5p.classes.php b/h5p.classes.php index d8ed62e..a978516 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2109,14 +2109,18 @@ class H5PCore { // Using content dependencies foreach ($dependencies as $dependency) { + $libraryName = H5PCore::libraryToString($dependency, TRUE); if (isset($dependency['path']) === FALSE) { - $dependency['path'] = 'libraries/' . H5PCore::libraryToString($dependency, TRUE); + $dependency['path'] = 'libraries/' . $libraryName; $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); + if( $this->fs->hasPresave($libraryName) ){ + $this->addPresaveFile($files, $dependency, $prefix); + } } if ($this->aggregateAssets) { @@ -2130,6 +2134,14 @@ class H5PCore { return $files; } + public function addPresaveFile(&$assets, $library, $prefix = ''){ + $this->getDependencyAssets([ + 'path' => 'libraries' . DIRECTORY_SEPARATOR . self::libraryToString($library, true), + 'version' => array_key_exists('version', $library) ? $library['version'] : "?ver={$library['majorVersion']}.{$library['minorVersion']}.{$library['patchVersion']}", + 'presaveJs' => ['presave.js'] + ], 'presaveJs', $assets['scripts'], $prefix); + } + private static function getDependenciesHash(&$dependencies) { // Build hash of dependencies $toHash = array(); From 04edd738552cc4ee0391824459eb4ced16e54351 Mon Sep 17 00:00:00 2001 From: Thomas Horn Sivertsen Date: Fri, 19 Jan 2018 14:56:07 +0100 Subject: [PATCH 2/7] HFP-1806 Formatting code + adding comments. --- h5p-default-storage.class.php | 21 +++++++++++++-------- h5p.classes.php | 16 ++++++++-------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/h5p-default-storage.class.php b/h5p-default-storage.class.php index ba41eb9..a06c47c 100644 --- a/h5p-default-storage.class.php +++ b/h5p-default-storage.class.php @@ -451,15 +451,20 @@ class H5PDefaultStorage implements \H5PFileStorage { return self::dirReady($this->path); } + /** + * Check if the file presave.js exists in the root of the library + * + * @param string $name + * @return bool + */ public function hasPresave($name) { - $filePath = implode(DIRECTORY_SEPARATOR, [ - $this->path, - 'libraries', - $name, - 'presave.js', - ]); - - return file_exists($filePath); + $filePath = implode(DIRECTORY_SEPARATOR, [ + $this->path, + 'libraries', + $name, + 'presave.js', + ]); + return file_exists($filePath); } /** diff --git a/h5p.classes.php b/h5p.classes.php index a978516..80399d9 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2119,7 +2119,7 @@ class H5PCore { $this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts'], $prefix); $this->getDependencyAssets($dependency, 'preloadedCss', $files['styles'], $prefix); if( $this->fs->hasPresave($libraryName) ){ - $this->addPresaveFile($files, $dependency, $prefix); + $this->addPresaveFile($files, $dependency, $prefix); } } @@ -2134,13 +2134,13 @@ class H5PCore { return $files; } - public function addPresaveFile(&$assets, $library, $prefix = ''){ - $this->getDependencyAssets([ - 'path' => 'libraries' . DIRECTORY_SEPARATOR . self::libraryToString($library, true), - 'version' => array_key_exists('version', $library) ? $library['version'] : "?ver={$library['majorVersion']}.{$library['minorVersion']}.{$library['patchVersion']}", - 'presaveJs' => ['presave.js'] - ], 'presaveJs', $assets['scripts'], $prefix); - } + public function addPresaveFile(&$assets, $library, $prefix = ''){ + $this->getDependencyAssets([ + 'path' => 'libraries' . DIRECTORY_SEPARATOR . self::libraryToString($library, true), + 'version' => array_key_exists('version', $library) ? $library['version'] : "?ver={$library['majorVersion']}.{$library['minorVersion']}.{$library['patchVersion']}", + 'presaveJs' => ['presave.js'] + ], 'presaveJs', $assets['scripts'], $prefix); + } private static function getDependenciesHash(&$dependencies) { // Build hash of dependencies From 615bac7c08e4fe23726fc45edbe8615ee9b7139b Mon Sep 17 00:00:00 2001 From: Thomas Horn Sivertsen Date: Thu, 1 Feb 2018 12:18:31 +0100 Subject: [PATCH 3/7] HFP-1806 Add presave for development and public libraries --- h5p-default-storage.class.php | 10 +++------- h5p-file-storage.interface.php | 4 ++-- h5p.classes.php | 16 ++++++++++++++-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/h5p-default-storage.class.php b/h5p-default-storage.class.php index a06c47c..9766e47 100644 --- a/h5p-default-storage.class.php +++ b/h5p-default-storage.class.php @@ -457,13 +457,9 @@ class H5PDefaultStorage implements \H5PFileStorage { * @param string $name * @return bool */ - public function hasPresave($name) { - $filePath = implode(DIRECTORY_SEPARATOR, [ - $this->path, - 'libraries', - $name, - 'presave.js', - ]); + public function hasPresave($libraryFolder, $developmentPath = null) { + $path = is_null($developmentPath) ? 'libraries' . DIRECTORY_SEPARATOR . $libraryFolder : $developmentPath; + $filePath = realpath($this->path . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . 'presave.js'); return file_exists($filePath); } diff --git a/h5p-file-storage.interface.php b/h5p-file-storage.interface.php index 953365c..976605e 100644 --- a/h5p-file-storage.interface.php +++ b/h5p-file-storage.interface.php @@ -194,7 +194,7 @@ interface H5PFileStorage { /** * Check if the library has a presave.js in the root folder * - * @return string|null Path to script or null if missing + * @return string|null Path if presave.js found */ - public function hasPresave($name); + public function hasPresave($libraryName, $developmentPath = null); } diff --git a/h5p.classes.php b/h5p.classes.php index 80399d9..efac783 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2118,7 +2118,7 @@ class H5PCore { $dependency['version'] = "?ver={$dependency['majorVersion']}.{$dependency['minorVersion']}.{$dependency['patchVersion']}"; $this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts'], $prefix); $this->getDependencyAssets($dependency, 'preloadedCss', $files['styles'], $prefix); - if( $this->fs->hasPresave($libraryName) ){ + if( $this->hasPresave($libraryName) ){ $this->addPresaveFile($files, $dependency, $prefix); } } @@ -2134,9 +2134,21 @@ class H5PCore { return $files; } + public function hasPresave($libraryName) + { + if( isset($this->h5pD) ){ + extract(H5PCore::libraryFromString($libraryName)); + $library = $this->h5pD->getLibrary($machineName, $majorVersion, $minorVersion); + if( !is_null($library)){ + return $this->fs->hasPresave($libraryName, $library['path']); + } + } + return $this->fs->hasPresave($libraryName); + } + public function addPresaveFile(&$assets, $library, $prefix = ''){ $this->getDependencyAssets([ - 'path' => 'libraries' . DIRECTORY_SEPARATOR . self::libraryToString($library, true), + 'path' => array_key_exists('path', $library) ? $library['path'] : 'libraries' . DIRECTORY_SEPARATOR . self::libraryToString($library, true), 'version' => array_key_exists('version', $library) ? $library['version'] : "?ver={$library['majorVersion']}.{$library['minorVersion']}.{$library['patchVersion']}", 'presaveJs' => ['presave.js'] ], 'presaveJs', $assets['scripts'], $prefix); From a59640672dc5ff0030bc688eaacfa8a544ed9f72 Mon Sep 17 00:00:00 2001 From: Thomas Horn Sivertsen Date: Thu, 7 Jun 2018 12:43:12 +0200 Subject: [PATCH 4/7] HFP-1806 - fixed PhpDoc - only load presave.js when editing a H5P - moved loading logic from core to editor - removed unused code --- h5p-default-storage.class.php | 3 ++- h5p-file-storage.interface.php | 4 +++- h5p.classes.php | 23 ----------------------- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/h5p-default-storage.class.php b/h5p-default-storage.class.php index 9766e47..d6d13ea 100644 --- a/h5p-default-storage.class.php +++ b/h5p-default-storage.class.php @@ -454,7 +454,8 @@ class H5PDefaultStorage implements \H5PFileStorage { /** * Check if the file presave.js exists in the root of the library * - * @param string $name + * @param string $libraryFolder + * @param string $developmentPath * @return bool */ public function hasPresave($libraryFolder, $developmentPath = null) { diff --git a/h5p-file-storage.interface.php b/h5p-file-storage.interface.php index 976605e..4dbdbc6 100644 --- a/h5p-file-storage.interface.php +++ b/h5p-file-storage.interface.php @@ -194,7 +194,9 @@ interface H5PFileStorage { /** * Check if the library has a presave.js in the root folder * - * @return string|null Path if presave.js found + * @param string $libraryName + * @param string $developmentPath + * @return bool */ public function hasPresave($libraryName, $developmentPath = null); } diff --git a/h5p.classes.php b/h5p.classes.php index efac783..650895c 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2118,9 +2118,6 @@ class H5PCore { $dependency['version'] = "?ver={$dependency['majorVersion']}.{$dependency['minorVersion']}.{$dependency['patchVersion']}"; $this->getDependencyAssets($dependency, 'preloadedJs', $files['scripts'], $prefix); $this->getDependencyAssets($dependency, 'preloadedCss', $files['styles'], $prefix); - if( $this->hasPresave($libraryName) ){ - $this->addPresaveFile($files, $dependency, $prefix); - } } if ($this->aggregateAssets) { @@ -2134,26 +2131,6 @@ class H5PCore { return $files; } - public function hasPresave($libraryName) - { - if( isset($this->h5pD) ){ - extract(H5PCore::libraryFromString($libraryName)); - $library = $this->h5pD->getLibrary($machineName, $majorVersion, $minorVersion); - if( !is_null($library)){ - return $this->fs->hasPresave($libraryName, $library['path']); - } - } - return $this->fs->hasPresave($libraryName); - } - - public function addPresaveFile(&$assets, $library, $prefix = ''){ - $this->getDependencyAssets([ - 'path' => array_key_exists('path', $library) ? $library['path'] : 'libraries' . DIRECTORY_SEPARATOR . self::libraryToString($library, true), - 'version' => array_key_exists('version', $library) ? $library['version'] : "?ver={$library['majorVersion']}.{$library['minorVersion']}.{$library['patchVersion']}", - 'presaveJs' => ['presave.js'] - ], 'presaveJs', $assets['scripts'], $prefix); - } - private static function getDependenciesHash(&$dependencies) { // Build hash of dependencies $toHash = array(); From 930f85e0fb3a0ee2bcc2496303dbca1c8012cb40 Mon Sep 17 00:00:00 2001 From: Thomas Horn Sivertsen Date: Mon, 18 Jun 2018 11:24:04 +0200 Subject: [PATCH 5/7] HFP-1806 Removed unused changes, fixed indentation --- h5p-default-storage.class.php | 14 +++++++------- h5p.classes.php | 3 +-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/h5p-default-storage.class.php b/h5p-default-storage.class.php index d6d13ea..26e3e01 100644 --- a/h5p-default-storage.class.php +++ b/h5p-default-storage.class.php @@ -451,13 +451,13 @@ class H5PDefaultStorage implements \H5PFileStorage { return self::dirReady($this->path); } - /** - * Check if the file presave.js exists in the root of the library - * - * @param string $libraryFolder - * @param string $developmentPath - * @return bool - */ + /** + * Check if the file presave.js exists in the root of the library + * + * @param string $libraryFolder + * @param string $developmentPath + * @return bool + */ public function hasPresave($libraryFolder, $developmentPath = null) { $path = is_null($developmentPath) ? 'libraries' . DIRECTORY_SEPARATOR . $libraryFolder : $developmentPath; $filePath = realpath($this->path . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR . 'presave.js'); diff --git a/h5p.classes.php b/h5p.classes.php index 650895c..d8ed62e 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -2109,9 +2109,8 @@ class H5PCore { // Using content dependencies foreach ($dependencies as $dependency) { - $libraryName = H5PCore::libraryToString($dependency, TRUE); if (isset($dependency['path']) === FALSE) { - $dependency['path'] = 'libraries/' . $libraryName; + $dependency['path'] = 'libraries/' . H5PCore::libraryToString($dependency, TRUE); $dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']); $dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']); } From b1446e8d60dd5ae4e7cf9a12aadc7029de29c216 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Mon, 25 Jun 2018 15:59:17 +0200 Subject: [PATCH 6/7] HFP-2027 Add copy paste helpers to core --- js/h5p.js | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/js/h5p.js b/js/h5p.js index ef56834..00dc019 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -2041,9 +2041,129 @@ H5P.createTitle = function (rawTitle, maxLength) { contentUserDataAjax(contentId, dataId, subContentId, undefined, null); }; + /** + * Prepares the content parameters for storing in the clipboard. + * + * @class + * @param {Object} parameters The parameters for the content to store + * @param {string} [genericProperty] If only part of the parameters are generic, which part + * @param {string} [specificKey] If the parameters are specific, what content type does it fit + * @returns {Object} Ready for the clipboard + */ + H5P.ClipboardItem = function (parameters, genericProperty, specificKey) { + this.specific = parameters; + + if (genericProperty && specificKey) { + this.from = specificKey; + if (parameters[genericProperty]) { + this.generic = genericProperty; + } + } + + if (window.H5PEditor && H5PEditor.contentId) { + this.contentId = H5PEditor.contentId; + } + }; + + /** + * Store item in the H5P Clipboard. + * + * @param {H5P.ClipboardItem|*} clipboardItem + */ + H5P.clipboardify = function (clipboardItem) { + if (!(clipboardItem instanceof H5P.ClipboardItem)) { + clipboardItem = new H5P.ClipboardItem(clipboardItem); + } + + localStorage.setItem('h5pClipboard', JSON.stringify(clipboardItem)); + + // Trigger an event so all 'Paste' buttons may be enabled. + H5P.externalDispatcher.trigger('datainclipboard', {reset: false}); + }; + + /** + * Get item from the H5P Clipboard. + * + * @return {Object} + */ + H5P.getClipboard = function () { + var clipboardData = localStorage.getItem('h5pClipboard'); + if (!clipboardData) { + return; + } + + // Try to parse clipboard dat + try { + clipboardData = JSON.parse(clipboardData); + } + catch (err) { + console.error('Unable to parse JSON from clipboard.', err); + return; + } + + // Update file URLs + H5P.updateFileUrls(clipboardData.specific, function (path) { + var isTmpFile = (path.substr(-4, 4) === '#tmp'); + if (!isTmpFile && clipboardData.contentId) { + // Comes from existing content + + if (H5PEditor.contentId) { + // .. to existing content + return '../' + clipboardData.contentId + '/' + path; + } + else { + // .. to new content + return (H5PEditor.contentRelUrl ? H5PEditor.contentRelUrl : '../content/') + clipboardData.contentId + '/' + path; + } + } + return path; // Will automatically be looked for in tmp folder + }); + + if (clipboardData.from === undefined) { + clipboardData.generic = clipboardData.specific; + } + else if (clipboardData.generic) { + clipboardData.generic = clipboardData.specific[clipboardData.generic]; + } + + if (clipboardData.generic) { + // Avoid multiple content with same ID + delete clipboardData.generic.subContentId; + } + + return clipboardData; + }; + + /** + * Update file URLs. Useful when copying content. + * + * @param {object} params Reference + * @param {function} handler Modifies the path to work when pasted + */ + H5P.updateFileUrls = function (params, handler) { + for (var prop in params) { + if (params.hasOwnProperty(prop) && params[prop] instanceof Object) { + var obj = params[prop]; + if (obj.path !== undefined && obj.mime !== undefined) { + obj.path = handler(obj.path); + } + else { + H5P.updateFileUrls(obj, handler); + } + } + } + }; + // Init H5P when page is fully loadded $(document).ready(function () { + window.addEventListener('storage', function (event) { + // Pick up clipboard changes from other tabs + if (event.key === 'h5pClipboard') { + H5P.externalDispatcher.trigger('datainclipboard', {reset: event.newValue === null}); + } + }); + var ccVersions = { 'default': '4.0', '4.0': H5P.t('licenseCC40'), From d24fd0e66792e8a510c8616ed035bfec76b00e9a Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Tue, 26 Jun 2018 11:52:57 +0200 Subject: [PATCH 7/7] HFP-2072 Improve copy-paste API behavior --- js/h5p.js | 90 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/js/h5p.js b/js/h5p.js index 00dc019..b09759b 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -2051,17 +2051,49 @@ H5P.createTitle = function (rawTitle, maxLength) { * @returns {Object} Ready for the clipboard */ H5P.ClipboardItem = function (parameters, genericProperty, specificKey) { - this.specific = parameters; + var self = this; - if (genericProperty && specificKey) { - this.from = specificKey; - if (parameters[genericProperty]) { - this.generic = genericProperty; + /** + * Set relative dimensions when params contains a file with a width and a height. + * Very useful to be compatible with wysiwyg editors. + * + * @private + */ + var setDimensionsFromFile = function () { + if (!self.generic) { + return; } + var params = self.specific[self.generic]; + if (!params.params.file || !params.params.file.width || !params.params.file.height) { + return; + } + + self.width = 20; // % + self.height = (params.params.file.height / params.params.file.width) * self.width; + } + + if (!genericProperty) { + genericProperty = 'action'; + parameters = { + action: parameters + }; + } + + self.specific = parameters; + + if (genericProperty && parameters[genericProperty]) { + self.generic = genericProperty; + } + if (specificKey) { + self.from = specificKey; } if (window.H5PEditor && H5PEditor.contentId) { - this.contentId = H5PEditor.contentId; + self.contentId = H5PEditor.contentId; + } + + if (!self.specific.width && !self.specific.height) { + setDimensionsFromFile(); } }; @@ -2077,16 +2109,40 @@ H5P.createTitle = function (rawTitle, maxLength) { localStorage.setItem('h5pClipboard', JSON.stringify(clipboardItem)); + // Clear cache + parsedClipboard = null; + // Trigger an event so all 'Paste' buttons may be enabled. H5P.externalDispatcher.trigger('datainclipboard', {reset: false}); }; /** - * Get item from the H5P Clipboard. + * This is a cache for pasted data to prevent parsing multiple times. + * @type {Object} + */ + var parsedClipboard = null; + + /** + * Retrieve parsed clipboard data. * * @return {Object} */ H5P.getClipboard = function () { + if (!parsedClipboard) { + parsedClipboard = parseClipboard(); + } + + return parsedClipboard; + } + + /** + * Get item from the H5P Clipboard. + * + * @private + * @param {boolean} [skipUpdateFileUrls] + * @return {Object} + */ + var parseClipboard = function () { var clipboardData = localStorage.getItem('h5pClipboard'); if (!clipboardData) { return; @@ -2102,7 +2158,7 @@ H5P.createTitle = function (rawTitle, maxLength) { } // Update file URLs - H5P.updateFileUrls(clipboardData.specific, function (path) { + updateFileUrls(clipboardData.specific, function (path) { var isTmpFile = (path.substr(-4, 4) === '#tmp'); if (!isTmpFile && clipboardData.contentId) { // Comes from existing content @@ -2119,14 +2175,11 @@ H5P.createTitle = function (rawTitle, maxLength) { return path; // Will automatically be looked for in tmp folder }); - if (clipboardData.from === undefined) { - clipboardData.generic = clipboardData.specific; - } - else if (clipboardData.generic) { - clipboardData.generic = clipboardData.specific[clipboardData.generic]; - } if (clipboardData.generic) { + // Use reference instead of key + clipboardData.generic = clipboardData.specific[clipboardData.generic]; + // Avoid multiple content with same ID delete clipboardData.generic.subContentId; } @@ -2137,10 +2190,11 @@ H5P.createTitle = function (rawTitle, maxLength) { /** * Update file URLs. Useful when copying content. * + * @private * @param {object} params Reference * @param {function} handler Modifies the path to work when pasted */ - H5P.updateFileUrls = function (params, handler) { + var updateFileUrls = function (params, handler) { for (var prop in params) { if (params.hasOwnProperty(prop) && params[prop] instanceof Object) { var obj = params[prop]; @@ -2148,7 +2202,7 @@ H5P.createTitle = function (rawTitle, maxLength) { obj.path = handler(obj.path); } else { - H5P.updateFileUrls(obj, handler); + updateFileUrls(obj, handler); } } } @@ -2160,6 +2214,10 @@ H5P.createTitle = function (rawTitle, maxLength) { window.addEventListener('storage', function (event) { // Pick up clipboard changes from other tabs if (event.key === 'h5pClipboard') { + // Clear cache + parsedClipboard = null; + + // Trigger an event so all 'Paste' buttons may be enabled. H5P.externalDispatcher.trigger('datainclipboard', {reset: event.newValue === null}); } });