From 00686b733d9194e4aa2469c4ddce36b016943655 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 13 Jan 2016 14:23:34 +0100 Subject: [PATCH] Added support for aggregating and caching library assets --- h5p-default-storage.class.php | 98 ++++++++++++++++++++++++++++++++++ h5p-file-storage.interface.php | 28 ++++++++++ h5p.classes.php | 40 ++++++++++++-- 3 files changed, 161 insertions(+), 5 deletions(-) diff --git a/h5p-default-storage.class.php b/h5p-default-storage.class.php index 517caf9..d445122 100644 --- a/h5p-default-storage.class.php +++ b/h5p-default-storage.class.php @@ -148,6 +148,104 @@ class DefaultStorage implements \H5P\FileStorage { } } + /** + * Will concatenate all JavaScrips and Stylesheets into two files in order + * to improve page performance. + * + * @param array $files + * A set of all the assets required for content to display + * @param string $key + * Hashed key for cached asset + */ + public function cacheAssets(&$files, $key) { + foreach ($files as $type => $assets) { + $content = ''; + + foreach ($assets as $asset) { + // Get content from asset file + $assetContent = file_get_contents($this->path . $asset->path); + $cssRelPath = preg_replace('/[^\/]+$/', '', $asset->path); + + // Get file content and concatenate + if ($type === 'scripts') { + $content .= $assetContent . ";\n"; + } + else { + // Rewrite relative URLs used inside stylesheets + $content .= preg_replace_callback( + '/url\([\'"]?([^"\')]+)[\'"]?\)/i', + function ($matches) use ($cssRelPath) { + return substr($matches[1], 0, 3) !== '../' ? $matches[0] : 'url("../' . $cssRelPath . $matches[1] . '")'; + }, + $assetContent + ) . "\n"; + } + } + + self::dirReady("{$this->path}/cachedassets"); + $ext = ($type === 'scripts' ? 'js' : 'css'); + file_put_contents("{$this->path}/cachedassets/{$key}.{$ext}", $content); + } + + // Use the newly created cache + $files = self::formatCachedAssets($key); + } + + /** + * Will check if there are cache assets available for content. + * + * @param string $key + * Hashed key for cached asset + * @return array + */ + public function getCachedAssets($key) { + if (!file_exists("{$this->path}/cachedassets/{$key}.js") || + !file_exists("{$this->path}/cachedassets/{$key}.css") { + return NULL; + } + return self::formatCachedAssets($key); + } + + /** + * Remove the aggregated cache files. + * + * @param array $keys + * The hash keys of removed files + */ + public function deleteCachedAssets($keys) { + $context = \context_system::instance(); + $fs = get_file_storage(); + + foreach ($keys as $hash) { + foreach (array('js', 'css') as $ext) { + $path = "{$this->path}/cachedassets/{$key}.{$ext}"; + if (file_exists($path)) { + unlink($path); + } + } + } + } + + /** + * Format the cached assets data the way it's supposed to be. + * + * @param string $key + * Hashed key for cached asset + * @return array + */ + private static function formatCachedAssets($key) { + return array( + 'scripts' => array((object) array( + 'path' => "/cachedassets/{$key}.js", + 'version' => '' + )), + 'styles' => array((object) array( + 'path' => "/cachedassets/{$key}.css", + 'version' => '' + )) + ); + } + /** * Recursive function for copying directories. * diff --git a/h5p-file-storage.interface.php b/h5p-file-storage.interface.php index 7056c6e..b0d4b4b 100644 --- a/h5p-file-storage.interface.php +++ b/h5p-file-storage.interface.php @@ -91,4 +91,32 @@ interface FileStorage { * @param string $filename */ public function deleteExport($filename); + + /** + * Will concatenate all JavaScrips and Stylesheets into two files in order + * to improve page performance. + * + * @param array $files + * A set of all the assets required for content to display + * @param string $key + * Hashed key for cached asset + */ + public function cacheAssets(&$files, $key); + + /** + * Will check if there are cache assets available for content. + * + * @param string $key + * Hashed key for cached asset + * @return array + */ + public function getCachedAssets($key); + + /** + * Remove the aggregated cache files. + * + * @param array $keys + * The hash keys of removed files + */ + public function deleteCachedAssets($keys); } diff --git a/h5p.classes.php b/h5p.classes.php index 1b5c52d..816f116 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -536,6 +536,29 @@ interface H5PFrameworkInterface { * @return boolean */ public function isContentSlugAvailable($slug); + + /** + * Stores hash keys for cached assets, aggregated JavaScripts and + * stylesheets, and connects it to libraries so that we know which cache file + * to delete when a library is updated. + * + * @param string $key + * Hash key for the given libraries + * @param array $libraries + * List of dependencies(libraries) used to create the key + */ + public function saveCachedAssets($key, $libraries); + + /** + * Locate hash keys for given library and delete them. + * Used when cache file are deleted. + * + * @param int $library_id + * Library identifier + * @return array + * List of hash keys removed + */ + public function deleteCachedAssets($library_id); } /** @@ -1345,6 +1368,12 @@ class H5PStorage { // Save library folder $this->h5pC->fs->saveLibrary($library); + // Remove cachedassets that uses this library + if ($this->h5pC->aggregateAssets && isset($library['libraryId'])) { + $removedKeys = $this->h5pF->deleteCachedAssets($library['libraryId']); + $this->h5pC->fs->deleteCachedAssets($removedKeys); + } + // Remove tmp folder H5PCore::deleteFileTree($library['uploadDirectory']); @@ -1680,6 +1709,8 @@ class H5PCore { $this->exportEnabled = $export; $this->development_mode = $development_mode; + $this->aggregateAssets = FALSE; // Off by default.. for now + if ($development_mode & H5PDevelopment::MODE_LIBRARY) { $this->h5pD = new H5PDevelopment($this->h5pF, $path . '/', $language); } @@ -1909,9 +1940,7 @@ class H5PCore { * @return array files. */ public function getDependenciesFiles($dependencies, $prefix = '') { - $aggregateAssets = TRUE; - - if ($aggregateAssets) { + if ($this->aggregateAssets) { // Get aggregated files for assets $key = self::getDependenciesHash($dependencies); @@ -1938,11 +1967,12 @@ class H5PCore { $this->getDependencyAssets($dependency, 'preloadedCss', $files['styles'], $prefix); } - if ($aggregateAssets) { + if ($this->aggregateAssets) { // Aggregate and store assets $this->fs->cacheAssets($files, $key); - // TODO: Update cache table + // Keep track of which libraries have been cached in case they are updated + $this->h5pF->saveCachedAssets($key, $dependencies); } return $files;