2014-10-15 08:09:46 +11:00
|
|
|
<?php namespace System\Classes;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2014-12-17 13:46:08 +11:00
|
|
|
use App;
|
2016-06-02 05:22:15 +10:00
|
|
|
use Url;
|
2014-05-14 23:24:20 +10:00
|
|
|
use File;
|
|
|
|
use Lang;
|
2016-06-03 07:22:05 +10:00
|
|
|
use Event;
|
2014-05-14 23:24:20 +10:00
|
|
|
use Cache;
|
2014-07-16 18:28:15 +10:00
|
|
|
use Route;
|
2014-05-14 23:24:20 +10:00
|
|
|
use Config;
|
2014-06-26 20:37:55 +10:00
|
|
|
use Request;
|
2014-05-14 23:24:20 +10:00
|
|
|
use Response;
|
|
|
|
use Assetic\Asset\FileAsset;
|
|
|
|
use Assetic\Asset\GlobAsset;
|
|
|
|
use Assetic\Asset\AssetCache;
|
2016-06-02 05:22:15 +10:00
|
|
|
use Assetic\Asset\AssetCollection;
|
2016-07-30 15:06:50 +10:00
|
|
|
use Assetic\Factory\AssetFactory;
|
2016-06-18 09:23:16 +10:00
|
|
|
use October\Rain\Parse\Assetic\FilesystemCache;
|
2016-06-02 05:22:15 +10:00
|
|
|
use System\Helpers\Cache as CacheHelper;
|
2015-01-28 18:03:35 +11:00
|
|
|
use ApplicationException;
|
2014-12-17 13:46:08 +11:00
|
|
|
use DateTime;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/**
|
2017-03-16 17:08:20 +11:00
|
|
|
* Combiner class used for combining JavaScript and StyleSheet files.
|
2014-05-14 23:24:20 +10:00
|
|
|
*
|
2017-03-16 17:08:20 +11:00
|
|
|
* This works by taking a collection of asset locations, serializing them,
|
|
|
|
* then storing them in the session with a unique ID. The ID is then used
|
|
|
|
* to generate a URL to the `/combine` route via the system controller.
|
|
|
|
*
|
|
|
|
* When the combine route is hit, the unique ID is used to serve up the
|
|
|
|
* assets -- minified, compiled or both. Special E-Tags are used to prevent
|
|
|
|
* compilation and delivery of cached assets that are unchanged.
|
|
|
|
*
|
|
|
|
* Use the `CombineAssets::combine` method to combine your own assets.
|
|
|
|
*
|
|
|
|
* The functionality of this class is controlled by these config items:
|
|
|
|
*
|
|
|
|
* - cms.enableAssetCache - Cache untouched assets
|
|
|
|
* - cms.enableAssetMinify - Compress assets using minification
|
|
|
|
* - cms.enableAssetDeepHashing - Advanced caching of imports
|
|
|
|
*
|
|
|
|
* @see System\Classes\SystemController System controller
|
|
|
|
* @see https://octobercms.com/docs/services/session Session service
|
2014-05-14 23:24:20 +10:00
|
|
|
* @package october\system
|
|
|
|
* @author Alexey Bobkov, Samuel Georges
|
|
|
|
*/
|
|
|
|
class CombineAssets
|
|
|
|
{
|
2015-01-12 20:08:31 +11:00
|
|
|
use \October\Rain\Support\Traits\Singleton;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array A list of known JavaScript extensions.
|
|
|
|
*/
|
|
|
|
protected static $jsExtensions = ['js'];
|
2014-10-15 08:09:46 +11:00
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
/**
|
|
|
|
* @var array A list of known StyleSheet extensions.
|
|
|
|
*/
|
|
|
|
protected static $cssExtensions = ['css', 'less', 'scss', 'sass'];
|
|
|
|
|
2014-08-29 19:23:09 +10:00
|
|
|
/**
|
|
|
|
* @var array Aliases for asset file paths.
|
|
|
|
*/
|
|
|
|
protected $aliases = [];
|
|
|
|
|
2015-01-12 20:08:31 +11:00
|
|
|
/**
|
|
|
|
* @var array Bundles that are compiled to the filesystem.
|
|
|
|
*/
|
|
|
|
protected $bundles = [];
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
/**
|
|
|
|
* @var array Filters to apply to each file.
|
|
|
|
*/
|
|
|
|
protected $filters = [];
|
|
|
|
|
|
|
|
/**
|
2015-02-17 20:58:38 +11:00
|
|
|
* @var string The local path context to find assets.
|
2014-05-14 23:24:20 +10:00
|
|
|
*/
|
2015-02-17 20:58:38 +11:00
|
|
|
protected $localPath;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string The output folder for storing combined files.
|
|
|
|
*/
|
|
|
|
protected $storagePath;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var bool Cache untouched files.
|
|
|
|
*/
|
|
|
|
public $useCache = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var bool Compress (minify) asset files.
|
|
|
|
*/
|
|
|
|
public $useMinify = false;
|
|
|
|
|
2016-07-30 15:06:50 +10:00
|
|
|
/**
|
|
|
|
* @var bool When true, cache will be busted when an import is modified.
|
|
|
|
* Enabling this feature will make page loading slower.
|
|
|
|
*/
|
|
|
|
public $useDeepHashing = false;
|
|
|
|
|
2015-01-12 20:08:31 +11:00
|
|
|
/**
|
|
|
|
* @var array Cache of registration callbacks.
|
|
|
|
*/
|
|
|
|
private static $callbacks = [];
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
*/
|
2015-01-12 20:08:31 +11:00
|
|
|
public function init()
|
2014-05-14 23:24:20 +10:00
|
|
|
{
|
|
|
|
/*
|
2014-12-06 13:43:06 +11:00
|
|
|
* Register preferences
|
2014-05-14 23:24:20 +10:00
|
|
|
*/
|
|
|
|
$this->useCache = Config::get('cms.enableAssetCache', false);
|
2014-12-06 13:43:06 +11:00
|
|
|
$this->useMinify = Config::get('cms.enableAssetMinify', null);
|
2016-07-30 15:06:50 +10:00
|
|
|
$this->useDeepHashing = Config::get('cms.enableAssetDeepHashing', null);
|
2014-12-06 13:43:06 +11:00
|
|
|
|
|
|
|
if ($this->useMinify === null) {
|
|
|
|
$this->useMinify = !Config::get('app.debug', false);
|
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2016-07-30 15:06:50 +10:00
|
|
|
if ($this->useDeepHashing === null) {
|
|
|
|
$this->useDeepHashing = Config::get('app.debug', false);
|
|
|
|
}
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
/*
|
2014-08-29 19:23:09 +10:00
|
|
|
* Register JavaScript filters
|
2014-05-14 23:24:20 +10:00
|
|
|
*/
|
2016-06-18 09:23:16 +10:00
|
|
|
$this->registerFilter('js', new \October\Rain\Parse\Assetic\JavascriptImporter);
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/*
|
2014-08-29 19:23:09 +10:00
|
|
|
* Register CSS filters
|
2014-05-14 23:24:20 +10:00
|
|
|
*/
|
|
|
|
$this->registerFilter('css', new \Assetic\Filter\CssImportFilter);
|
2016-07-10 19:44:54 +02:00
|
|
|
$this->registerFilter(['css', 'less', 'scss'], new \Assetic\Filter\CssRewriteFilter);
|
2016-06-18 09:23:16 +10:00
|
|
|
$this->registerFilter('less', new \October\Rain\Parse\Assetic\LessCompiler);
|
2016-07-10 19:44:54 +02:00
|
|
|
$this->registerFilter('scss', new \October\Rain\Parse\Assetic\ScssCompiler);
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/*
|
2014-05-17 18:08:01 +02:00
|
|
|
* Minification filters
|
2014-05-14 23:24:20 +10:00
|
|
|
*/
|
|
|
|
if ($this->useMinify) {
|
|
|
|
$this->registerFilter('js', new \Assetic\Filter\JSMinFilter);
|
2016-07-10 19:44:54 +02:00
|
|
|
$this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Parse\Assetic\StylesheetMinify);
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
2014-08-29 19:23:09 +10:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Common Aliases
|
|
|
|
*/
|
2015-01-12 20:08:31 +11:00
|
|
|
$this->registerAlias('jquery', '~/modules/backend/assets/js/vendor/jquery.min.js');
|
|
|
|
$this->registerAlias('framework', '~/modules/system/assets/js/framework.js');
|
|
|
|
$this->registerAlias('framework.extras', '~/modules/system/assets/js/framework.extras.js');
|
|
|
|
$this->registerAlias('framework.extras', '~/modules/system/assets/css/framework.extras.css');
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Deferred registration
|
|
|
|
*/
|
|
|
|
foreach (static::$callbacks as $callback) {
|
|
|
|
$callback($this);
|
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Combines JavaScript or StyleSheet file references
|
|
|
|
* to produce a page relative URL to the combined contents.
|
2017-03-16 17:08:20 +11:00
|
|
|
*
|
|
|
|
* $assets = [
|
|
|
|
* 'assets/vendor/mustache/mustache.js',
|
|
|
|
* 'assets/js/vendor/jquery.ui.widget.js',
|
|
|
|
* 'assets/js/vendor/canvas-to-blob.js',
|
|
|
|
* ];
|
|
|
|
*
|
|
|
|
* CombineAssets::combine($assets, base_path('plugins/acme/blog'));
|
|
|
|
*
|
|
|
|
* @param array $assets Collection of assets
|
|
|
|
* @param string $localPath Prefix all assets with this path (optional)
|
2014-05-14 23:24:20 +10:00
|
|
|
* @return string URL to contents.
|
|
|
|
*/
|
2015-02-17 20:58:38 +11:00
|
|
|
public static function combine($assets = [], $localPath = null)
|
2014-05-14 23:24:20 +10:00
|
|
|
{
|
2015-02-17 20:58:38 +11:00
|
|
|
return self::instance()->prepareRequest($assets, $localPath);
|
2015-01-12 20:08:31 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Combines a collection of assets files to a destination file
|
2017-03-16 17:08:20 +11:00
|
|
|
*
|
|
|
|
* $assets = [
|
|
|
|
* 'assets/less/header.less',
|
|
|
|
* 'assets/less/footer.less',
|
|
|
|
* ];
|
|
|
|
*
|
|
|
|
* CombineAssets::combineToFile(
|
|
|
|
* $assets,
|
|
|
|
* base_path('themes/website/assets/theme.less'),
|
|
|
|
* base_path('themes/website')
|
|
|
|
* );
|
|
|
|
*
|
|
|
|
* @param array $assets Collection of assets
|
|
|
|
* @param string $destination Write the combined file to this location
|
|
|
|
* @param string $localPath Prefix all assets with this path (optional)
|
2015-01-12 20:08:31 +11:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-03-16 17:08:20 +11:00
|
|
|
public function combineToFile($assets = [], $destination, $localPath = null)
|
2015-01-12 20:08:31 +11:00
|
|
|
{
|
|
|
|
// Disable cache always
|
|
|
|
$this->storagePath = null;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2015-01-12 20:08:31 +11:00
|
|
|
list($assets, $extension) = $this->prepareAssets($assets);
|
|
|
|
|
|
|
|
$rewritePath = File::localToPublic(dirname($destination));
|
|
|
|
$combiner = $this->prepareCombiner($assets, $rewritePath);
|
|
|
|
|
|
|
|
$contents = $combiner->dump();
|
|
|
|
File::put($destination, $contents);
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
2014-08-29 19:23:09 +10:00
|
|
|
/**
|
|
|
|
* Returns the combined contents from a prepared cache identifier.
|
2016-11-16 20:09:46 +01:00
|
|
|
* @param string $cacheKey Cache identifier.
|
2014-08-29 19:23:09 +10:00
|
|
|
* @return string Combined file contents.
|
|
|
|
*/
|
2016-06-03 07:22:05 +10:00
|
|
|
public function getContents($cacheKey)
|
2014-08-29 19:23:09 +10:00
|
|
|
{
|
2016-06-03 07:22:05 +10:00
|
|
|
$cacheInfo = $this->getCache($cacheKey);
|
2014-10-11 01:22:03 +02:00
|
|
|
if (!$cacheInfo) {
|
2016-06-03 07:22:05 +10:00
|
|
|
throw new ApplicationException(Lang::get('system::lang.combiner.not_found', ['name'=>$cacheKey]));
|
2014-10-11 01:22:03 +02:00
|
|
|
}
|
2014-08-29 19:23:09 +10:00
|
|
|
|
2015-02-17 20:58:38 +11:00
|
|
|
$this->localPath = $cacheInfo['path'];
|
2016-01-15 10:07:39 +01:00
|
|
|
$this->storagePath = storage_path('cms/combiner/assets');
|
2014-08-29 19:23:09 +10:00
|
|
|
|
2016-11-23 08:42:47 +11:00
|
|
|
/*
|
|
|
|
* Analyse cache information
|
|
|
|
*/
|
|
|
|
$lastModifiedTime = gmdate("D, d M Y H:i:s \G\M\T", array_get($cacheInfo, 'lastMod'));
|
|
|
|
$etag = array_get($cacheInfo, 'etag');
|
|
|
|
$mime = (array_get($cacheInfo, 'extension') == 'css')
|
|
|
|
? 'text/css'
|
|
|
|
: 'application/javascript';
|
2014-08-29 19:23:09 +10:00
|
|
|
|
2016-11-23 08:42:47 +11:00
|
|
|
/*
|
|
|
|
* Set 304 Not Modified header, if necessary
|
|
|
|
*/
|
2014-08-29 19:23:09 +10:00
|
|
|
header_remove();
|
2016-11-23 08:42:47 +11:00
|
|
|
$response = Response::make();
|
2014-08-29 19:23:09 +10:00
|
|
|
$response->header('Content-Type', $mime);
|
2016-11-23 08:42:47 +11:00
|
|
|
$response->setLastModified(new DateTime($lastModifiedTime));
|
|
|
|
$response->setEtag($etag);
|
2017-03-31 13:02:16 +11:00
|
|
|
$response->setPublic();
|
2016-11-23 08:42:47 +11:00
|
|
|
$modified = !$response->isNotModified(App::make('request'));
|
2014-12-17 13:46:08 +11:00
|
|
|
|
|
|
|
/*
|
2016-11-23 08:42:47 +11:00
|
|
|
* Request says response is cached, no code evaluation needed
|
2014-12-17 13:46:08 +11:00
|
|
|
*/
|
2016-11-23 08:42:47 +11:00
|
|
|
if ($modified) {
|
|
|
|
$this->setHashOnCombinerFilters($cacheKey);
|
|
|
|
$combiner = $this->prepareCombiner($cacheInfo['files']);
|
|
|
|
$contents = $combiner->dump();
|
|
|
|
$response->setContent($contents);
|
|
|
|
}
|
2014-08-29 19:23:09 +10:00
|
|
|
|
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-01-12 20:08:31 +11:00
|
|
|
* Prepares an array of assets by normalizing the collection
|
|
|
|
* and processing aliases.
|
2016-11-16 20:09:46 +01:00
|
|
|
* @param array $assets
|
2015-01-12 20:08:31 +11:00
|
|
|
* @return array
|
2014-08-29 19:23:09 +10:00
|
|
|
*/
|
2015-01-12 20:08:31 +11:00
|
|
|
protected function prepareAssets(array $assets)
|
2014-08-29 19:23:09 +10:00
|
|
|
{
|
2014-10-11 01:22:03 +02:00
|
|
|
if (!is_array($assets)) {
|
2014-05-14 23:24:20 +10:00
|
|
|
$assets = [$assets];
|
2014-10-11 01:22:03 +02:00
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Split assets in to groups.
|
|
|
|
*/
|
|
|
|
$combineJs = [];
|
|
|
|
$combineCss = [];
|
|
|
|
|
|
|
|
foreach ($assets as $asset) {
|
2014-08-29 19:23:09 +10:00
|
|
|
/*
|
|
|
|
* Allow aliases to go through without an extension
|
|
|
|
*/
|
|
|
|
if (substr($asset, 0, 1) == '@') {
|
|
|
|
$combineJs[] = $asset;
|
|
|
|
$combineCss[] = $asset;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
$extension = File::extension($asset);
|
|
|
|
|
|
|
|
if (in_array($extension, self::$jsExtensions)) {
|
|
|
|
$combineJs[] = $asset;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (in_array($extension, self::$cssExtensions)) {
|
|
|
|
$combineCss[] = $asset;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Determine which group of assets to combine.
|
|
|
|
*/
|
2014-08-29 19:23:09 +10:00
|
|
|
if (count($combineCss) > count($combineJs)) {
|
|
|
|
$extension = 'css';
|
|
|
|
$assets = $combineCss;
|
2014-11-01 12:00:45 +11:00
|
|
|
}
|
|
|
|
else {
|
2014-05-14 23:24:20 +10:00
|
|
|
$extension = 'js';
|
|
|
|
$assets = $combineJs;
|
|
|
|
}
|
2014-08-29 19:23:09 +10:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Apply registered aliases
|
|
|
|
*/
|
|
|
|
if ($aliasMap = $this->getAliases($extension)) {
|
|
|
|
foreach ($assets as $key => $asset) {
|
2014-10-11 01:22:03 +02:00
|
|
|
if (substr($asset, 0, 1) !== '@') {
|
2014-08-29 19:23:09 +10:00
|
|
|
continue;
|
2014-10-11 01:22:03 +02:00
|
|
|
}
|
2014-08-29 19:23:09 +10:00
|
|
|
$_asset = substr($asset, 1);
|
|
|
|
|
2014-10-11 01:22:03 +02:00
|
|
|
if (isset($aliasMap[$_asset])) {
|
2014-08-29 19:23:09 +10:00
|
|
|
$assets[$key] = $aliasMap[$_asset];
|
2014-10-11 01:22:03 +02:00
|
|
|
}
|
2014-08-29 19:23:09 +10:00
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
2015-01-12 20:08:31 +11:00
|
|
|
return [$assets, $extension];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-07-06 11:21:15 +02:00
|
|
|
* Combines asset file references of a single type to produce
|
2015-01-12 20:08:31 +11:00
|
|
|
* a URL reference to the combined contents.
|
2016-11-16 20:09:46 +01:00
|
|
|
* @param array $assets List of asset files.
|
|
|
|
* @param string $localPath File extension, used for aesthetic purposes only.
|
2015-01-12 20:08:31 +11:00
|
|
|
* @return string URL to contents.
|
|
|
|
*/
|
2015-02-17 20:58:38 +11:00
|
|
|
protected function prepareRequest(array $assets, $localPath = null)
|
2015-01-12 20:08:31 +11:00
|
|
|
{
|
2015-02-17 20:58:38 +11:00
|
|
|
if (substr($localPath, -1) != '/') {
|
|
|
|
$localPath = $localPath.'/';
|
2015-01-12 20:08:31 +11:00
|
|
|
}
|
|
|
|
|
2015-02-17 20:58:38 +11:00
|
|
|
$this->localPath = $localPath;
|
2016-01-15 10:07:39 +01:00
|
|
|
$this->storagePath = storage_path('cms/combiner/assets');
|
2015-01-12 20:08:31 +11:00
|
|
|
|
|
|
|
list($assets, $extension) = $this->prepareAssets($assets);
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
/*
|
|
|
|
* Cache and process
|
|
|
|
*/
|
2016-06-03 07:22:05 +10:00
|
|
|
$cacheKey = $this->getCacheKey($assets);
|
|
|
|
$cacheInfo = $this->useCache ? $this->getCache($cacheKey) : false;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
if (!$cacheInfo) {
|
2016-07-30 15:06:50 +10:00
|
|
|
$this->setHashOnCombinerFilters($cacheKey);
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
$combiner = $this->prepareCombiner($assets);
|
2016-07-30 16:14:54 +10:00
|
|
|
|
|
|
|
if ($this->useDeepHashing) {
|
|
|
|
$factory = new AssetFactory($this->localPath);
|
|
|
|
$lastMod = $factory->getLastModified($combiner);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$lastMod = $combiner->getLastModified();
|
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
$cacheInfo = [
|
2016-06-03 07:22:05 +10:00
|
|
|
'version' => $cacheKey.'-'.$lastMod,
|
|
|
|
'etag' => $cacheKey,
|
2014-12-17 13:46:08 +11:00
|
|
|
'lastMod' => $lastMod,
|
2014-05-16 15:01:31 +10:00
|
|
|
'files' => $assets,
|
2015-02-17 20:58:38 +11:00
|
|
|
'path' => $this->localPath,
|
2014-05-16 15:01:31 +10:00
|
|
|
'extension' => $extension
|
2014-05-14 23:24:20 +10:00
|
|
|
];
|
|
|
|
|
2016-06-03 07:22:05 +10:00
|
|
|
$this->putCache($cacheKey, $cacheInfo);
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
2014-12-17 13:46:08 +11:00
|
|
|
return $this->getCombinedUrl($cacheInfo['version']);
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the combined contents from a prepared cache identifier.
|
2016-11-16 20:09:46 +01:00
|
|
|
* @param array $assets List of asset files.
|
|
|
|
* @param string $rewritePath
|
2014-05-14 23:24:20 +10:00
|
|
|
* @return string Combined file contents.
|
|
|
|
*/
|
2016-07-27 11:49:16 +02:00
|
|
|
protected function prepareCombiner(array $assets, $rewritePath = null)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Extensibility
|
|
|
|
*/
|
|
|
|
Event::fire('cms.combiner.beforePrepare', [$this, $assets]);
|
|
|
|
|
|
|
|
$files = [];
|
|
|
|
$filesSalt = null;
|
|
|
|
foreach ($assets as $asset) {
|
|
|
|
$filters = $this->getFilters(File::extension($asset)) ?: [];
|
2017-04-27 00:17:05 -06:00
|
|
|
$path = file_exists($asset) ? $asset : File::symbolizePath($asset, null) ?: $this->localPath . $asset;
|
2016-07-27 11:49:16 +02:00
|
|
|
$files[] = new FileAsset($path, $filters, public_path());
|
|
|
|
$filesSalt .= $this->localPath . $asset;
|
|
|
|
}
|
|
|
|
$filesSalt = md5($filesSalt);
|
|
|
|
|
|
|
|
$collection = new AssetCollection($files, [], $filesSalt);
|
|
|
|
$collection->setTargetPath($this->getTargetPath($rewritePath));
|
|
|
|
|
|
|
|
if ($this->storagePath === null) {
|
|
|
|
return $collection;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!File::isDirectory($this->storagePath)) {
|
2017-01-20 07:20:08 +11:00
|
|
|
@File::makeDirectory($this->storagePath);
|
2016-07-27 11:49:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$cache = new FilesystemCache($this->storagePath);
|
|
|
|
|
|
|
|
$cachedFiles = [];
|
|
|
|
foreach ($files as $file) {
|
|
|
|
$cachedFiles[] = new AssetCache($file, $cache);
|
|
|
|
}
|
|
|
|
|
|
|
|
$cachedCollection = new AssetCollection($cachedFiles, [], $filesSalt);
|
|
|
|
$cachedCollection->setTargetPath($this->getTargetPath($rewritePath));
|
|
|
|
return $cachedCollection;
|
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2016-07-30 15:06:50 +10:00
|
|
|
/**
|
|
|
|
* Busts the cache based on a different cache key.
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
protected function setHashOnCombinerFilters($hash)
|
|
|
|
{
|
|
|
|
$allFilters = call_user_func_array('array_merge', $this->getFilters());
|
|
|
|
|
|
|
|
foreach ($allFilters as $filter) {
|
|
|
|
if (method_exists($filter, 'setHash')) {
|
|
|
|
$filter->setHash($hash);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a deep hash on filters that support it.
|
2016-11-16 20:09:46 +01:00
|
|
|
* @param array $assets List of asset files.
|
2016-07-30 15:06:50 +10:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
protected function getDeepHashFromAssets($assets)
|
|
|
|
{
|
|
|
|
$key = '';
|
|
|
|
|
2017-04-24 13:38:19 +02:00
|
|
|
$assetFiles = array_map(function ($file) {
|
2017-04-27 00:17:05 -06:00
|
|
|
return file_exists($file) ? $file : File::symbolizePath($file, null) ?: $this->localPath . $file;
|
2016-07-30 15:06:50 +10:00
|
|
|
}, $assets);
|
|
|
|
|
2016-07-30 16:05:37 +10:00
|
|
|
foreach ($assetFiles as $file) {
|
|
|
|
$filters = $this->getFilters(File::extension($file));
|
|
|
|
|
|
|
|
foreach ($filters as $filter) {
|
|
|
|
if (method_exists($filter, 'hashAsset')) {
|
2016-07-30 16:06:37 +10:00
|
|
|
$key .= $filter->hashAsset($file, $this->localPath);
|
2016-07-30 16:05:37 +10:00
|
|
|
}
|
2016-07-30 15:06:50 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $key;
|
|
|
|
}
|
|
|
|
|
2015-01-12 20:08:31 +11:00
|
|
|
/**
|
|
|
|
* Returns the URL used for accessing the combined files.
|
|
|
|
* @param string $outputFilename A custom file name to use.
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function getCombinedUrl($outputFilename = 'undefined.css')
|
|
|
|
{
|
|
|
|
$combineAction = 'System\Classes\Controller@combine';
|
|
|
|
$actionExists = Route::getRoutes()->getByAction($combineAction) !== null;
|
|
|
|
|
|
|
|
if ($actionExists) {
|
2016-06-02 05:22:15 +10:00
|
|
|
return Url::action($combineAction, [$outputFilename], false);
|
2015-01-12 20:08:31 +11:00
|
|
|
}
|
|
|
|
else {
|
2015-02-09 21:52:17 +11:00
|
|
|
return '/combine/'.$outputFilename;
|
2015-01-12 20:08:31 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-26 20:37:55 +10:00
|
|
|
/**
|
|
|
|
* Returns the target path for use with the combiner. The target
|
|
|
|
* path helps generate relative links within CSS.
|
|
|
|
*
|
|
|
|
* /combine returns combine/
|
|
|
|
* /index.php/combine returns index-php/combine/
|
|
|
|
*
|
2016-11-16 20:09:46 +01:00
|
|
|
* @param string|null $path
|
2014-06-26 20:37:55 +10:00
|
|
|
* @return string The new target path
|
|
|
|
*/
|
|
|
|
protected function getTargetPath($path = null)
|
|
|
|
{
|
2014-06-29 09:34:49 +10:00
|
|
|
if ($path === null) {
|
|
|
|
$baseUri = substr(Request::getBaseUrl(), strlen(Request::getBasePath()));
|
|
|
|
$path = $baseUri.'/combine';
|
|
|
|
}
|
2014-06-26 20:37:55 +10:00
|
|
|
|
2014-10-11 01:22:03 +02:00
|
|
|
if (strpos($path, '/') === 0) {
|
2014-06-26 20:37:55 +10:00
|
|
|
$path = substr($path, 1);
|
2014-10-11 01:22:03 +02:00
|
|
|
}
|
2014-06-26 20:37:55 +10:00
|
|
|
|
|
|
|
$path = str_replace('.', '-', $path).'/';
|
|
|
|
return $path;
|
|
|
|
}
|
|
|
|
|
2015-01-12 20:08:31 +11:00
|
|
|
//
|
|
|
|
// Registration
|
|
|
|
//
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a callback function that defines bundles.
|
|
|
|
* The callback function should register bundles by calling the manager's
|
2017-04-24 13:38:19 +02:00
|
|
|
* `registerBundle` method. Thi instance is passed to the callback
|
2017-03-16 17:08:20 +11:00
|
|
|
* function as an argument. Usage:
|
|
|
|
*
|
|
|
|
* CombineAssets::registerCallback(function($combiner){
|
|
|
|
* $combiner->registerBundle('~/modules/backend/assets/less/october.less');
|
|
|
|
* });
|
|
|
|
*
|
2015-01-12 20:08:31 +11:00
|
|
|
* @param callable $callback A callable function.
|
|
|
|
*/
|
|
|
|
public static function registerCallback(callable $callback)
|
|
|
|
{
|
|
|
|
self::$callbacks[] = $callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Filters
|
|
|
|
//
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a filter to apply to the combining process.
|
|
|
|
* @param string|array $extension Extension name. Eg: css
|
|
|
|
* @param object $filter Collection of files to combine.
|
2016-11-16 20:09:46 +01:00
|
|
|
* @return self
|
2015-01-12 20:08:31 +11:00
|
|
|
*/
|
|
|
|
public function registerFilter($extension, $filter)
|
|
|
|
{
|
|
|
|
if (is_array($extension)) {
|
|
|
|
foreach ($extension as $_extension) {
|
|
|
|
$this->registerFilter($_extension, $filter);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$extension = strtolower($extension);
|
|
|
|
|
|
|
|
if (!isset($this->filters[$extension])) {
|
|
|
|
$this->filters[$extension] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($filter !== null) {
|
|
|
|
$this->filters[$extension][] = $filter;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clears any registered filters.
|
|
|
|
* @param string $extension Extension name. Eg: css
|
2016-11-16 20:09:46 +01:00
|
|
|
* @return self
|
2015-01-12 20:08:31 +11:00
|
|
|
*/
|
|
|
|
public function resetFilters($extension = null)
|
|
|
|
{
|
|
|
|
if ($extension === null) {
|
|
|
|
$this->filters = [];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$this->filters[$extension] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns filters.
|
|
|
|
* @param string $extension Extension name. Eg: css
|
2016-11-16 20:09:46 +01:00
|
|
|
* @return self
|
2015-01-12 20:08:31 +11:00
|
|
|
*/
|
|
|
|
public function getFilters($extension = null)
|
|
|
|
{
|
|
|
|
if ($extension === null) {
|
|
|
|
return $this->filters;
|
|
|
|
}
|
|
|
|
elseif (isset($this->filters[$extension])) {
|
|
|
|
return $this->filters[$extension];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Bundles
|
|
|
|
//
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers an alias to use for a longer file reference.
|
|
|
|
* @param string $alias Alias name. Eg: framework
|
|
|
|
* @param object $filter Collection of files to combine
|
|
|
|
* @param string $extension Extension name. Eg: css
|
2016-11-16 20:09:46 +01:00
|
|
|
* @return self
|
2015-01-12 20:08:31 +11:00
|
|
|
*/
|
|
|
|
public function registerBundle($files, $destination = null, $extension = null)
|
|
|
|
{
|
|
|
|
if (!is_array($files)) {
|
|
|
|
$files = [$files];
|
|
|
|
}
|
|
|
|
|
|
|
|
$firstFile = array_values($files)[0];
|
|
|
|
|
|
|
|
if ($extension === null) {
|
|
|
|
$extension = File::extension($firstFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
$extension = strtolower(trim($extension));
|
|
|
|
|
|
|
|
if ($destination === null) {
|
|
|
|
$file = File::name($firstFile);
|
|
|
|
$path = dirname($firstFile);
|
2016-11-16 20:09:46 +01:00
|
|
|
$preprocessors = array_except(self::$cssExtensions, 'css');
|
2015-01-12 20:08:31 +11:00
|
|
|
|
2016-11-16 20:09:46 +01:00
|
|
|
if (in_array($extension, $preprocessors)) {
|
2015-01-12 20:08:31 +11:00
|
|
|
$cssPath = $path.'/../css';
|
2015-07-29 19:14:54 +10:00
|
|
|
if (
|
2016-11-16 20:09:46 +01:00
|
|
|
in_array(strtolower(basename($path)), $preprocessors) &&
|
2015-07-29 19:14:54 +10:00
|
|
|
File::isDirectory(File::symbolizePath($cssPath))
|
|
|
|
) {
|
2015-01-12 20:08:31 +11:00
|
|
|
$path = $cssPath;
|
|
|
|
}
|
|
|
|
$destination = $path.'/'.$file.'.css';
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$destination = $path.'/'.$file.'-min.'.$extension;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->bundles[$extension][$destination] = $files;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns bundles.
|
|
|
|
* @param string $extension Extension name. Eg: css
|
2016-11-16 20:09:46 +01:00
|
|
|
* @return self
|
2015-01-12 20:08:31 +11:00
|
|
|
*/
|
|
|
|
public function getBundles($extension = null)
|
|
|
|
{
|
|
|
|
if ($extension === null) {
|
|
|
|
return $this->bundles;
|
|
|
|
}
|
|
|
|
elseif (isset($this->bundles[$extension])) {
|
|
|
|
return $this->bundles[$extension];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Aliases
|
|
|
|
//
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register an alias to use for a longer file reference.
|
|
|
|
* @param string $alias Alias name. Eg: framework
|
|
|
|
* @param string $file Path to file to use for alias
|
|
|
|
* @param string $extension Extension name. Eg: css
|
2016-11-16 20:09:46 +01:00
|
|
|
* @return self
|
2015-01-12 20:08:31 +11:00
|
|
|
*/
|
|
|
|
public function registerAlias($alias, $file, $extension = null)
|
|
|
|
{
|
|
|
|
if ($extension === null) {
|
|
|
|
$extension = File::extension($file);
|
|
|
|
}
|
|
|
|
|
|
|
|
$extension = strtolower($extension);
|
|
|
|
|
|
|
|
if (!isset($this->aliases[$extension])) {
|
|
|
|
$this->aliases[$extension] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->aliases[$extension][$alias] = $file;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clears any registered aliases.
|
|
|
|
* @param string $extension Extension name. Eg: css
|
2016-11-16 20:09:46 +01:00
|
|
|
* @return self
|
2015-01-12 20:08:31 +11:00
|
|
|
*/
|
|
|
|
public function resetAliases($extension = null)
|
|
|
|
{
|
|
|
|
if ($extension === null) {
|
|
|
|
$this->aliases = [];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$this->aliases[$extension] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns aliases.
|
|
|
|
* @param string $extension Extension name. Eg: css
|
2016-11-16 20:09:46 +01:00
|
|
|
* @return self
|
2015-01-12 20:08:31 +11:00
|
|
|
*/
|
|
|
|
public function getAliases($extension = null)
|
|
|
|
{
|
|
|
|
if ($extension === null) {
|
|
|
|
return $this->aliases;
|
|
|
|
}
|
|
|
|
elseif (isset($this->aliases[$extension])) {
|
|
|
|
return $this->aliases[$extension];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Cache
|
|
|
|
//
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
/**
|
|
|
|
* Stores information about a asset collection against
|
|
|
|
* a cache identifier.
|
2016-11-16 20:09:46 +01:00
|
|
|
* @param string $cacheKey Cache identifier.
|
|
|
|
* @param array $cacheInfo List of asset files.
|
2014-05-14 23:24:20 +10:00
|
|
|
* @return bool Successful
|
|
|
|
*/
|
2016-06-03 07:22:05 +10:00
|
|
|
protected function putCache($cacheKey, array $cacheInfo)
|
2014-05-14 23:24:20 +10:00
|
|
|
{
|
2016-06-03 07:22:05 +10:00
|
|
|
$cacheKey = 'combiner.'.$cacheKey;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2016-06-03 07:22:05 +10:00
|
|
|
if (Cache::has($cacheKey)) {
|
2014-05-14 23:24:20 +10:00
|
|
|
return false;
|
2014-10-11 01:22:03 +02:00
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2016-06-03 07:22:05 +10:00
|
|
|
$this->putCacheIndex($cacheKey);
|
|
|
|
Cache::forever($cacheKey, base64_encode(serialize($cacheInfo)));
|
2014-05-14 23:24:20 +10:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Look up information about a cache identifier.
|
2016-11-16 20:09:46 +01:00
|
|
|
* @param string $cacheKey Cache identifier
|
2014-05-14 23:24:20 +10:00
|
|
|
* @return array Cache information
|
|
|
|
*/
|
2016-06-03 07:22:05 +10:00
|
|
|
protected function getCache($cacheKey)
|
2014-05-14 23:24:20 +10:00
|
|
|
{
|
2016-06-03 07:22:05 +10:00
|
|
|
$cacheKey = 'combiner.'.$cacheKey;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2016-06-03 07:22:05 +10:00
|
|
|
if (!Cache::has($cacheKey)) {
|
2014-05-14 23:24:20 +10:00
|
|
|
return false;
|
2014-10-11 01:22:03 +02:00
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2016-06-03 07:22:05 +10:00
|
|
|
return @unserialize(@base64_decode(Cache::get($cacheKey)));
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Builds a unique string based on assets
|
2016-11-16 20:09:46 +01:00
|
|
|
* @param array $assets Asset files
|
2014-05-14 23:24:20 +10:00
|
|
|
* @return string Unique identifier
|
|
|
|
*/
|
2016-06-03 07:22:05 +10:00
|
|
|
protected function getCacheKey(array $assets)
|
2014-05-14 23:24:20 +10:00
|
|
|
{
|
2016-06-03 07:22:05 +10:00
|
|
|
$cacheKey = $this->localPath . implode('|', $assets);
|
|
|
|
|
2016-07-30 15:06:50 +10:00
|
|
|
/*
|
|
|
|
* Deep hashing
|
|
|
|
*/
|
|
|
|
if ($this->useDeepHashing) {
|
|
|
|
$cacheKey .= $this->getDeepHashFromAssets($assets);
|
|
|
|
}
|
|
|
|
|
2016-06-03 07:22:05 +10:00
|
|
|
/*
|
|
|
|
* Extensibility
|
|
|
|
*/
|
|
|
|
$dataHolder = (object) ['key' => $cacheKey];
|
|
|
|
Event::fire('cms.combiner.getCacheKey', [$this, $dataHolder]);
|
|
|
|
$cacheKey = $dataHolder->key;
|
|
|
|
|
|
|
|
return md5($cacheKey);
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resets the combiner cache
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function resetCache()
|
|
|
|
{
|
2016-06-02 05:22:15 +10:00
|
|
|
if (Cache::has('combiner.index')) {
|
|
|
|
$index = (array) @unserialize(@base64_decode(Cache::get('combiner.index'))) ?: [];
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2016-06-03 07:22:05 +10:00
|
|
|
foreach ($index as $cacheKey) {
|
|
|
|
Cache::forget($cacheKey);
|
2016-06-02 05:22:15 +10:00
|
|
|
}
|
2016-04-16 08:22:31 +10:00
|
|
|
|
2016-06-02 05:22:15 +10:00
|
|
|
Cache::forget('combiner.index');
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
2016-06-02 05:22:15 +10:00
|
|
|
CacheHelper::instance()->clearCombiner();
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a cache identifier to the index store used for
|
|
|
|
* performing a reset of the cache.
|
2016-11-16 20:09:46 +01:00
|
|
|
* @param string $cacheKey Cache identifier
|
2014-05-14 23:24:20 +10:00
|
|
|
* @return bool Returns false if identifier is already in store
|
|
|
|
*/
|
2016-06-03 07:22:05 +10:00
|
|
|
protected function putCacheIndex($cacheKey)
|
2014-05-14 23:24:20 +10:00
|
|
|
{
|
|
|
|
$index = [];
|
2016-04-16 08:22:31 +10:00
|
|
|
|
2014-10-11 01:22:03 +02:00
|
|
|
if (Cache::has('combiner.index')) {
|
2016-04-16 08:22:31 +10:00
|
|
|
$index = (array) @unserialize(@base64_decode(Cache::get('combiner.index'))) ?: [];
|
2014-10-11 01:22:03 +02:00
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2016-06-03 07:22:05 +10:00
|
|
|
if (in_array($cacheKey, $index)) {
|
2014-05-14 23:24:20 +10:00
|
|
|
return false;
|
2014-10-11 01:22:03 +02:00
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2016-06-03 07:22:05 +10:00
|
|
|
$index[] = $cacheKey;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2016-04-16 08:22:31 +10:00
|
|
|
Cache::forever('combiner.index', base64_encode(serialize($index)));
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
return true;
|
|
|
|
}
|
2014-10-11 01:22:03 +02:00
|
|
|
}
|