├── Gemfile ├── package.json ├── src ├── Assetic │ ├── Exception │ │ ├── Exception.php │ │ └── FilterException.php │ ├── Util │ │ ├── SassUtils.php │ │ ├── LessUtils.php │ │ ├── TraversableString.php │ │ ├── VarUtils.php │ │ └── FilesystemUtils.php │ ├── Factory │ │ ├── Resource │ │ │ ├── IteratorResourceInterface.php │ │ │ ├── FileResource.php │ │ │ ├── ResourceInterface.php │ │ │ ├── CoalescingDirectoryResource.php │ │ │ └── DirectoryResource.php │ │ ├── Loader │ │ │ ├── FormulaLoaderInterface.php │ │ │ ├── FunctionCallsFormulaLoader.php │ │ │ └── CachedFormulaLoader.php │ │ └── Worker │ │ │ ├── WorkerInterface.php │ │ │ ├── EnsureFilterWorker.php │ │ │ └── CacheBustingWorker.php │ ├── Filter │ │ ├── HashableInterface.php │ │ ├── Sass │ │ │ ├── ScssFilter.php │ │ │ └── BaseSassFilter.php │ │ ├── Yui │ │ │ ├── CssCompressorFilter.php │ │ │ ├── JsCompressorFilter.php │ │ │ └── BaseCompressorFilter.php │ │ ├── JSMinFilter.php │ │ ├── JSMinPlusFilter.php │ │ ├── FilterInterface.php │ │ ├── DependencyExtractorInterface.php │ │ ├── MinifyCssCompressorFilter.php │ │ ├── BaseNodeFilter.php │ │ ├── SeparatorFilter.php │ │ ├── PhpCssEmbedFilter.php │ │ ├── BaseProcessFilter.php │ │ ├── PackerFilter.php │ │ ├── BaseCssFilter.php │ │ ├── CallablesFilter.php │ │ ├── CssCacheBustingFilter.php │ │ ├── PackagerFilter.php │ │ ├── CssMinFilter.php │ │ ├── DartFilter.php │ │ ├── OptiPngFilter.php │ │ ├── FilterCollection.php │ │ ├── JpegoptimFilter.php │ │ ├── CoffeeScriptFilter.php │ │ ├── ReactJsxFilter.php │ │ ├── JSqueezeFilter.php │ │ ├── AutoprefixerFilter.php │ │ ├── RooleFilter.php │ │ ├── TypeScriptFilter.php │ │ ├── JpegtranFilter.php │ │ ├── EmberPrecompileFilter.php │ │ ├── GoogleClosure │ │ │ ├── BaseCompilerFilter.php │ │ │ └── CompilerJarFilter.php │ │ ├── HandlebarsFilter.php │ │ ├── StylusFilter.php │ │ ├── UglifyCssFilter.php │ │ ├── PngoutFilter.php │ │ ├── CssRewriteFilter.php │ │ ├── CssImportFilter.php │ │ ├── SassphpFilter.php │ │ ├── UglifyJs2Filter.php │ │ ├── CssEmbedFilter.php │ │ ├── SprocketsFilter.php │ │ ├── GssFilter.php │ │ ├── ScssphpFilter.php │ │ └── UglifyJsFilter.php │ ├── ValueSupplierInterface.php │ ├── Extension │ │ └── Twig │ │ │ ├── AsseticFilterNode.php │ │ │ ├── AsseticFilterFunction.php │ │ │ ├── TwigResource.php │ │ │ ├── AsseticFilterInvoker.php │ │ │ ├── ValueContainer.php │ │ │ ├── AsseticExtension.php │ │ │ └── TwigFormulaLoader.php │ ├── Cache │ │ ├── ArrayCache.php │ │ ├── CacheInterface.php │ │ ├── ApcCache.php │ │ ├── ExpiringCache.php │ │ ├── FilesystemCache.php │ │ └── ConfigCache.php │ ├── Asset │ │ ├── StringAsset.php │ │ ├── AssetCollectionInterface.php │ │ ├── FileAsset.php │ │ ├── HttpAsset.php │ │ ├── Iterator │ │ │ ├── AssetCollectionFilterIterator.php │ │ │ └── AssetCollectionIterator.php │ │ ├── GlobAsset.php │ │ └── AssetReference.php │ ├── FilterManager.php │ ├── AssetManager.php │ └── AssetWriter.php └── functions.php ├── CHANGELOG-1.0.md ├── LICENSE ├── CHANGELOG-1.2.md ├── composer.json └── CHANGELOG-1.1.md /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "sprockets", "~> 1.0.0" 4 | gem "sass" 5 | gem "compass" 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "uglifycss": "*", 4 | "coffee-script": "*", 5 | "stylus": "*", 6 | "nib": "*", 7 | "ember-precompile": "*", 8 | "typescript": "*", 9 | "less": "*", 10 | "handlebars": "*", 11 | "uglify-js": "*", 12 | "autoprefixer": "*", 13 | "autoprefixer-5": "^1.x", 14 | "autoprefixer-cli": "*", 15 | "roole": "*", 16 | "react-tools": "*", 17 | "clean-css": "*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Assetic/Exception/Exception.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | interface Exception 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Assetic/Util/SassUtils.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | abstract class SassUtils extends CssUtils 20 | { 21 | const REGEX_COMMENTS = '/((?:\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)|\/\/[^\n]+)/'; 22 | } 23 | -------------------------------------------------------------------------------- /src/Assetic/Factory/Resource/IteratorResourceInterface.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | interface IteratorResourceInterface extends ResourceInterface, \IteratorAggregate 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Assetic/Filter/HashableInterface.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | interface HashableInterface 20 | { 21 | /** 22 | * Generates a hash for the object 23 | * 24 | * @return string Object hash 25 | */ 26 | public function hash(); 27 | } 28 | -------------------------------------------------------------------------------- /src/Assetic/ValueSupplierInterface.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | interface ValueSupplierInterface 22 | { 23 | /** 24 | * Returns a map of values. 25 | * 26 | * @return array 27 | */ 28 | public function getValues(); 29 | } 30 | -------------------------------------------------------------------------------- /src/Assetic/Filter/Sass/ScssFilter.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class ScssFilter extends SassFilter 21 | { 22 | public function __construct($sassPath = '/usr/bin/sass', $rubyPath = null) 23 | { 24 | parent::__construct($sassPath, $rubyPath); 25 | 26 | $this->setScss(true); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Assetic/Extension/Twig/AsseticFilterNode.php: -------------------------------------------------------------------------------- 1 | raw(sprintf('$this->env->getExtension(\'Assetic\\Extension\\Twig\\AsseticExtension\')->getFilterInvoker(\'%s\')->invoke', $this->getAttribute('name'))); 19 | 20 | $this->compileArguments($compiler); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Assetic/Extension/Twig/AsseticFilterFunction.php: -------------------------------------------------------------------------------- 1 | false, 20 | 'needs_context' => false, 21 | 'node_class' => '\Assetic\Extension\Twig\AsseticFilterNode', 22 | ))); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Assetic/Filter/Yui/CssCompressorFilter.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class CssCompressorFilter extends BaseCompressorFilter 23 | { 24 | public function filterDump(AssetInterface $asset) 25 | { 26 | $asset->setContent($this->compress($asset->getContent(), 'css')); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Assetic/Util/LessUtils.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | abstract class LessUtils extends CssUtils 20 | { 21 | const REGEX_IMPORTS = '/@import(?:-once)? (?:\([a-z]*\) )?(?:url\()?(\'|"|)(?P[^\'"\)\n\r]*)\1\)?;?/'; 22 | const REGEX_IMPORTS_NO_URLS = '/@import(?:-once)? (?:\([a-z]*\) )?(?!url\()(\'|"|)(?P[^\'"\)\n\r]*)\1;?/'; 23 | const REGEX_COMMENTS = '/((?:\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)|\/\/[^\n]+)/'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Assetic/Filter/JSMinFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class JSMinFilter implements FilterInterface 25 | { 26 | public function filterLoad(AssetInterface $asset) 27 | { 28 | } 29 | 30 | public function filterDump(AssetInterface $asset) 31 | { 32 | $asset->setContent(\JSMin::minify($asset->getContent())); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Assetic/Filter/JSMinPlusFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class JSMinPlusFilter implements FilterInterface 25 | { 26 | public function filterLoad(AssetInterface $asset) 27 | { 28 | } 29 | 30 | public function filterDump(AssetInterface $asset) 31 | { 32 | $asset->setContent(\JSMinPlus::minify($asset->getContent())); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Assetic/Filter/FilterInterface.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | interface FilterInterface 22 | { 23 | /** 24 | * Filters an asset after it has been loaded. 25 | * 26 | * @param AssetInterface $asset An asset 27 | */ 28 | public function filterLoad(AssetInterface $asset); 29 | 30 | /** 31 | * Filters an asset just before it's dumped. 32 | * 33 | * @param AssetInterface $asset An asset 34 | */ 35 | public function filterDump(AssetInterface $asset); 36 | } 37 | -------------------------------------------------------------------------------- /src/Assetic/Factory/Loader/FormulaLoaderInterface.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | interface FormulaLoaderInterface 22 | { 23 | /** 24 | * Loads formulae from a resource. 25 | * 26 | * Formulae should be loaded the same regardless of the current debug 27 | * mode. Debug considerations should happen downstream. 28 | * 29 | * @param ResourceInterface $resource A resource 30 | * 31 | * @return array An array of formulae 32 | */ 33 | public function load(ResourceInterface $resource); 34 | } 35 | -------------------------------------------------------------------------------- /src/Assetic/Factory/Worker/WorkerInterface.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | interface WorkerInterface 23 | { 24 | /** 25 | * Processes an asset. 26 | * 27 | * @param AssetInterface $asset An asset 28 | * @param AssetFactory $factory The factory 29 | * 30 | * @return AssetInterface|null May optionally return a replacement asset 31 | */ 32 | public function process(AssetInterface $asset, AssetFactory $factory); 33 | } 34 | -------------------------------------------------------------------------------- /CHANGELOG-1.0.md: -------------------------------------------------------------------------------- 1 | 1.0.4 (August 28, 2012) 2 | ----------------------- 3 | 4 | * Fixed the Twig tag to avoid a fatal error when left unclosed 5 | * Added the HashableInterface for non-serialiable filters 6 | * Fixed a bug for compass on windows 7 | 8 | 1.0.3 (March 2, 2012) 9 | --------------------- 10 | 11 | * Added "boring" option to Compass filter 12 | * Fixed accumulation of load paths in Compass filter 13 | * Fixed issues in CssImport and CssRewrite filters 14 | 15 | 1.0.2 (August 26, 2011) 16 | ----------------------- 17 | 18 | * Twig 1.2 compatibility 19 | * Fixed filtering of large LessCSS assets 20 | * Fixed escaping of commands on Windows 21 | * Misc fixes to Compass filter 22 | * Removed default CssEmbed charset 23 | 24 | 1.0.1 (July 15, 2011) 25 | --------------------- 26 | 27 | * Fixed Twig error handling 28 | * Removed use of STDIN 29 | * Added inheritance of environment variables 30 | * Fixed Compass on Windows 31 | * Improved escaping of commands 32 | 33 | 1.0.0 (July 10, 2011) 34 | --------------------- 35 | 36 | * Initial release 37 | -------------------------------------------------------------------------------- /src/Assetic/Filter/DependencyExtractorInterface.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | interface DependencyExtractorInterface extends FilterInterface 23 | { 24 | /** 25 | * Returns child assets. 26 | * 27 | * @param AssetFactory $factory The asset factory 28 | * @param string $content The asset content 29 | * @param string $loadPath An optional load path 30 | * 31 | * @return AssetInterface[] Child assets 32 | */ 33 | public function getChildren(AssetFactory $factory, $content, $loadPath = null); 34 | } 35 | -------------------------------------------------------------------------------- /src/Assetic/Util/TraversableString.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class TraversableString implements \IteratorAggregate, \Countable 20 | { 21 | private $one; 22 | private $many; 23 | 24 | public function __construct($one, array $many) 25 | { 26 | $this->one = $one; 27 | $this->many = $many; 28 | } 29 | 30 | public function getIterator() 31 | { 32 | return new \ArrayIterator($this->many); 33 | } 34 | 35 | public function count() 36 | { 37 | return count($this->many); 38 | } 39 | 40 | public function __toString() 41 | { 42 | return (string) $this->one; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Assetic/Filter/MinifyCssCompressorFilter.php: -------------------------------------------------------------------------------- 1 | 23 | * @author http://code.google.com/u/1stvamp/ (Issue 64 patch) 24 | */ 25 | class MinifyCssCompressorFilter implements FilterInterface 26 | { 27 | public function filterLoad(AssetInterface $asset) 28 | { 29 | } 30 | 31 | public function filterDump(AssetInterface $asset) 32 | { 33 | $asset->setContent(\Minify_CSS_Compressor::process($asset->getContent())); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2015 OpenSky Project Inc 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/Assetic/Filter/BaseNodeFilter.php: -------------------------------------------------------------------------------- 1 | nodePaths; 21 | } 22 | 23 | public function setNodePaths(array $nodePaths) 24 | { 25 | $this->nodePaths = $nodePaths; 26 | } 27 | 28 | public function addNodePath($nodePath) 29 | { 30 | $this->nodePaths[] = $nodePath; 31 | } 32 | 33 | protected function createProcessBuilder(array $arguments = array()) 34 | { 35 | $pb = parent::createProcessBuilder($arguments); 36 | 37 | if ($this->nodePaths) { 38 | $this->mergeEnv($pb); 39 | $pb->setEnv('NODE_PATH', implode(PATH_SEPARATOR, $this->nodePaths)); 40 | } 41 | 42 | return $pb; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Assetic/Factory/Resource/FileResource.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class FileResource implements ResourceInterface 20 | { 21 | private $path; 22 | 23 | /** 24 | * Constructor. 25 | * 26 | * @param string $path The path to a file 27 | */ 28 | public function __construct($path) 29 | { 30 | $this->path = $path; 31 | } 32 | 33 | public function isFresh($timestamp) 34 | { 35 | return file_exists($this->path) && filemtime($this->path) <= $timestamp; 36 | } 37 | 38 | public function getContent() 39 | { 40 | return file_exists($this->path) ? file_get_contents($this->path) : ''; 41 | } 42 | 43 | public function __toString() 44 | { 45 | return $this->path; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Assetic/Factory/Resource/ResourceInterface.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | interface ResourceInterface 20 | { 21 | /** 22 | * Checks if a timestamp represents the latest resource. 23 | * 24 | * @param integer $timestamp A UNIX timestamp 25 | * 26 | * @return Boolean True if the timestamp is up to date 27 | */ 28 | public function isFresh($timestamp); 29 | 30 | /** 31 | * Returns the content of the resource. 32 | * 33 | * @return string The content 34 | */ 35 | public function getContent(); 36 | 37 | /** 38 | * Returns a unique string for the current resource. 39 | * 40 | * @return string A unique string to identity the current resource 41 | */ 42 | public function __toString(); 43 | } 44 | -------------------------------------------------------------------------------- /src/Assetic/Filter/SeparatorFilter.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class SeparatorFilter implements FilterInterface 23 | { 24 | /** 25 | * @var string 26 | */ 27 | private $separator; 28 | 29 | /** 30 | * Constructor. 31 | * 32 | * @param string $separator Separator to use between assets 33 | */ 34 | public function __construct($separator = ';') 35 | { 36 | $this->separator = $separator; 37 | } 38 | 39 | public function filterLoad(AssetInterface $asset) 40 | { 41 | } 42 | 43 | public function filterDump(AssetInterface $asset) 44 | { 45 | $asset->setContent($asset->getContent() . $this->separator); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Assetic/Cache/ArrayCache.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class ArrayCache implements CacheInterface 20 | { 21 | private $cache = array(); 22 | 23 | /** 24 | * @see CacheInterface::has() 25 | */ 26 | public function has($key) 27 | { 28 | return isset($this->cache[$key]); 29 | } 30 | 31 | /** 32 | * @see CacheInterface::get() 33 | */ 34 | public function get($key) 35 | { 36 | if (!$this->has($key)) { 37 | throw new \RuntimeException('There is no cached value for '.$key); 38 | } 39 | 40 | return $this->cache[$key]; 41 | } 42 | 43 | /** 44 | * @see CacheInterface::set() 45 | */ 46 | public function set($key, $value) 47 | { 48 | $this->cache[$key] = $value; 49 | } 50 | 51 | /** 52 | * @see CacheInterface::remove() 53 | */ 54 | public function remove($key) 55 | { 56 | unset($this->cache[$key]); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Assetic/Factory/Loader/FunctionCallsFormulaLoader.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class FunctionCallsFormulaLoader extends BasePhpFormulaLoader 20 | { 21 | protected function registerPrototypes() 22 | { 23 | return array( 24 | 'assetic_javascripts(*)' => array('output' => 'js/*.js'), 25 | 'assetic_stylesheets(*)' => array('output' => 'css/*.css'), 26 | 'assetic_image(*)' => array('output' => 'images/*'), 27 | ); 28 | } 29 | 30 | protected function registerSetupCode() 31 | { 32 | return <<<'EOF' 33 | function assetic_javascripts() 34 | { 35 | global $_call; 36 | $_call = func_get_args(); 37 | } 38 | 39 | function assetic_stylesheets() 40 | { 41 | global $_call; 42 | $_call = func_get_args(); 43 | } 44 | 45 | function assetic_image() 46 | { 47 | global $_call; 48 | $_call = func_get_args(); 49 | } 50 | 51 | EOF; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Assetic/Cache/CacheInterface.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | interface CacheInterface 20 | { 21 | /** 22 | * Checks if the cache has a value for a key. 23 | * 24 | * @param string $key A unique key 25 | * 26 | * @return Boolean Whether the cache has a value for this key 27 | */ 28 | public function has($key); 29 | 30 | /** 31 | * Returns the value for a key. 32 | * 33 | * @param string $key A unique key 34 | * 35 | * @return string|null The value in the cache 36 | */ 37 | public function get($key); 38 | 39 | /** 40 | * Sets a value in the cache. 41 | * 42 | * @param string $key A unique key 43 | * @param string $value The value to cache 44 | */ 45 | public function set($key, $value); 46 | 47 | /** 48 | * Removes a value from the cache. 49 | * 50 | * @param string $key A unique key 51 | */ 52 | public function remove($key); 53 | } 54 | -------------------------------------------------------------------------------- /src/Assetic/Filter/PhpCssEmbedFilter.php: -------------------------------------------------------------------------------- 1 | 22 | * @link https://github.com/krichprollsch/phpCssEmbed 23 | */ 24 | class PhpCssEmbedFilter implements DependencyExtractorInterface 25 | { 26 | private $presets = array(); 27 | 28 | public function setPresets(array $presets) 29 | { 30 | $this->presets = $presets; 31 | } 32 | 33 | public function filterLoad(AssetInterface $asset) 34 | { 35 | $pce = new CssEmbed(); 36 | if ($dir = $asset->getSourceDirectory()) { 37 | $pce->setRootDir($dir); 38 | } 39 | 40 | $asset->setContent($pce->embedString($asset->getContent())); 41 | } 42 | 43 | public function filterDump(AssetInterface $asset) 44 | { 45 | } 46 | 47 | public function getChildren(AssetFactory $factory, $content, $loadPath = null) 48 | { 49 | // todo 50 | return array(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Assetic/Extension/Twig/TwigResource.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class TwigResource implements ResourceInterface 22 | { 23 | private $loader; 24 | private $name; 25 | 26 | public function __construct(\Twig_LoaderInterface $loader, $name) 27 | { 28 | $this->loader = $loader; 29 | $this->name = $name; 30 | } 31 | 32 | public function getContent() 33 | { 34 | try { 35 | return method_exists($this->loader, 'getSourceContext') 36 | ? $this->loader->getSourceContext($this->name)->getCode() 37 | : $this->loader->getSource($this->name); 38 | } catch (\Twig_Error_Loader $e) { 39 | return ''; 40 | } 41 | } 42 | 43 | public function isFresh($timestamp) 44 | { 45 | try { 46 | return $this->loader->isFresh($this->name, $timestamp); 47 | } catch (\Twig_Error_Loader $e) { 48 | return false; 49 | } 50 | } 51 | 52 | public function __toString() 53 | { 54 | return $this->name; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Assetic/Asset/StringAsset.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class StringAsset extends BaseAsset 22 | { 23 | private $string; 24 | private $lastModified; 25 | 26 | /** 27 | * Constructor. 28 | * 29 | * @param string $content The content of the asset 30 | * @param array $filters Filters for the asset 31 | * @param string $sourceRoot The source asset root directory 32 | * @param string $sourcePath The source asset path 33 | */ 34 | public function __construct($content, $filters = array(), $sourceRoot = null, $sourcePath = null) 35 | { 36 | $this->string = $content; 37 | 38 | parent::__construct($filters, $sourceRoot, $sourcePath); 39 | } 40 | 41 | public function load(FilterInterface $additionalFilter = null) 42 | { 43 | $this->doLoad($this->string, $additionalFilter); 44 | } 45 | 46 | public function setLastModified($lastModified) 47 | { 48 | $this->lastModified = $lastModified; 49 | } 50 | 51 | public function getLastModified() 52 | { 53 | return $this->lastModified; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Assetic/Cache/ApcCache.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class ApcCache implements CacheInterface 20 | { 21 | public $ttl = 0; 22 | 23 | /** 24 | * @see CacheInterface::has() 25 | */ 26 | public function has($key) 27 | { 28 | return apc_exists($key); 29 | } 30 | 31 | /** 32 | * @see CacheInterface::get() 33 | */ 34 | public function get($key) 35 | { 36 | $value = apc_fetch($key, $success); 37 | 38 | if (!$success) { 39 | throw new \RuntimeException('There is no cached value for '.$key); 40 | } 41 | 42 | return $value; 43 | } 44 | 45 | /** 46 | * @see CacheInterface::set() 47 | */ 48 | public function set($key, $value) 49 | { 50 | $store = apc_store($key, $value, $this->ttl); 51 | 52 | if (!$store) { 53 | throw new \RuntimeException('Unable to store "'.$key.'" for '.$this->ttl.' seconds.'); 54 | } 55 | 56 | return $store; 57 | } 58 | 59 | /** 60 | * @see CacheInterface::remove() 61 | */ 62 | public function remove($key) 63 | { 64 | return apc_delete($key); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Assetic/Cache/ExpiringCache.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class ExpiringCache implements CacheInterface 20 | { 21 | private $cache; 22 | private $lifetime; 23 | 24 | public function __construct(CacheInterface $cache, $lifetime) 25 | { 26 | $this->cache = $cache; 27 | $this->lifetime = $lifetime; 28 | } 29 | 30 | public function has($key) 31 | { 32 | if ($this->cache->has($key)) { 33 | if (time() < $this->cache->get($key.'.expires')) { 34 | return true; 35 | } 36 | 37 | $this->cache->remove($key.'.expires'); 38 | $this->cache->remove($key); 39 | } 40 | 41 | return false; 42 | } 43 | 44 | public function get($key) 45 | { 46 | return $this->cache->get($key); 47 | } 48 | 49 | public function set($key, $value) 50 | { 51 | $this->cache->set($key.'.expires', time() + $this->lifetime); 52 | $this->cache->set($key, $value); 53 | } 54 | 55 | public function remove($key) 56 | { 57 | $this->cache->remove($key.'.expires'); 58 | $this->cache->remove($key); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Assetic/Filter/BaseProcessFilter.php: -------------------------------------------------------------------------------- 1 | timeout = $timeout; 31 | } 32 | 33 | /** 34 | * Creates a new process builder. 35 | * 36 | * @param array $arguments An optional array of arguments 37 | * 38 | * @return ProcessBuilder A new process builder 39 | */ 40 | protected function createProcessBuilder(array $arguments = array()) 41 | { 42 | $pb = new ProcessBuilder($arguments); 43 | 44 | if (null !== $this->timeout) { 45 | $pb->setTimeout($this->timeout); 46 | } 47 | 48 | return $pb; 49 | } 50 | 51 | protected function mergeEnv(ProcessBuilder $pb) 52 | { 53 | foreach (array_filter($_SERVER, 'is_scalar') as $key => $value) { 54 | $pb->setEnv($key, $value); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Assetic/Filter/PackerFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class PackerFilter implements FilterInterface 25 | { 26 | protected $encoding = 'None'; 27 | 28 | protected $fastDecode = true; 29 | 30 | protected $specialChars = false; 31 | 32 | public function setEncoding($encoding) 33 | { 34 | $this->encoding = $encoding; 35 | } 36 | 37 | public function setFastDecode($fastDecode) 38 | { 39 | $this->fastDecode = (bool) $fastDecode; 40 | } 41 | 42 | public function setSpecialChars($specialChars) 43 | { 44 | $this->specialChars = (bool) $specialChars; 45 | } 46 | 47 | public function filterLoad(AssetInterface $asset) 48 | { 49 | } 50 | 51 | public function filterDump(AssetInterface $asset) 52 | { 53 | $packer = new \JavaScriptPacker($asset->getContent(), $this->encoding, $this->fastDecode, $this->specialChars); 54 | $asset->setContent($packer->pack()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Assetic/Extension/Twig/AsseticFilterInvoker.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class AsseticFilterInvoker 20 | { 21 | private $factory; 22 | private $filters; 23 | private $options; 24 | 25 | public function __construct($factory, $filter) 26 | { 27 | $this->factory = $factory; 28 | 29 | if (is_array($filter) && isset($filter['filter'])) { 30 | $this->filters = (array) $filter['filter']; 31 | $this->options = isset($filter['options']) ? (array) $filter['options'] : array(); 32 | } else { 33 | $this->filters = (array) $filter; 34 | $this->options = array(); 35 | } 36 | } 37 | 38 | public function getFactory() 39 | { 40 | return $this->factory; 41 | } 42 | 43 | public function getFilters() 44 | { 45 | return $this->filters; 46 | } 47 | 48 | public function getOptions() 49 | { 50 | return $this->options; 51 | } 52 | 53 | public function invoke($input, array $options = array()) 54 | { 55 | $asset = $this->factory->createAsset($input, $this->filters, $options + $this->options); 56 | 57 | return $asset->getTargetPath(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Assetic/Filter/BaseCssFilter.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | abstract class BaseCssFilter implements FilterInterface 22 | { 23 | /** 24 | * @see CssUtils::filterReferences() 25 | */ 26 | protected function filterReferences($content, $callback, $limit = -1, &$count = 0) 27 | { 28 | return CssUtils::filterReferences($content, $callback, $limit, $count); 29 | } 30 | 31 | /** 32 | * @see CssUtils::filterUrls() 33 | */ 34 | protected function filterUrls($content, $callback, $limit = -1, &$count = 0) 35 | { 36 | return CssUtils::filterUrls($content, $callback, $limit, $count); 37 | } 38 | 39 | /** 40 | * @see CssUtils::filterImports() 41 | */ 42 | protected function filterImports($content, $callback, $limit = -1, &$count = 0, $includeUrl = true) 43 | { 44 | return CssUtils::filterImports($content, $callback, $limit, $count, $includeUrl); 45 | } 46 | 47 | /** 48 | * @see CssUtils::filterIEFilters() 49 | */ 50 | protected function filterIEFilters($content, $callback, $limit = -1, &$count = 0) 51 | { 52 | return CssUtils::filterIEFilters($content, $callback, $limit, $count); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Assetic/Filter/Yui/JsCompressorFilter.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class JsCompressorFilter extends BaseCompressorFilter 23 | { 24 | private $nomunge; 25 | private $preserveSemi; 26 | private $disableOptimizations; 27 | 28 | public function setNomunge($nomunge = true) 29 | { 30 | $this->nomunge = $nomunge; 31 | } 32 | 33 | public function setPreserveSemi($preserveSemi) 34 | { 35 | $this->preserveSemi = $preserveSemi; 36 | } 37 | 38 | public function setDisableOptimizations($disableOptimizations) 39 | { 40 | $this->disableOptimizations = $disableOptimizations; 41 | } 42 | 43 | public function filterDump(AssetInterface $asset) 44 | { 45 | $options = array(); 46 | 47 | if ($this->nomunge) { 48 | $options[] = '--nomunge'; 49 | } 50 | 51 | if ($this->preserveSemi) { 52 | $options[] = '--preserve-semi'; 53 | } 54 | 55 | if ($this->disableOptimizations) { 56 | $options[] = '--disable-optimizations'; 57 | } 58 | 59 | $asset->setContent($this->compress($asset->getContent(), 'js', $options)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Assetic/FilterManager.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class FilterManager 22 | { 23 | private $filters = array(); 24 | 25 | public function set($alias, FilterInterface $filter) 26 | { 27 | $this->checkName($alias); 28 | 29 | $this->filters[$alias] = $filter; 30 | } 31 | 32 | public function get($alias) 33 | { 34 | if (!isset($this->filters[$alias])) { 35 | throw new \InvalidArgumentException(sprintf('There is no "%s" filter.', $alias)); 36 | } 37 | 38 | return $this->filters[$alias]; 39 | } 40 | 41 | public function has($alias) 42 | { 43 | return isset($this->filters[$alias]); 44 | } 45 | 46 | public function getNames() 47 | { 48 | return array_keys($this->filters); 49 | } 50 | 51 | /** 52 | * Checks that a name is valid. 53 | * 54 | * @param string $name An asset name candidate 55 | * 56 | * @throws \InvalidArgumentException If the asset name is invalid 57 | */ 58 | protected function checkName($name) 59 | { 60 | if (!ctype_alnum(str_replace('_', '', $name))) { 61 | throw new \InvalidArgumentException(sprintf('The name "%s" is invalid.', $name)); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Assetic/Filter/CallablesFilter.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class CallablesFilter implements FilterInterface, DependencyExtractorInterface 23 | { 24 | private $loader; 25 | private $dumper; 26 | private $extractor; 27 | 28 | /** 29 | * @param callable|null $loader 30 | * @param callable|null $dumper 31 | * @param callable|null $extractor 32 | */ 33 | public function __construct($loader = null, $dumper = null, $extractor = null) 34 | { 35 | $this->loader = $loader; 36 | $this->dumper = $dumper; 37 | $this->extractor = $extractor; 38 | } 39 | 40 | public function filterLoad(AssetInterface $asset) 41 | { 42 | if (null !== $callable = $this->loader) { 43 | $callable($asset); 44 | } 45 | } 46 | 47 | public function filterDump(AssetInterface $asset) 48 | { 49 | if (null !== $callable = $this->dumper) { 50 | $callable($asset); 51 | } 52 | } 53 | 54 | public function getChildren(AssetFactory $factory, $content, $loadPath = null) 55 | { 56 | if (null !== $callable = $this->extractor) { 57 | return $callable($factory, $content, $loadPath); 58 | } 59 | 60 | return array(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Assetic/Filter/CssCacheBustingFilter.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class CssCacheBustingFilter extends BaseCssFilter 23 | { 24 | private $version; 25 | private $format = '%s?%s'; 26 | 27 | public function setVersion($version) 28 | { 29 | $this->version = $version; 30 | } 31 | 32 | public function setFormat($versionFormat) 33 | { 34 | $this->format = $versionFormat; 35 | } 36 | 37 | public function filterLoad(AssetInterface $asset) 38 | { 39 | } 40 | 41 | public function filterDump(AssetInterface $asset) 42 | { 43 | if (!$this->version) { 44 | return; 45 | } 46 | 47 | $version = $this->version; 48 | $format = $this->format; 49 | 50 | $asset->setContent($this->filterReferences( 51 | $asset->getContent(), 52 | function ($matches) use ($version, $format) { 53 | if (0 === strpos($matches['url'], 'data:')) { 54 | return $matches[0]; 55 | } 56 | 57 | return str_replace( 58 | $matches['url'], 59 | sprintf($format, $matches['url'], $version), 60 | $matches[0] 61 | ); 62 | } 63 | )); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Assetic/Cache/FilesystemCache.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class FilesystemCache implements CacheInterface 20 | { 21 | private $dir; 22 | 23 | public function __construct($dir) 24 | { 25 | $this->dir = $dir; 26 | } 27 | 28 | public function has($key) 29 | { 30 | return file_exists($this->dir.'/'.$key); 31 | } 32 | 33 | public function get($key) 34 | { 35 | $path = $this->dir.'/'.$key; 36 | 37 | if (!file_exists($path)) { 38 | throw new \RuntimeException('There is no cached value for '.$key); 39 | } 40 | 41 | return file_get_contents($path); 42 | } 43 | 44 | public function set($key, $value) 45 | { 46 | if (!is_dir($this->dir) && false === @mkdir($this->dir, 0777, true)) { 47 | throw new \RuntimeException('Unable to create directory '.$this->dir); 48 | } 49 | 50 | $path = $this->dir.'/'.$key; 51 | 52 | if (false === @file_put_contents($path, $value)) { 53 | throw new \RuntimeException('Unable to write file '.$path); 54 | } 55 | } 56 | 57 | public function remove($key) 58 | { 59 | $path = $this->dir.'/'.$key; 60 | 61 | if (file_exists($path) && false === @unlink($path)) { 62 | throw new \RuntimeException('Unable to remove file '.$path); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Assetic/Filter/PackagerFilter.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class PackagerFilter implements FilterInterface 24 | { 25 | private $packages; 26 | 27 | public function __construct(array $packages = array()) 28 | { 29 | $this->packages = $packages; 30 | } 31 | 32 | public function addPackage($package) 33 | { 34 | $this->packages[] = $package; 35 | } 36 | 37 | public function filterLoad(AssetInterface $asset) 38 | { 39 | static $manifest = <<getContent()); 51 | 52 | $packager = new \Packager(array_merge(array($package), $this->packages)); 53 | $content = $packager->build(array(), array(), array('Application'.$hash)); 54 | 55 | unlink($package.'/package.yml'); 56 | unlink($package.'/source.js'); 57 | rmdir($package); 58 | 59 | $asset->setContent($content); 60 | } 61 | 62 | public function filterDump(AssetInterface $asset) 63 | { 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Assetic/Filter/CssMinFilter.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class CssMinFilter implements FilterInterface 23 | { 24 | private $filters; 25 | private $plugins; 26 | 27 | public function __construct() 28 | { 29 | $this->filters = array(); 30 | $this->plugins = array(); 31 | } 32 | 33 | public function setFilters(array $filters) 34 | { 35 | $this->filters = $filters; 36 | } 37 | 38 | public function setFilter($name, $value) 39 | { 40 | $this->filters[$name] = $value; 41 | } 42 | 43 | public function setPlugins(array $plugins) 44 | { 45 | $this->plugins = $plugins; 46 | } 47 | 48 | public function setPlugin($name, $value) 49 | { 50 | $this->plugins[$name] = $value; 51 | } 52 | 53 | public function filterLoad(AssetInterface $asset) 54 | { 55 | } 56 | 57 | public function filterDump(AssetInterface $asset) 58 | { 59 | $filters = $this->filters; 60 | $plugins = $this->plugins; 61 | 62 | if (isset($filters['ImportImports']) && true === $filters['ImportImports']) { 63 | if ($dir = $asset->getSourceDirectory()) { 64 | $filters['ImportImports'] = array('BasePath' => $dir); 65 | } else { 66 | unset($filters['ImportImports']); 67 | } 68 | } 69 | 70 | $asset->setContent(\CssMin::minify($asset->getContent(), $filters, $plugins)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Assetic/Asset/AssetCollectionInterface.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | interface AssetCollectionInterface extends AssetInterface, \Traversable 20 | { 21 | /** 22 | * Returns all child assets. 23 | * 24 | * @return array An array of AssetInterface objects 25 | */ 26 | public function all(); 27 | 28 | /** 29 | * Adds an asset to the current collection. 30 | * 31 | * @param AssetInterface $asset An asset 32 | */ 33 | public function add(AssetInterface $asset); 34 | 35 | /** 36 | * Removes a leaf. 37 | * 38 | * @param AssetInterface $leaf The leaf to remove 39 | * @param Boolean $graceful Whether the failure should return false or throw an exception 40 | * 41 | * @return Boolean Whether the asset has been found 42 | * 43 | * @throws \InvalidArgumentException If the asset cannot be found 44 | */ 45 | public function removeLeaf(AssetInterface $leaf, $graceful = false); 46 | 47 | /** 48 | * Replaces an existing leaf with a new one. 49 | * 50 | * @param AssetInterface $needle The current asset to replace 51 | * @param AssetInterface $replacement The new asset 52 | * @param Boolean $graceful Whether the failure should return false or throw an exception 53 | * 54 | * @return Boolean Whether the asset has been found 55 | * 56 | * @throws \InvalidArgumentException If the asset cannot be found 57 | */ 58 | public function replaceLeaf(AssetInterface $needle, AssetInterface $replacement, $graceful = false); 59 | } 60 | -------------------------------------------------------------------------------- /src/Assetic/Filter/DartFilter.php: -------------------------------------------------------------------------------- 1 | dartBin = $dartBin; 30 | } 31 | 32 | public function filterLoad(AssetInterface $asset) 33 | { 34 | $input = FilesystemUtils::createTemporaryFile('dart'); 35 | $output = FilesystemUtils::createTemporaryFile('dart'); 36 | 37 | file_put_contents($input, $asset->getContent()); 38 | 39 | $pb = $this->createProcessBuilder() 40 | ->add($this->dartBin) 41 | ->add('-o'.$output) 42 | ->add($input) 43 | ; 44 | 45 | $proc = $pb->getProcess(); 46 | $code = $proc->run(); 47 | unlink($input); 48 | 49 | if (0 !== $code) { 50 | $this->cleanup($output); 51 | 52 | throw FilterException::fromProcess($proc); 53 | } 54 | 55 | if (!file_exists($output)) { 56 | throw new \RuntimeException('Error creating output file.'); 57 | } 58 | 59 | $asset->setContent(file_get_contents($output)); 60 | $this->cleanup($output); 61 | } 62 | 63 | public function filterDump(AssetInterface $asset) 64 | { 65 | } 66 | 67 | private function cleanup($file) 68 | { 69 | foreach (glob($file.'*') as $related) { 70 | unlink($related); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Assetic/Factory/Worker/EnsureFilterWorker.php: -------------------------------------------------------------------------------- 1 | 22 | * @todo A better asset-matcher mechanism 23 | */ 24 | class EnsureFilterWorker implements WorkerInterface 25 | { 26 | const CHECK_SOURCE = 1; 27 | const CHECK_TARGET = 2; 28 | 29 | private $pattern; 30 | private $filter; 31 | private $flags; 32 | 33 | /** 34 | * Constructor. 35 | * 36 | * @param string $pattern A regex for checking the asset's target URL 37 | * @param FilterInterface $filter A filter to apply if the regex matches 38 | * @param integer $flags Flags for what to check 39 | */ 40 | public function __construct($pattern, FilterInterface $filter, $flags = null) 41 | { 42 | if (null === $flags) { 43 | $flags = self::CHECK_SOURCE | self::CHECK_TARGET; 44 | } 45 | 46 | $this->pattern = $pattern; 47 | $this->filter = $filter; 48 | $this->flags = $flags; 49 | } 50 | 51 | public function process(AssetInterface $asset, AssetFactory $factory) 52 | { 53 | if ( 54 | (self::CHECK_SOURCE === (self::CHECK_SOURCE & $this->flags) && preg_match($this->pattern, $asset->getSourcePath())) 55 | || 56 | (self::CHECK_TARGET === (self::CHECK_TARGET & $this->flags) && preg_match($this->pattern, $asset->getTargetPath())) 57 | ) { 58 | $asset->ensureFilter($this->filter); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Assetic/Exception/FilterException.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class FilterException extends \RuntimeException implements Exception 22 | { 23 | private $originalMessage; 24 | private $input; 25 | 26 | public static function fromProcess(Process $proc) 27 | { 28 | $message = sprintf("An error occurred while running:\n%s", $proc->getCommandLine()); 29 | 30 | $errorOutput = $proc->getErrorOutput(); 31 | if (!empty($errorOutput)) { 32 | $message .= "\n\nError Output:\n".str_replace("\r", '', $errorOutput); 33 | } 34 | 35 | $output = $proc->getOutput(); 36 | if (!empty($output)) { 37 | $message .= "\n\nOutput:\n".str_replace("\r", '', $output); 38 | } 39 | 40 | return new self($message); 41 | } 42 | 43 | public function __construct($message, $code = 0, \Exception $previous = null) 44 | { 45 | parent::__construct($message, $code, $previous); 46 | 47 | $this->originalMessage = $message; 48 | } 49 | 50 | public function setInput($input) 51 | { 52 | $this->input = $input; 53 | $this->updateMessage(); 54 | 55 | return $this; 56 | } 57 | 58 | public function getInput() 59 | { 60 | return $this->input; 61 | } 62 | 63 | private function updateMessage() 64 | { 65 | $message = $this->originalMessage; 66 | 67 | if (!empty($this->input)) { 68 | $message .= "\n\nInput:\n".$this->input; 69 | } 70 | 71 | $this->message = $message; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /CHANGELOG-1.2.md: -------------------------------------------------------------------------------- 1 | 1.2.0 (2014-10-14) 2 | ------------------ 3 | 4 | ### New features 5 | 6 | * Added the autoprefixer filter 7 | * Added --no-header option for Coffeescript 8 | * Implemented the extraction of dependencies for the compass filter 9 | * Allow custom functions to be registered on the Lessphp filter 10 | * Added MinifyCssCompressor filter based on `mrclay/minify` 11 | * Added `setVariables` in the ScssPhpFilter 12 | * Improved the support of the compress options for UglifyJS2 13 | * Added CssCacheBustingFilter to apply cache busting on URLs in the CSS 14 | * Added support for `--relative-assets` option for the compass filter 15 | 16 | ### Bug fixes 17 | 18 | * Allow functions.php to be included many times 19 | * Updated the ScssPhpFilter for upstream class renaming 20 | 21 | 1.2.0-alpha1 (2014-07-08) 22 | ------------------------- 23 | 24 | ### BC breaks 25 | 26 | * Added `AssetFactory` instance as second argument for `WorkerInterface::process()` 27 | * Removed `LazyAssetManager` from `CacheBustingWorker` constructor 28 | * A new `getSourceDirectory()` method was added on the AssetInterface 29 | * Removed limit and count arguments from CssUtils functions 30 | * Removed the deprecated `PathUtils` class 31 | 32 | ### New features 33 | 34 | * added `CssUtils::filterCommentless()` 35 | * Added `DependencyExtractorInterface` for filters to report other files they import 36 | * Added the support of nib in the stylus filter 37 | * Added `registerFunction` and `setFormatter` to the scssphp filter 38 | * Added the support of flag file for the ClosureCompilerJarFilter 39 | * Added the JsSqueeze filter 40 | * Added support of the define option for uglifyjs (1 & 2) filters 41 | * Added logging for Twig errors in the extractor 42 | 43 | ### Bug fixes 44 | 45 | * Fixed the detection of protocol-relative CSS imports 46 | * Updated AssetCollection to not add several time the same variable in path 47 | * Fixed the merging of the environment variables to keep the configuration of the NODE_PATH working 48 | * Fixed the support of ``{% embed %}`` in the Twig extractor 49 | * Fixed the support of asset variables in GlobAsset 50 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kriswallsmith/assetic", 3 | "description": "Asset Management for PHP", 4 | "keywords": [ "assets", "compression", "minification" ], 5 | "homepage": "https://github.com/kriswallsmith/assetic", 6 | "type": "library", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Kris Wallsmith", 11 | "email": "kris.wallsmith@gmail.com", 12 | "homepage": "http://kriswallsmith.net/" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.3.1", 17 | "symfony/process": "~2.1|~3.0" 18 | }, 19 | "conflict": { 20 | "twig/twig": "<1.27" 21 | }, 22 | "require-dev": { 23 | "leafo/lessphp": "^0.3.7", 24 | "leafo/scssphp": "~0.1", 25 | "meenie/javascript-packer": "^1.1", 26 | "mrclay/minify": "<2.3", 27 | "natxet/cssmin": "3.0.4", 28 | "patchwork/jsqueeze": "~1.0|~2.0", 29 | "phpunit/phpunit": "~4.8 || ^5.6", 30 | "psr/log": "~1.0", 31 | "ptachoire/cssembed": "~1.0", 32 | "symfony/phpunit-bridge": "~2.7|~3.0", 33 | "twig/twig": "~1.23|~2.0", 34 | "yfix/packager": "dev-master" 35 | }, 36 | "suggest": { 37 | "twig/twig": "Assetic provides the integration with the Twig templating engine", 38 | "leafo/lessphp": "Assetic provides the integration with the lessphp LESS compiler", 39 | "leafo/scssphp": "Assetic provides the integration with the scssphp SCSS compiler", 40 | "ptachoire/cssembed": "Assetic provides the integration with phpcssembed to embed data uris", 41 | "leafo/scssphp-compass": "Assetic provides the integration with the SCSS compass plugin", 42 | "patchwork/jsqueeze": "Assetic provides the integration with the JSqueeze JavaScript compressor" 43 | }, 44 | "autoload": { 45 | "psr-0": { "Assetic": "src/" }, 46 | "files": [ "src/functions.php" ] 47 | }, 48 | "config": { 49 | "bin-dir": "bin" 50 | }, 51 | "extra": { 52 | "branch-alias": { 53 | "dev-master": "1.4-dev" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Assetic/Factory/Worker/CacheBustingWorker.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class CacheBustingWorker implements WorkerInterface 24 | { 25 | private $separator; 26 | 27 | public function __construct($separator = '-') 28 | { 29 | $this->separator = $separator; 30 | } 31 | 32 | public function process(AssetInterface $asset, AssetFactory $factory) 33 | { 34 | if (!$path = $asset->getTargetPath()) { 35 | // no path to work with 36 | return; 37 | } 38 | 39 | if (!$search = pathinfo($path, PATHINFO_EXTENSION)) { 40 | // nothing to replace 41 | return; 42 | } 43 | 44 | $replace = $this->separator.$this->getHash($asset, $factory).'.'.$search; 45 | if (preg_match('/'.preg_quote($replace, '/').'$/', $path)) { 46 | // already replaced 47 | return; 48 | } 49 | 50 | $asset->setTargetPath( 51 | preg_replace('/\.'.preg_quote($search, '/').'$/', $replace, $path) 52 | ); 53 | } 54 | 55 | protected function getHash(AssetInterface $asset, AssetFactory $factory) 56 | { 57 | $hash = hash_init('sha1'); 58 | 59 | hash_update($hash, $factory->getLastModified($asset)); 60 | 61 | if ($asset instanceof AssetCollectionInterface) { 62 | foreach ($asset as $i => $leaf) { 63 | $sourcePath = $leaf->getSourcePath(); 64 | hash_update($hash, $sourcePath ?: $i); 65 | } 66 | } 67 | 68 | return substr(hash_final($hash), 0, 7); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Assetic/Filter/OptiPngFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class OptiPngFilter extends BaseProcessFilter 25 | { 26 | private $optipngBin; 27 | private $level; 28 | 29 | /** 30 | * Constructor. 31 | * 32 | * @param string $optipngBin Path to the optipng binary 33 | */ 34 | public function __construct($optipngBin = '/usr/bin/optipng') 35 | { 36 | $this->optipngBin = $optipngBin; 37 | } 38 | 39 | public function setLevel($level) 40 | { 41 | $this->level = $level; 42 | } 43 | 44 | public function filterLoad(AssetInterface $asset) 45 | { 46 | } 47 | 48 | public function filterDump(AssetInterface $asset) 49 | { 50 | $pb = $this->createProcessBuilder(array($this->optipngBin)); 51 | 52 | if (null !== $this->level) { 53 | $pb->add('-o')->add($this->level); 54 | } 55 | 56 | $pb->add('-out')->add($output = FilesystemUtils::createTemporaryFile('optipng_out')); 57 | unlink($output); 58 | 59 | $pb->add($input = FilesystemUtils::createTemporaryFile('optinpg_in')); 60 | file_put_contents($input, $asset->getContent()); 61 | 62 | $proc = $pb->getProcess(); 63 | $code = $proc->run(); 64 | 65 | if (0 !== $code) { 66 | unlink($input); 67 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 68 | } 69 | 70 | $asset->setContent(file_get_contents($output)); 71 | 72 | unlink($input); 73 | unlink($output); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Assetic/Extension/Twig/ValueContainer.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class ValueContainer implements \ArrayAccess, \IteratorAggregate, \Countable 22 | { 23 | private $values; 24 | private $valueSupplier; 25 | 26 | public function __construct(ValueSupplierInterface $valueSupplier) 27 | { 28 | $this->valueSupplier = $valueSupplier; 29 | } 30 | 31 | public function offsetExists($offset) 32 | { 33 | $this->initialize(); 34 | 35 | return array_key_exists($offset, $this->values); 36 | } 37 | 38 | public function offsetGet($offset) 39 | { 40 | $this->initialize(); 41 | 42 | if (!array_key_exists($offset, $this->values)) { 43 | throw new \OutOfRangeException(sprintf('The variable "%s" does not exist.', $offset)); 44 | } 45 | 46 | return $this->values[$offset]; 47 | } 48 | 49 | public function offsetSet($offset, $value) 50 | { 51 | throw new \BadMethodCallException('The ValueContainer is read-only.'); 52 | } 53 | 54 | public function offsetUnset($offset) 55 | { 56 | throw new \BadMethodCallException('The ValueContainer is read-only.'); 57 | } 58 | 59 | public function getIterator() 60 | { 61 | $this->initialize(); 62 | 63 | return new \ArrayIterator($this->values); 64 | } 65 | 66 | public function count() 67 | { 68 | $this->initialize(); 69 | 70 | return count($this->values); 71 | } 72 | 73 | private function initialize() 74 | { 75 | if (null === $this->values) { 76 | $this->values = $this->valueSupplier->getValues(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Assetic/Filter/FilterCollection.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class FilterCollection implements FilterInterface, \IteratorAggregate, \Countable 22 | { 23 | private $filters = array(); 24 | 25 | public function __construct($filters = array()) 26 | { 27 | foreach ($filters as $filter) { 28 | $this->ensure($filter); 29 | } 30 | } 31 | 32 | /** 33 | * Checks that the current collection contains the supplied filter. 34 | * 35 | * If the supplied filter is another filter collection, each of its 36 | * filters will be checked. 37 | */ 38 | public function ensure(FilterInterface $filter) 39 | { 40 | if ($filter instanceof \Traversable) { 41 | foreach ($filter as $f) { 42 | $this->ensure($f); 43 | } 44 | } elseif (!in_array($filter, $this->filters, true)) { 45 | $this->filters[] = $filter; 46 | } 47 | } 48 | 49 | public function all() 50 | { 51 | return $this->filters; 52 | } 53 | 54 | public function clear() 55 | { 56 | $this->filters = array(); 57 | } 58 | 59 | public function filterLoad(AssetInterface $asset) 60 | { 61 | foreach ($this->filters as $filter) { 62 | $filter->filterLoad($asset); 63 | } 64 | } 65 | 66 | public function filterDump(AssetInterface $asset) 67 | { 68 | foreach ($this->filters as $filter) { 69 | $filter->filterDump($asset); 70 | } 71 | } 72 | 73 | public function getIterator() 74 | { 75 | return new \ArrayIterator($this->filters); 76 | } 77 | 78 | public function count() 79 | { 80 | return count($this->filters); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Assetic/Filter/JpegoptimFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class JpegoptimFilter extends BaseProcessFilter 25 | { 26 | private $jpegoptimBin; 27 | private $stripAll; 28 | private $max; 29 | 30 | /** 31 | * Constructor. 32 | * 33 | * @param string $jpegoptimBin Path to the jpegoptim binary 34 | */ 35 | public function __construct($jpegoptimBin = '/usr/bin/jpegoptim') 36 | { 37 | $this->jpegoptimBin = $jpegoptimBin; 38 | } 39 | 40 | public function setStripAll($stripAll) 41 | { 42 | $this->stripAll = $stripAll; 43 | } 44 | 45 | public function setMax($max) 46 | { 47 | $this->max = $max; 48 | } 49 | 50 | public function filterLoad(AssetInterface $asset) 51 | { 52 | } 53 | 54 | public function filterDump(AssetInterface $asset) 55 | { 56 | $pb = $this->createProcessBuilder(array($this->jpegoptimBin)); 57 | 58 | if ($this->stripAll) { 59 | $pb->add('--strip-all'); 60 | } 61 | 62 | if ($this->max) { 63 | $pb->add('--max='.$this->max); 64 | } 65 | 66 | $pb->add($input = FilesystemUtils::createTemporaryFile('jpegoptim')); 67 | file_put_contents($input, $asset->getContent()); 68 | 69 | $proc = $pb->getProcess(); 70 | $proc->run(); 71 | 72 | if (false !== strpos($proc->getOutput(), 'ERROR')) { 73 | unlink($input); 74 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 75 | } 76 | 77 | $asset->setContent(file_get_contents($input)); 78 | 79 | unlink($input); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Assetic/Filter/CoffeeScriptFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class CoffeeScriptFilter extends BaseNodeFilter 25 | { 26 | private $coffeeBin; 27 | private $nodeBin; 28 | 29 | // coffee options 30 | private $bare; 31 | private $noHeader; 32 | 33 | public function __construct($coffeeBin = '/usr/bin/coffee', $nodeBin = null) 34 | { 35 | $this->coffeeBin = $coffeeBin; 36 | $this->nodeBin = $nodeBin; 37 | } 38 | 39 | public function setBare($bare) 40 | { 41 | $this->bare = $bare; 42 | } 43 | 44 | public function setNoHeader($noHeader) 45 | { 46 | $this->noHeader = $noHeader; 47 | } 48 | 49 | public function filterLoad(AssetInterface $asset) 50 | { 51 | $input = FilesystemUtils::createTemporaryFile('coffee'); 52 | file_put_contents($input, $asset->getContent()); 53 | 54 | $pb = $this->createProcessBuilder($this->nodeBin 55 | ? array($this->nodeBin, $this->coffeeBin) 56 | : array($this->coffeeBin)); 57 | 58 | $pb->add('-cp'); 59 | 60 | if ($this->bare) { 61 | $pb->add('--bare'); 62 | } 63 | 64 | if ($this->noHeader) { 65 | $pb->add('--no-header'); 66 | } 67 | 68 | $pb->add($input); 69 | $proc = $pb->getProcess(); 70 | $code = $proc->run(); 71 | unlink($input); 72 | 73 | if (0 !== $code) { 74 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 75 | } 76 | 77 | $asset->setContent($proc->getOutput()); 78 | } 79 | 80 | public function filterDump(AssetInterface $asset) 81 | { 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Assetic/Factory/Loader/CachedFormulaLoader.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | class CachedFormulaLoader implements FormulaLoaderInterface 26 | { 27 | private $loader; 28 | private $configCache; 29 | private $debug; 30 | 31 | /** 32 | * Constructor. 33 | * 34 | * When the loader is in debug mode it will ensure the cached formulae 35 | * are fresh before returning them. 36 | * 37 | * @param FormulaLoaderInterface $loader A formula loader 38 | * @param ConfigCache $configCache A config cache 39 | * @param Boolean $debug The debug mode 40 | */ 41 | public function __construct(FormulaLoaderInterface $loader, ConfigCache $configCache, $debug = false) 42 | { 43 | $this->loader = $loader; 44 | $this->configCache = $configCache; 45 | $this->debug = $debug; 46 | } 47 | 48 | public function load(ResourceInterface $resources) 49 | { 50 | if (!$resources instanceof IteratorResourceInterface) { 51 | $resources = array($resources); 52 | } 53 | 54 | $formulae = array(); 55 | 56 | foreach ($resources as $resource) { 57 | $id = (string) $resource; 58 | if (!$this->configCache->has($id) || ($this->debug && !$resource->isFresh($this->configCache->getTimestamp($id)))) { 59 | $formulae += $this->loader->load($resource); 60 | $this->configCache->set($id, $formulae); 61 | } else { 62 | $formulae += $this->configCache->get($id); 63 | } 64 | } 65 | 66 | return $formulae; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Assetic/Filter/ReactJsxFilter.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class ReactJsxFilter extends BaseNodeFilter 16 | { 17 | private $jsxBin; 18 | private $nodeBin; 19 | 20 | public function __construct($jsxBin = '/usr/bin/jsx', $nodeBin = null) 21 | { 22 | $this->jsxBin = $jsxBin; 23 | $this->nodeBin = $nodeBin; 24 | } 25 | 26 | public function filterLoad(AssetInterface $asset) 27 | { 28 | $builder = $this->createProcessBuilder($this->nodeBin 29 | ? array($this->nodeBin, $this->jsxBin) 30 | : array($this->jsxBin)); 31 | 32 | $inputDir = FilesystemUtils::createThrowAwayDirectory('jsx_in'); 33 | $inputFile = $inputDir.DIRECTORY_SEPARATOR.'asset.js'; 34 | $outputDir = FilesystemUtils::createThrowAwayDirectory('jsx_out'); 35 | $outputFile = $outputDir.DIRECTORY_SEPARATOR.'asset.js'; 36 | 37 | // create the asset file 38 | file_put_contents($inputFile, $asset->getContent()); 39 | 40 | $builder 41 | ->add($inputDir) 42 | ->add($outputDir) 43 | ->add('--no-cache-dir') 44 | ; 45 | 46 | $proc = $builder->getProcess(); 47 | $code = $proc->run(); 48 | 49 | // remove the input directory and asset file 50 | unlink($inputFile); 51 | rmdir($inputDir); 52 | 53 | if (0 !== $code) { 54 | if (file_exists($outputFile)) { 55 | unlink($outputFile); 56 | } 57 | 58 | if (file_exists($outputDir)) { 59 | rmdir($outputDir); 60 | } 61 | 62 | throw FilterException::fromProcess($proc); 63 | } 64 | 65 | $asset->setContent(file_get_contents($outputFile)); 66 | 67 | // remove the output directory and processed asset file 68 | unlink($outputFile); 69 | rmdir($outputDir); 70 | } 71 | 72 | public function filterDump(AssetInterface $asset) 73 | { 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Assetic/Filter/JSqueezeFilter.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class JSqueezeFilter implements FilterInterface 23 | { 24 | private $singleLine = true; 25 | private $keepImportantComments = true; 26 | private $className; 27 | private $specialVarRx = false; 28 | private $defaultRx; 29 | 30 | public function __construct() 31 | { 32 | // JSqueeze is namespaced since 2.x, this works with both 1.x and 2.x 33 | if (class_exists('\\Patchwork\\JSqueeze')) { 34 | $this->className = '\\Patchwork\\JSqueeze'; 35 | $this->defaultRx = \Patchwork\JSqueeze::SPECIAL_VAR_PACKER; 36 | } else { 37 | $this->className = '\\JSqueeze'; 38 | $this->defaultRx = \JSqueeze::SPECIAL_VAR_RX; 39 | } 40 | } 41 | 42 | public function setSingleLine($bool) 43 | { 44 | $this->singleLine = (bool) $bool; 45 | } 46 | 47 | // call setSpecialVarRx(true) to enable global var/method/property 48 | // renaming with the default regex (for 1.x or 2.x) 49 | public function setSpecialVarRx($specialVarRx) 50 | { 51 | if (true === $specialVarRx) { 52 | $this->specialVarRx = $this->defaultRx; 53 | } else { 54 | $this->specialVarRx = $specialVarRx; 55 | } 56 | } 57 | 58 | public function keepImportantComments($bool) 59 | { 60 | $this->keepImportantComments = (bool) $bool; 61 | } 62 | 63 | public function filterLoad(AssetInterface $asset) 64 | { 65 | } 66 | 67 | public function filterDump(AssetInterface $asset) 68 | { 69 | $parser = new $this->className(); 70 | $asset->setContent($parser->squeeze( 71 | $asset->getContent(), 72 | $this->singleLine, 73 | $this->keepImportantComments, 74 | $this->specialVarRx 75 | )); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Assetic/AssetManager.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class AssetManager 22 | { 23 | private $assets = array(); 24 | 25 | /** 26 | * Gets an asset by name. 27 | * 28 | * @param string $name The asset name 29 | * 30 | * @return AssetInterface The asset 31 | * 32 | * @throws \InvalidArgumentException If there is no asset by that name 33 | */ 34 | public function get($name) 35 | { 36 | if (!isset($this->assets[$name])) { 37 | throw new \InvalidArgumentException(sprintf('There is no "%s" asset.', $name)); 38 | } 39 | 40 | return $this->assets[$name]; 41 | } 42 | 43 | /** 44 | * Checks if the current asset manager has a certain asset. 45 | * 46 | * @param string $name an asset name 47 | * 48 | * @return Boolean True if the asset has been set, false if not 49 | */ 50 | public function has($name) 51 | { 52 | return isset($this->assets[$name]); 53 | } 54 | 55 | /** 56 | * Registers an asset to the current asset manager. 57 | * 58 | * @param string $name The asset name 59 | * @param AssetInterface $asset The asset 60 | * 61 | * @throws \InvalidArgumentException If the asset name is invalid 62 | */ 63 | public function set($name, AssetInterface $asset) 64 | { 65 | if (!ctype_alnum(str_replace('_', '', $name))) { 66 | throw new \InvalidArgumentException(sprintf('The name "%s" is invalid.', $name)); 67 | } 68 | 69 | $this->assets[$name] = $asset; 70 | } 71 | 72 | /** 73 | * Returns an array of asset names. 74 | * 75 | * @return array An array of asset names 76 | */ 77 | public function getNames() 78 | { 79 | return array_keys($this->assets); 80 | } 81 | 82 | /** 83 | * Clears all assets. 84 | */ 85 | public function clear() 86 | { 87 | $this->assets = array(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Assetic/Filter/AutoprefixerFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class AutoprefixerFilter extends BaseNodeFilter 25 | { 26 | /** 27 | * @var string 28 | */ 29 | private $autoprefixerBin; 30 | 31 | /** 32 | * @var array 33 | */ 34 | private $browsers = array(); 35 | 36 | public function __construct($autoprefixerBin) 37 | { 38 | $this->autoprefixerBin = $autoprefixerBin; 39 | } 40 | 41 | /** 42 | * @param array $browsers 43 | */ 44 | public function setBrowsers(array $browsers) 45 | { 46 | $this->browsers = $browsers; 47 | } 48 | 49 | /** 50 | * @param string $browser 51 | */ 52 | public function addBrowser($browser) 53 | { 54 | $this->browsers[] = $browser; 55 | } 56 | 57 | public function filterLoad(AssetInterface $asset) 58 | { 59 | $input = $asset->getContent(); 60 | $pb = $this->createProcessBuilder(array($this->autoprefixerBin)); 61 | 62 | $pb->setInput($input); 63 | if ($this->browsers) { 64 | $pb->add('-b')->add(implode(',', $this->browsers)); 65 | } 66 | 67 | $output = FilesystemUtils::createTemporaryFile('autoprefixer'); 68 | $pb->add('-o')->add($output); 69 | 70 | $proc = $pb->getProcess(); 71 | if (0 !== $proc->run()) { 72 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 73 | } 74 | 75 | $asset->setContent(file_get_contents($output)); 76 | unlink($output); 77 | } 78 | 79 | /** 80 | * Filters an asset just before it's dumped. 81 | * 82 | * @param AssetInterface $asset An asset 83 | */ 84 | public function filterDump(AssetInterface $asset) 85 | { 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Assetic/Filter/RooleFilter.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | class RooleFilter extends BaseNodeFilter implements DependencyExtractorInterface 26 | { 27 | private $rooleBin; 28 | private $nodeBin; 29 | 30 | /** 31 | * Constructor 32 | * 33 | * @param string $rooleBin The path to the roole binary 34 | * @param string $nodeBin The path to the node binary 35 | */ 36 | public function __construct($rooleBin = '/usr/bin/roole', $nodeBin = null) 37 | { 38 | $this->rooleBin = $rooleBin; 39 | $this->nodeBin = $nodeBin; 40 | } 41 | 42 | public function filterLoad(AssetInterface $asset) 43 | { 44 | $input = FilesystemUtils::createTemporaryFile('roole'); 45 | $output = $input.'.css'; 46 | 47 | file_put_contents($input, $asset->getContent()); 48 | 49 | $pb = $this->createProcessBuilder($this->nodeBin 50 | ? array($this->nodeBin, $this->rooleBin) 51 | : array($this->rooleBin)); 52 | 53 | $pb->add($input); 54 | 55 | $proc = $pb->getProcess(); 56 | $code = $proc->run(); 57 | unlink($input); 58 | 59 | if (0 !== $code) { 60 | if (file_exists($output)) { 61 | unlink($output); 62 | } 63 | 64 | throw FilterException::fromProcess($proc); 65 | } 66 | 67 | if (!file_exists($output)) { 68 | throw new \RuntimeException('Error creating output file.'); 69 | } 70 | 71 | $asset->setContent(file_get_contents($output)); 72 | unlink($output); 73 | } 74 | 75 | public function filterDump(AssetInterface $asset) 76 | { 77 | } 78 | 79 | public function getChildren(AssetFactory $factory, $content, $loadPath = null) 80 | { 81 | // todo 82 | return array(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Assetic/Util/VarUtils.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | abstract class VarUtils 20 | { 21 | /** 22 | * Resolves variable placeholders. 23 | * 24 | * @param string $template A template string 25 | * @param array $vars Variable names 26 | * @param array $values Variable values 27 | * 28 | * @return string The resolved string 29 | * 30 | * @throws \InvalidArgumentException If there is a variable with no value 31 | */ 32 | public static function resolve($template, array $vars, array $values) 33 | { 34 | $map = array(); 35 | foreach ($vars as $var) { 36 | if (false === strpos($template, '{'.$var.'}')) { 37 | continue; 38 | } 39 | 40 | if (!isset($values[$var])) { 41 | throw new \InvalidArgumentException(sprintf('The template "%s" contains the variable "%s", but was not given any value for it.', $template, $var)); 42 | } 43 | 44 | $map['{'.$var.'}'] = $values[$var]; 45 | } 46 | 47 | return strtr($template, $map); 48 | } 49 | 50 | public static function getCombinations(array $vars, array $values) 51 | { 52 | if (!$vars) { 53 | return array(array()); 54 | } 55 | 56 | $combinations = array(); 57 | $nbValues = array(); 58 | foreach ($values as $var => $vals) { 59 | if (!in_array($var, $vars, true)) { 60 | continue; 61 | } 62 | 63 | $nbValues[$var] = count($vals); 64 | } 65 | 66 | for ($i = array_product($nbValues), $c = $i * 2; $i < $c; $i++) { 67 | $k = $i; 68 | $combination = array(); 69 | 70 | foreach ($vars as $var) { 71 | $combination[$var] = $values[$var][$k % $nbValues[$var]]; 72 | $k = intval($k / $nbValues[$var]); 73 | } 74 | 75 | $combinations[] = $combination; 76 | } 77 | 78 | return $combinations; 79 | } 80 | 81 | final private function __construct() 82 | { 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /CHANGELOG-1.1.md: -------------------------------------------------------------------------------- 1 | 1.1.2 (July 18, 2013) 2 | ------------------- 3 | 4 | * Fixed deep mtime on asset collections 5 | * `CallablesFilter` now implements `DependencyExtractorInterface` 6 | * Fixed detection of "partial" children in subfolders in `SassFilter` 7 | * Restored `PathUtils` for BC 8 | 9 | 1.1.1 (June 1, 2013) 10 | -------------------- 11 | 12 | * Fixed cloning of asset collections 13 | * Fixed environment var inheritance 14 | * Replaced `AssetWriter::getCombinations()` for BC, even though we don't use it 15 | * Added support for `@import-once` to Less filters 16 | 17 | 1.1.0 (May 15, 2013) 18 | -------------------- 19 | 20 | * Added LazyAssetManager::getLastModified() for determining "deep" mtime 21 | * Added DartFilter 22 | * Added EmberPrecompile 23 | * Added GssFilter 24 | * Added PhpCssEmbedFilter 25 | * Added RooleFilter 26 | * Added TypeScriptFilter 27 | * Added the possibility to configure additional load paths for less and lessphp 28 | * Added the UglifyCssFilter 29 | * Fixed the handling of directories in the GlobAsset. #256 30 | * Added Handlebars support 31 | * Added Scssphp-compass support 32 | * Added the CacheBustingWorker 33 | * Added the UglifyJs2Filter 34 | 35 | 1.1.0-alpha1 (August 28, 2012) 36 | ------------------------------ 37 | 38 | * Added pure php css embed filter 39 | * Added Scssphp support 40 | * Added support for Google Closure language option 41 | * Added a way to set a specific ruby path for CompassFilter and SassFilter 42 | * Ensure uniqueness of temporary files created by the compressor filter. Fixed #61 43 | * Added Compass option for generated_images_path (for generated Images/Sprites) 44 | * Added PackerFilter 45 | * Add the way to contact closure compiler API using curl, if available and allow_url_fopen is off 46 | * Added filters for JSMin and JSMinPlus 47 | * Added the UglifyJsFilter 48 | * Improved the error message in getModifiedTime when a file asset uses an invalid file 49 | * added support for asset variables: 50 | 51 | Asset variables allow you to pre-compile your assets for a finite set of known 52 | variable values, and then to simply deliver the correct asset version at runtime. 53 | For example, this is helpful for assets with language, or browser-specific code. 54 | * Removed the copy-paste of the Symfony2 Process component and use the original one 55 | * Added ability to pass variables into lessphp filter 56 | * Added google closure stylesheets jar filter 57 | * Added the support of `--bare` for the CoffeeScriptFilter 58 | -------------------------------------------------------------------------------- /src/Assetic/Extension/Twig/AsseticExtension.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 26 | $this->functions = array(); 27 | $this->valueSupplier = $valueSupplier; 28 | 29 | foreach ($functions as $function => $options) { 30 | if (is_integer($function) && is_string($options)) { 31 | $this->functions[$options] = array('filter' => $options); 32 | } else { 33 | $this->functions[$function] = $options + array('filter' => $function); 34 | } 35 | } 36 | } 37 | 38 | public function getTokenParsers() 39 | { 40 | return array( 41 | new AsseticTokenParser($this->factory, 'javascripts', 'js/*.js'), 42 | new AsseticTokenParser($this->factory, 'stylesheets', 'css/*.css'), 43 | new AsseticTokenParser($this->factory, 'image', 'images/*', true), 44 | ); 45 | } 46 | 47 | public function getFunctions() 48 | { 49 | $functions = array(); 50 | foreach ($this->functions as $function => $filter) { 51 | $functions[] = new AsseticFilterFunction($function); 52 | } 53 | 54 | return $functions; 55 | } 56 | 57 | public function getGlobals() 58 | { 59 | return array( 60 | 'assetic' => array( 61 | 'debug' => $this->factory->isDebug(), 62 | 'vars' => null !== $this->valueSupplier ? new ValueContainer($this->valueSupplier) : array(), 63 | ), 64 | ); 65 | } 66 | 67 | public function getFilterInvoker($function) 68 | { 69 | return new AsseticFilterInvoker($this->factory, $this->functions[$function]); 70 | } 71 | 72 | public function getName() 73 | { 74 | return 'assetic'; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Assetic/Filter/TypeScriptFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class TypeScriptFilter extends BaseNodeFilter 25 | { 26 | private $tscBin; 27 | private $nodeBin; 28 | 29 | public function __construct($tscBin = '/usr/bin/tsc', $nodeBin = null) 30 | { 31 | $this->tscBin = $tscBin; 32 | $this->nodeBin = $nodeBin; 33 | } 34 | 35 | public function filterLoad(AssetInterface $asset) 36 | { 37 | $pb = $this->createProcessBuilder($this->nodeBin 38 | ? array($this->nodeBin, $this->tscBin) 39 | : array($this->tscBin)); 40 | 41 | if ($sourcePath = $asset->getSourcePath()) { 42 | $templateName = basename($sourcePath); 43 | } else { 44 | $templateName = 'asset'; 45 | } 46 | 47 | $inputDirPath = FilesystemUtils::createThrowAwayDirectory('typescript_in'); 48 | $inputPath = $inputDirPath.DIRECTORY_SEPARATOR.$templateName.'.ts'; 49 | $outputPath = FilesystemUtils::createTemporaryFile('typescript_out'); 50 | 51 | file_put_contents($inputPath, $asset->getContent()); 52 | 53 | $pb->add($inputPath)->add('--out')->add($outputPath); 54 | 55 | $proc = $pb->getProcess(); 56 | $code = $proc->run(); 57 | unlink($inputPath); 58 | rmdir($inputDirPath); 59 | 60 | if (0 !== $code) { 61 | if (file_exists($outputPath)) { 62 | unlink($outputPath); 63 | } 64 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 65 | } 66 | 67 | if (!file_exists($outputPath)) { 68 | throw new \RuntimeException('Error creating output file.'); 69 | } 70 | 71 | $compiledJs = file_get_contents($outputPath); 72 | unlink($outputPath); 73 | 74 | $asset->setContent($compiledJs); 75 | } 76 | 77 | public function filterDump(AssetInterface $asset) 78 | { 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Assetic/Util/FilesystemUtils.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class FilesystemUtils 20 | { 21 | /** 22 | * Recursively removes a directory from the filesystem. 23 | */ 24 | public static function removeDirectory($directory) 25 | { 26 | $inner = new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS); 27 | $outer = new \RecursiveIteratorIterator($inner, \RecursiveIteratorIterator::SELF_FIRST); 28 | 29 | // remove the files first 30 | foreach ($outer as $file) { 31 | if ($file->isFile()) { 32 | unlink($file); 33 | } 34 | } 35 | 36 | // remove the sub-directories next 37 | $files = iterator_to_array($outer); 38 | foreach (array_reverse($files) as $file) { 39 | /** @var \SplFileInfo $file */ 40 | if ($file->isDir()) { 41 | rmdir($file); 42 | } 43 | } 44 | 45 | // finally the directory itself 46 | rmdir($directory); 47 | } 48 | 49 | /** 50 | * Creates a throw-away directory. 51 | * 52 | * This is not considered a "temporary" directory because it will not be 53 | * automatically deleted at the end of the request or process. It must be 54 | * deleted manually. 55 | * 56 | * @param string $prefix A prefix for the directory name 57 | * 58 | * @return string The directory path 59 | */ 60 | public static function createThrowAwayDirectory($prefix) 61 | { 62 | $directory = self::getTemporaryDirectory().DIRECTORY_SEPARATOR.uniqid('assetic_'.$prefix); 63 | mkdir($directory); 64 | 65 | return $directory; 66 | } 67 | 68 | /** 69 | * Creates a temporary file. 70 | * 71 | * @param string $prefix A prefix for the file name 72 | * 73 | * @return string The file path 74 | */ 75 | public static function createTemporaryFile($prefix) 76 | { 77 | return tempnam(self::getTemporaryDirectory(), 'assetic_'.$prefix); 78 | } 79 | 80 | public static function getTemporaryDirectory() 81 | { 82 | return realpath(sys_get_temp_dir()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Assetic/Asset/FileAsset.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class FileAsset extends BaseAsset 23 | { 24 | private $source; 25 | 26 | /** 27 | * Constructor. 28 | * 29 | * @param string $source An absolute path 30 | * @param array $filters An array of filters 31 | * @param string $sourceRoot The source asset root directory 32 | * @param string $sourcePath The source asset path 33 | * @param array $vars 34 | * 35 | * @throws \InvalidArgumentException If the supplied root doesn't match the source when guessing the path 36 | */ 37 | public function __construct($source, $filters = array(), $sourceRoot = null, $sourcePath = null, array $vars = array()) 38 | { 39 | if (null === $sourceRoot) { 40 | $sourceRoot = dirname($source); 41 | if (null === $sourcePath) { 42 | $sourcePath = basename($source); 43 | } 44 | } elseif (null === $sourcePath) { 45 | if (0 !== strpos($source, $sourceRoot)) { 46 | throw new \InvalidArgumentException(sprintf('The source "%s" is not in the root directory "%s"', $source, $sourceRoot)); 47 | } 48 | 49 | $sourcePath = substr($source, strlen($sourceRoot) + 1); 50 | } 51 | 52 | $this->source = $source; 53 | 54 | parent::__construct($filters, $sourceRoot, $sourcePath, $vars); 55 | } 56 | 57 | public function load(FilterInterface $additionalFilter = null) 58 | { 59 | $source = VarUtils::resolve($this->source, $this->getVars(), $this->getValues()); 60 | 61 | if (!is_file($source)) { 62 | throw new \RuntimeException(sprintf('The source file "%s" does not exist.', $source)); 63 | } 64 | 65 | $this->doLoad(file_get_contents($source), $additionalFilter); 66 | } 67 | 68 | public function getLastModified() 69 | { 70 | $source = VarUtils::resolve($this->source, $this->getVars(), $this->getValues()); 71 | 72 | if (!is_file($source)) { 73 | throw new \RuntimeException(sprintf('The source file "%s" does not exist.', $source)); 74 | } 75 | 76 | return filemtime($source); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Assetic/Asset/HttpAsset.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class HttpAsset extends BaseAsset 23 | { 24 | private $sourceUrl; 25 | private $ignoreErrors; 26 | 27 | /** 28 | * Constructor. 29 | * 30 | * @param string $sourceUrl The source URL 31 | * @param array $filters An array of filters 32 | * @param Boolean $ignoreErrors 33 | * @param array $vars 34 | * 35 | * @throws \InvalidArgumentException If the first argument is not an URL 36 | */ 37 | public function __construct($sourceUrl, $filters = array(), $ignoreErrors = false, array $vars = array()) 38 | { 39 | if (0 === strpos($sourceUrl, '//')) { 40 | $sourceUrl = 'http:'.$sourceUrl; 41 | } elseif (false === strpos($sourceUrl, '://')) { 42 | throw new \InvalidArgumentException(sprintf('"%s" is not a valid URL.', $sourceUrl)); 43 | } 44 | 45 | $this->sourceUrl = $sourceUrl; 46 | $this->ignoreErrors = $ignoreErrors; 47 | 48 | list($scheme, $url) = explode('://', $sourceUrl, 2); 49 | list($host, $path) = explode('/', $url, 2); 50 | 51 | parent::__construct($filters, $scheme.'://'.$host, $path, $vars); 52 | } 53 | 54 | public function load(FilterInterface $additionalFilter = null) 55 | { 56 | $content = @file_get_contents( 57 | VarUtils::resolve($this->sourceUrl, $this->getVars(), $this->getValues()) 58 | ); 59 | 60 | if (false === $content && !$this->ignoreErrors) { 61 | throw new \RuntimeException(sprintf('Unable to load asset from URL "%s"', $this->sourceUrl)); 62 | } 63 | 64 | $this->doLoad($content, $additionalFilter); 65 | } 66 | 67 | public function getLastModified() 68 | { 69 | if (false !== @file_get_contents($this->sourceUrl, false, stream_context_create(array('http' => array('method' => 'HEAD'))))) { 70 | foreach ($http_response_header as $header) { 71 | if (0 === stripos($header, 'Last-Modified: ')) { 72 | list(, $mtime) = explode(':', $header, 2); 73 | 74 | return strtotime(trim($mtime)); 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Assetic/Asset/Iterator/AssetCollectionFilterIterator.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class AssetCollectionFilterIterator extends \RecursiveFilterIterator 23 | { 24 | private $visited; 25 | private $sources; 26 | 27 | /** 28 | * Constructor. 29 | * 30 | * @param AssetCollectionIterator $iterator The inner iterator 31 | * @param array $visited An array of visited asset objects 32 | * @param array $sources An array of visited source strings 33 | */ 34 | public function __construct(AssetCollectionIterator $iterator, array $visited = array(), array $sources = array()) 35 | { 36 | parent::__construct($iterator); 37 | 38 | $this->visited = $visited; 39 | $this->sources = $sources; 40 | } 41 | 42 | /** 43 | * Determines whether the current asset is a duplicate. 44 | * 45 | * De-duplication is performed based on either strict equality or by 46 | * matching sources. 47 | * 48 | * @return Boolean Returns true if we have not seen this asset yet 49 | */ 50 | public function accept() 51 | { 52 | $asset = $this->getInnerIterator()->current(true); 53 | $duplicate = false; 54 | 55 | // check strict equality 56 | if (in_array($asset, $this->visited, true)) { 57 | $duplicate = true; 58 | } else { 59 | $this->visited[] = $asset; 60 | } 61 | 62 | // check source 63 | $sourceRoot = $asset->getSourceRoot(); 64 | $sourcePath = $asset->getSourcePath(); 65 | if ($sourceRoot && $sourcePath) { 66 | $source = $sourceRoot.'/'.$sourcePath; 67 | if (in_array($source, $this->sources)) { 68 | $duplicate = true; 69 | } else { 70 | $this->sources[] = $source; 71 | } 72 | } 73 | 74 | return !$duplicate; 75 | } 76 | 77 | /** 78 | * Passes visited objects and source URLs to the child iterator. 79 | */ 80 | public function getChildren() 81 | { 82 | return new self($this->getInnerIterator()->getChildren(), $this->visited, $this->sources); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Assetic/Filter/JpegtranFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class JpegtranFilter extends BaseProcessFilter 25 | { 26 | const COPY_NONE = 'none'; 27 | const COPY_COMMENTS = 'comments'; 28 | const COPY_ALL = 'all'; 29 | 30 | private $jpegtranBin; 31 | private $optimize; 32 | private $copy; 33 | private $progressive; 34 | private $restart; 35 | 36 | /** 37 | * Constructor. 38 | * 39 | * @param string $jpegtranBin Path to the jpegtran binary 40 | */ 41 | public function __construct($jpegtranBin = '/usr/bin/jpegtran') 42 | { 43 | $this->jpegtranBin = $jpegtranBin; 44 | } 45 | 46 | public function setOptimize($optimize) 47 | { 48 | $this->optimize = $optimize; 49 | } 50 | 51 | public function setCopy($copy) 52 | { 53 | $this->copy = $copy; 54 | } 55 | 56 | public function setProgressive($progressive) 57 | { 58 | $this->progressive = $progressive; 59 | } 60 | 61 | public function setRestart($restart) 62 | { 63 | $this->restart = $restart; 64 | } 65 | 66 | public function filterLoad(AssetInterface $asset) 67 | { 68 | } 69 | 70 | public function filterDump(AssetInterface $asset) 71 | { 72 | $pb = $this->createProcessBuilder(array($this->jpegtranBin)); 73 | 74 | if ($this->optimize) { 75 | $pb->add('-optimize'); 76 | } 77 | 78 | if ($this->copy) { 79 | $pb->add('-copy')->add($this->copy); 80 | } 81 | 82 | if ($this->progressive) { 83 | $pb->add('-progressive'); 84 | } 85 | 86 | if (null !== $this->restart) { 87 | $pb->add('-restart')->add($this->restart); 88 | } 89 | 90 | $pb->add($input = FilesystemUtils::createTemporaryFile('jpegtran')); 91 | file_put_contents($input, $asset->getContent()); 92 | 93 | $proc = $pb->getProcess(); 94 | $code = $proc->run(); 95 | unlink($input); 96 | 97 | if (0 !== $code) { 98 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 99 | } 100 | 101 | $asset->setContent($proc->getOutput()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Assetic/AssetWriter.php: -------------------------------------------------------------------------------- 1 | 21 | * @author Johannes M. Schmitt 22 | */ 23 | class AssetWriter 24 | { 25 | private $dir; 26 | private $values; 27 | 28 | /** 29 | * Constructor. 30 | * 31 | * @param string $dir The base web directory 32 | * @param array $values Variable values 33 | * 34 | * @throws \InvalidArgumentException if a variable value is not a string 35 | */ 36 | public function __construct($dir, array $values = array()) 37 | { 38 | foreach ($values as $var => $vals) { 39 | foreach ($vals as $value) { 40 | if (!is_string($value)) { 41 | throw new \InvalidArgumentException(sprintf('All variable values must be strings, but got %s for variable "%s".', json_encode($value), $var)); 42 | } 43 | } 44 | } 45 | 46 | $this->dir = $dir; 47 | $this->values = $values; 48 | } 49 | 50 | public function writeManagerAssets(AssetManager $am) 51 | { 52 | foreach ($am->getNames() as $name) { 53 | $this->writeAsset($am->get($name)); 54 | } 55 | } 56 | 57 | public function writeAsset(AssetInterface $asset) 58 | { 59 | foreach (VarUtils::getCombinations($asset->getVars(), $this->values) as $combination) { 60 | $asset->setValues($combination); 61 | 62 | static::write( 63 | $this->dir.'/'.VarUtils::resolve( 64 | $asset->getTargetPath(), 65 | $asset->getVars(), 66 | $asset->getValues() 67 | ), 68 | $asset->dump() 69 | ); 70 | } 71 | } 72 | 73 | protected static function write($path, $contents) 74 | { 75 | if (!is_dir($dir = dirname($path)) && false === @mkdir($dir, 0777, true)) { 76 | throw new \RuntimeException('Unable to create directory '.$dir); 77 | } 78 | 79 | if (false === @file_put_contents($path, $contents)) { 80 | throw new \RuntimeException('Unable to write file '.$path); 81 | } 82 | } 83 | 84 | /** 85 | * Not used. 86 | * 87 | * This method is provided for backward compatibility with certain versions 88 | * of AsseticBundle. 89 | */ 90 | private function getCombinations(array $vars) 91 | { 92 | return VarUtils::getCombinations($vars, $this->values); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Assetic/Filter/EmberPrecompileFilter.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | class EmberPrecompileFilter extends BaseNodeFilter 27 | { 28 | private $emberBin; 29 | private $nodeBin; 30 | 31 | public function __construct($handlebarsBin = '/usr/bin/ember-precompile', $nodeBin = null) 32 | { 33 | $this->emberBin = $handlebarsBin; 34 | $this->nodeBin = $nodeBin; 35 | } 36 | 37 | public function filterLoad(AssetInterface $asset) 38 | { 39 | $pb = $this->createProcessBuilder($this->nodeBin 40 | ? array($this->nodeBin, $this->emberBin) 41 | : array($this->emberBin)); 42 | 43 | if ($sourcePath = $asset->getSourcePath()) { 44 | $templateName = basename($sourcePath); 45 | } else { 46 | throw new \LogicException('The embed-precompile filter requires that assets have a source path set'); 47 | } 48 | 49 | $inputDirPath = FilesystemUtils::createThrowAwayDirectory('ember_in'); 50 | $inputPath = $inputDirPath.DIRECTORY_SEPARATOR.$templateName; 51 | $outputPath = FilesystemUtils::createTemporaryFile('ember_out'); 52 | 53 | file_put_contents($inputPath, $asset->getContent()); 54 | 55 | $pb->add($inputPath)->add('-f')->add($outputPath); 56 | 57 | $process = $pb->getProcess(); 58 | $returnCode = $process->run(); 59 | 60 | unlink($inputPath); 61 | rmdir($inputDirPath); 62 | 63 | if (127 === $returnCode) { 64 | throw new \RuntimeException('Path to node executable could not be resolved.'); 65 | } 66 | 67 | if (0 !== $returnCode) { 68 | if (file_exists($outputPath)) { 69 | unlink($outputPath); 70 | } 71 | throw FilterException::fromProcess($process)->setInput($asset->getContent()); 72 | } 73 | 74 | if (!file_exists($outputPath)) { 75 | throw new \RuntimeException('Error creating output file.'); 76 | } 77 | 78 | $compiledJs = file_get_contents($outputPath); 79 | unlink($outputPath); 80 | 81 | $asset->setContent($compiledJs); 82 | } 83 | 84 | public function filterDump(AssetInterface $asset) 85 | { 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Assetic/Filter/GoogleClosure/BaseCompilerFilter.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | abstract class BaseCompilerFilter implements FilterInterface 23 | { 24 | // compilation levels 25 | const COMPILE_WHITESPACE_ONLY = 'WHITESPACE_ONLY'; 26 | const COMPILE_SIMPLE_OPTIMIZATIONS = 'SIMPLE_OPTIMIZATIONS'; 27 | const COMPILE_ADVANCED_OPTIMIZATIONS = 'ADVANCED_OPTIMIZATIONS'; 28 | 29 | // formatting modes 30 | const FORMAT_PRETTY_PRINT = 'pretty_print'; 31 | const FORMAT_PRINT_INPUT_DELIMITER = 'print_input_delimiter'; 32 | 33 | // warning levels 34 | const LEVEL_QUIET = 'QUIET'; 35 | const LEVEL_DEFAULT = 'DEFAULT'; 36 | const LEVEL_VERBOSE = 'VERBOSE'; 37 | 38 | // languages 39 | const LANGUAGE_ECMASCRIPT3 = 'ECMASCRIPT3'; 40 | const LANGUAGE_ECMASCRIPT5 = 'ECMASCRIPT5'; 41 | const LANGUAGE_ECMASCRIPT5_STRICT = 'ECMASCRIPT5_STRICT'; 42 | 43 | protected $timeout; 44 | protected $compilationLevel; 45 | protected $jsExterns; 46 | protected $externsUrl; 47 | protected $excludeDefaultExterns; 48 | protected $formatting; 49 | protected $useClosureLibrary; 50 | protected $warningLevel; 51 | protected $language; 52 | 53 | public function setTimeout($timeout) 54 | { 55 | $this->timeout = $timeout; 56 | } 57 | 58 | public function setCompilationLevel($compilationLevel) 59 | { 60 | $this->compilationLevel = $compilationLevel; 61 | } 62 | 63 | public function setJsExterns($jsExterns) 64 | { 65 | $this->jsExterns = $jsExterns; 66 | } 67 | 68 | public function setExternsUrl($externsUrl) 69 | { 70 | $this->externsUrl = $externsUrl; 71 | } 72 | 73 | public function setExcludeDefaultExterns($excludeDefaultExterns) 74 | { 75 | $this->excludeDefaultExterns = $excludeDefaultExterns; 76 | } 77 | 78 | public function setFormatting($formatting) 79 | { 80 | $this->formatting = $formatting; 81 | } 82 | 83 | public function setUseClosureLibrary($useClosureLibrary) 84 | { 85 | $this->useClosureLibrary = $useClosureLibrary; 86 | } 87 | 88 | public function setWarningLevel($warningLevel) 89 | { 90 | $this->warningLevel = $warningLevel; 91 | } 92 | 93 | public function setLanguage($language) 94 | { 95 | $this->language = $language; 96 | } 97 | 98 | public function filterLoad(AssetInterface $asset) 99 | { 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Assetic/Filter/Sass/BaseSassFilter.php: -------------------------------------------------------------------------------- 1 | loadPaths = $loadPaths; 18 | } 19 | 20 | public function addLoadPath($loadPath) 21 | { 22 | $this->loadPaths[] = $loadPath; 23 | } 24 | 25 | public function getChildren(AssetFactory $factory, $content, $loadPath = null) 26 | { 27 | $loadPaths = $this->loadPaths; 28 | if ($loadPath) { 29 | array_unshift($loadPaths, $loadPath); 30 | } 31 | 32 | if (!$loadPaths) { 33 | return array(); 34 | } 35 | 36 | $children = array(); 37 | foreach (SassUtils::extractImports($content) as $reference) { 38 | if ('.css' === substr($reference, -4)) { 39 | // skip normal css imports 40 | // todo: skip imports with media queries 41 | continue; 42 | } 43 | 44 | // the reference may or may not have an extension or be a partial 45 | if (pathinfo($reference, PATHINFO_EXTENSION)) { 46 | $needles = array( 47 | $reference, 48 | self::partialize($reference), 49 | ); 50 | } else { 51 | $needles = array( 52 | $reference.'.scss', 53 | $reference.'.sass', 54 | self::partialize($reference).'.scss', 55 | self::partialize($reference).'.sass', 56 | ); 57 | } 58 | 59 | foreach ($loadPaths as $loadPath) { 60 | foreach ($needles as $needle) { 61 | if (file_exists($file = $loadPath.'/'.$needle)) { 62 | $coll = $factory->createAsset($file, array(), array('root' => $loadPath)); 63 | foreach ($coll as $leaf) { 64 | /** @var $leaf AssetInterface */ 65 | $leaf->ensureFilter($this); 66 | $children[] = $leaf; 67 | goto next_reference; 68 | } 69 | } 70 | } 71 | } 72 | 73 | next_reference: 74 | } 75 | 76 | return $children; 77 | } 78 | 79 | private static function partialize($reference) 80 | { 81 | $parts = pathinfo($reference); 82 | 83 | if ('.' === $parts['dirname']) { 84 | $partial = '_'.$parts['filename']; 85 | } else { 86 | $partial = $parts['dirname'].DIRECTORY_SEPARATOR.'_'.$parts['filename']; 87 | } 88 | 89 | if (isset($parts['extension'])) { 90 | $partial .= '.'.$parts['extension']; 91 | } 92 | 93 | return $partial; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Assetic/Factory/Resource/CoalescingDirectoryResource.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class CoalescingDirectoryResource implements IteratorResourceInterface 20 | { 21 | private $directories; 22 | 23 | public function __construct($directories) 24 | { 25 | $this->directories = array(); 26 | 27 | foreach ($directories as $directory) { 28 | $this->addDirectory($directory); 29 | } 30 | } 31 | 32 | public function addDirectory(IteratorResourceInterface $directory) 33 | { 34 | $this->directories[] = $directory; 35 | } 36 | 37 | public function isFresh($timestamp) 38 | { 39 | foreach ($this->getFileResources() as $file) { 40 | if (!$file->isFresh($timestamp)) { 41 | return false; 42 | } 43 | } 44 | 45 | return true; 46 | } 47 | 48 | public function getContent() 49 | { 50 | $parts = array(); 51 | foreach ($this->getFileResources() as $file) { 52 | $parts[] = $file->getContent(); 53 | } 54 | 55 | return implode("\n", $parts); 56 | } 57 | 58 | /** 59 | * Returns a string to uniquely identify the current resource. 60 | * 61 | * @return string An identifying string 62 | */ 63 | public function __toString() 64 | { 65 | $parts = array(); 66 | foreach ($this->directories as $directory) { 67 | $parts[] = (string) $directory; 68 | } 69 | 70 | return implode(',', $parts); 71 | } 72 | 73 | public function getIterator() 74 | { 75 | return new \ArrayIterator($this->getFileResources()); 76 | } 77 | 78 | /** 79 | * Returns the relative version of a filename. 80 | * 81 | * @param ResourceInterface $file The file 82 | * @param ResourceInterface $directory The directory 83 | * 84 | * @return string The name to compare with files from other directories 85 | */ 86 | protected function getRelativeName(ResourceInterface $file, ResourceInterface $directory) 87 | { 88 | return substr((string) $file, strlen((string) $directory)); 89 | } 90 | 91 | /** 92 | * Performs the coalesce. 93 | * 94 | * @return array An array of file resources 95 | */ 96 | private function getFileResources() 97 | { 98 | $paths = array(); 99 | 100 | foreach ($this->directories as $directory) { 101 | foreach ($directory as $file) { 102 | $relative = $this->getRelativeName($file, $directory); 103 | 104 | if (!isset($paths[$relative])) { 105 | $paths[$relative] = $file; 106 | } 107 | } 108 | } 109 | 110 | return array_values($paths); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Assetic/Asset/GlobAsset.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class GlobAsset extends AssetCollection 23 | { 24 | private $globs; 25 | private $initialized; 26 | 27 | /** 28 | * Constructor. 29 | * 30 | * @param string|array $globs A single glob path or array of paths 31 | * @param array $filters An array of filters 32 | * @param string $root The root directory 33 | * @param array $vars 34 | */ 35 | public function __construct($globs, $filters = array(), $root = null, array $vars = array()) 36 | { 37 | $this->globs = (array) $globs; 38 | $this->initialized = false; 39 | 40 | parent::__construct(array(), $filters, $root, $vars); 41 | } 42 | 43 | public function all() 44 | { 45 | if (!$this->initialized) { 46 | $this->initialize(); 47 | } 48 | 49 | return parent::all(); 50 | } 51 | 52 | public function load(FilterInterface $additionalFilter = null) 53 | { 54 | if (!$this->initialized) { 55 | $this->initialize(); 56 | } 57 | 58 | parent::load($additionalFilter); 59 | } 60 | 61 | public function dump(FilterInterface $additionalFilter = null) 62 | { 63 | if (!$this->initialized) { 64 | $this->initialize(); 65 | } 66 | 67 | return parent::dump($additionalFilter); 68 | } 69 | 70 | public function getLastModified() 71 | { 72 | if (!$this->initialized) { 73 | $this->initialize(); 74 | } 75 | 76 | return parent::getLastModified(); 77 | } 78 | 79 | public function getIterator() 80 | { 81 | if (!$this->initialized) { 82 | $this->initialize(); 83 | } 84 | 85 | return parent::getIterator(); 86 | } 87 | 88 | public function setValues(array $values) 89 | { 90 | parent::setValues($values); 91 | $this->initialized = false; 92 | } 93 | 94 | /** 95 | * Initializes the collection based on the glob(s) passed in. 96 | */ 97 | private function initialize() 98 | { 99 | foreach ($this->globs as $glob) { 100 | $glob = VarUtils::resolve($glob, $this->getVars(), $this->getValues()); 101 | 102 | if (false !== $paths = glob($glob)) { 103 | foreach ($paths as $path) { 104 | if (is_file($path)) { 105 | $asset = new FileAsset($path, array(), $this->getSourceRoot(), null, $this->getVars()); 106 | $asset->setValues($this->getValues()); 107 | $this->add($asset); 108 | } 109 | } 110 | } 111 | } 112 | 113 | $this->initialized = true; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Assetic/Filter/HandlebarsFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class HandlebarsFilter extends BaseNodeFilter 25 | { 26 | private $handlebarsBin; 27 | private $nodeBin; 28 | 29 | private $minimize = false; 30 | private $simple = false; 31 | 32 | public function __construct($handlebarsBin = '/usr/bin/handlebars', $nodeBin = null) 33 | { 34 | $this->handlebarsBin = $handlebarsBin; 35 | $this->nodeBin = $nodeBin; 36 | } 37 | 38 | public function setMinimize($minimize) 39 | { 40 | $this->minimize = $minimize; 41 | } 42 | 43 | public function setSimple($simple) 44 | { 45 | $this->simple = $simple; 46 | } 47 | 48 | public function filterLoad(AssetInterface $asset) 49 | { 50 | $pb = $this->createProcessBuilder($this->nodeBin 51 | ? array($this->nodeBin, $this->handlebarsBin) 52 | : array($this->handlebarsBin)); 53 | 54 | if ($sourcePath = $asset->getSourcePath()) { 55 | $templateName = basename($sourcePath); 56 | } else { 57 | throw new \LogicException('The handlebars filter requires that assets have a source path set'); 58 | } 59 | 60 | $inputDirPath = FilesystemUtils::createThrowAwayDirectory('handlebars_in'); 61 | $inputPath = $inputDirPath.DIRECTORY_SEPARATOR.$templateName; 62 | $outputPath = FilesystemUtils::createTemporaryFile('handlebars_out'); 63 | 64 | file_put_contents($inputPath, $asset->getContent()); 65 | 66 | $pb->add($inputPath)->add('-f')->add($outputPath); 67 | 68 | if ($this->minimize) { 69 | $pb->add('--min'); 70 | } 71 | 72 | if ($this->simple) { 73 | $pb->add('--simple'); 74 | } 75 | 76 | $process = $pb->getProcess(); 77 | $returnCode = $process->run(); 78 | 79 | unlink($inputPath); 80 | rmdir($inputDirPath); 81 | 82 | if (127 === $returnCode) { 83 | throw new \RuntimeException('Path to node executable could not be resolved.'); 84 | } 85 | 86 | if (0 !== $returnCode) { 87 | if (file_exists($outputPath)) { 88 | unlink($outputPath); 89 | } 90 | throw FilterException::fromProcess($process)->setInput($asset->getContent()); 91 | } 92 | 93 | if (!file_exists($outputPath)) { 94 | throw new \RuntimeException('Error creating output file.'); 95 | } 96 | 97 | $compiledJs = file_get_contents($outputPath); 98 | unlink($outputPath); 99 | 100 | $asset->setContent($compiledJs); 101 | } 102 | 103 | public function filterDump(AssetInterface $asset) 104 | { 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Assetic/Filter/Yui/BaseCompressorFilter.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | abstract class BaseCompressorFilter extends BaseProcessFilter 26 | { 27 | private $jarPath; 28 | private $javaPath; 29 | private $charset; 30 | private $lineBreak; 31 | private $stackSize; 32 | 33 | public function __construct($jarPath, $javaPath = '/usr/bin/java') 34 | { 35 | $this->jarPath = $jarPath; 36 | $this->javaPath = $javaPath; 37 | } 38 | 39 | public function setCharset($charset) 40 | { 41 | $this->charset = $charset; 42 | } 43 | 44 | public function setLineBreak($lineBreak) 45 | { 46 | $this->lineBreak = $lineBreak; 47 | } 48 | 49 | public function setStackSize($stackSize) 50 | { 51 | $this->stackSize = $stackSize; 52 | } 53 | 54 | public function filterLoad(AssetInterface $asset) 55 | { 56 | } 57 | 58 | /** 59 | * Compresses a string. 60 | * 61 | * @param string $content The content to compress 62 | * @param string $type The type of content, either "js" or "css" 63 | * @param array $options An indexed array of additional options 64 | * 65 | * @return string The compressed content 66 | */ 67 | protected function compress($content, $type, $options = array()) 68 | { 69 | $pb = $this->createProcessBuilder(array($this->javaPath)); 70 | 71 | if (null !== $this->stackSize) { 72 | $pb->add('-Xss'.$this->stackSize); 73 | } 74 | 75 | $pb->add('-jar')->add($this->jarPath); 76 | 77 | foreach ($options as $option) { 78 | $pb->add($option); 79 | } 80 | 81 | if (null !== $this->charset) { 82 | $pb->add('--charset')->add($this->charset); 83 | } 84 | 85 | if (null !== $this->lineBreak) { 86 | $pb->add('--line-break')->add($this->lineBreak); 87 | } 88 | 89 | // input and output files 90 | $tempDir = FilesystemUtils::getTemporaryDirectory(); 91 | $input = tempnam($tempDir, 'assetic_yui_input'); 92 | $output = tempnam($tempDir, 'assetic_yui_output'); 93 | file_put_contents($input, $content); 94 | $pb->add('-o')->add($output)->add('--type')->add($type)->add($input); 95 | 96 | $proc = $pb->getProcess(); 97 | $code = $proc->run(); 98 | unlink($input); 99 | 100 | if (0 !== $code) { 101 | if (file_exists($output)) { 102 | unlink($output); 103 | } 104 | 105 | throw FilterException::fromProcess($proc)->setInput($content); 106 | } 107 | 108 | if (!file_exists($output)) { 109 | throw new \RuntimeException('Error creating output file.'); 110 | } 111 | 112 | $retval = file_get_contents($output); 113 | unlink($output); 114 | 115 | return $retval; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Assetic/Filter/StylusFilter.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | class StylusFilter extends BaseNodeFilter implements DependencyExtractorInterface 26 | { 27 | private $nodeBin; 28 | private $compress; 29 | private $useNib; 30 | 31 | /** 32 | * Constructs filter. 33 | * 34 | * @param string $nodeBin The path to the node binary 35 | * @param array $nodePaths An array of node paths 36 | */ 37 | public function __construct($nodeBin = '/usr/bin/node', array $nodePaths = array()) 38 | { 39 | $this->nodeBin = $nodeBin; 40 | $this->setNodePaths($nodePaths); 41 | } 42 | 43 | /** 44 | * Enable output compression. 45 | * 46 | * @param boolean $compress 47 | */ 48 | public function setCompress($compress) 49 | { 50 | $this->compress = $compress; 51 | } 52 | 53 | /** 54 | * Enable the use of Nib 55 | * 56 | * @param boolean $useNib 57 | */ 58 | public function setUseNib($useNib) 59 | { 60 | $this->useNib = $useNib; 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function filterLoad(AssetInterface $asset) 67 | { 68 | static $format = <<<'EOF' 69 | var stylus = require('stylus'); 70 | var sys = require(process.binding('natives').util ? 'util' : 'sys'); 71 | 72 | stylus(%s, %s)%s.render(function(e, css){ 73 | if (e) { 74 | throw e; 75 | } 76 | 77 | sys.print(css); 78 | process.exit(0); 79 | }); 80 | 81 | EOF; 82 | 83 | // parser options 84 | $parserOptions = array(); 85 | if ($dir = $asset->getSourceDirectory()) { 86 | $parserOptions['paths'] = array($dir); 87 | $parserOptions['filename'] = basename($asset->getSourcePath()); 88 | } 89 | 90 | if (null !== $this->compress) { 91 | $parserOptions['compress'] = $this->compress; 92 | } 93 | 94 | $pb = $this->createProcessBuilder(); 95 | 96 | $pb->add($this->nodeBin)->add($input = FilesystemUtils::createTemporaryFile('stylus')); 97 | file_put_contents($input, sprintf($format, 98 | json_encode($asset->getContent()), 99 | json_encode($parserOptions), 100 | $this->useNib ? '.use(require(\'nib\')())' : '' 101 | )); 102 | 103 | $proc = $pb->getProcess(); 104 | $code = $proc->run(); 105 | unlink($input); 106 | 107 | if (0 !== $code) { 108 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 109 | } 110 | 111 | $asset->setContent($proc->getOutput()); 112 | } 113 | 114 | /** 115 | * {@inheritdoc} 116 | */ 117 | public function filterDump(AssetInterface $asset) 118 | { 119 | } 120 | 121 | public function getChildren(AssetFactory $factory, $content, $loadPath = null) 122 | { 123 | // todo 124 | return array(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Assetic/Filter/UglifyCssFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class UglifyCssFilter extends BaseNodeFilter 25 | { 26 | private $uglifycssBin; 27 | private $nodeBin; 28 | 29 | private $expandVars; 30 | private $uglyComments; 31 | private $cuteComments; 32 | 33 | /** 34 | * @param string $uglifycssBin Absolute path to the uglifycss executable 35 | * @param string $nodeBin Absolute path to the folder containg node.js executable 36 | */ 37 | public function __construct($uglifycssBin = '/usr/bin/uglifycss', $nodeBin = null) 38 | { 39 | $this->uglifycssBin = $uglifycssBin; 40 | $this->nodeBin = $nodeBin; 41 | } 42 | 43 | /** 44 | * Expand variables 45 | * @param bool $expandVars True to enable 46 | */ 47 | public function setExpandVars($expandVars) 48 | { 49 | $this->expandVars = $expandVars; 50 | } 51 | 52 | /** 53 | * Remove newlines within preserved comments 54 | * @param bool $uglyComments True to enable 55 | */ 56 | public function setUglyComments($uglyComments) 57 | { 58 | $this->uglyComments = $uglyComments; 59 | } 60 | 61 | /** 62 | * Preserve newlines within and around preserved comments 63 | * @param bool $cuteComments True to enable 64 | */ 65 | public function setCuteComments($cuteComments) 66 | { 67 | $this->cuteComments = $cuteComments; 68 | } 69 | 70 | /** 71 | * @see Assetic\Filter\FilterInterface::filterLoad() 72 | */ 73 | public function filterLoad(AssetInterface $asset) 74 | { 75 | } 76 | 77 | /** 78 | * Run the asset through UglifyJs 79 | * 80 | * @see Assetic\Filter\FilterInterface::filterDump() 81 | */ 82 | public function filterDump(AssetInterface $asset) 83 | { 84 | $pb = $this->createProcessBuilder($this->nodeBin 85 | ? array($this->nodeBin, $this->uglifycssBin) 86 | : array($this->uglifycssBin)); 87 | 88 | if ($this->expandVars) { 89 | $pb->add('--expand-vars'); 90 | } 91 | 92 | if ($this->uglyComments) { 93 | $pb->add('--ugly-comments'); 94 | } 95 | 96 | if ($this->cuteComments) { 97 | $pb->add('--cute-comments'); 98 | } 99 | 100 | // input and output files 101 | $input = FilesystemUtils::createTemporaryFile('uglifycss'); 102 | 103 | file_put_contents($input, $asset->getContent()); 104 | $pb->add($input); 105 | 106 | $proc = $pb->getProcess(); 107 | $code = $proc->run(); 108 | unlink($input); 109 | 110 | if (127 === $code) { 111 | throw new \RuntimeException('Path to node executable could not be resolved.'); 112 | } 113 | 114 | if (0 !== $code) { 115 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 116 | } 117 | 118 | $asset->setContent($proc->getOutput()); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Assetic/Cache/ConfigCache.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class ConfigCache 20 | { 21 | private $dir; 22 | 23 | /** 24 | * Construct. 25 | * 26 | * @param string $dir The cache directory 27 | */ 28 | public function __construct($dir) 29 | { 30 | $this->dir = $dir; 31 | } 32 | 33 | /** 34 | * Checks of the cache has a file. 35 | * 36 | * @param string $resource A cache key 37 | * 38 | * @return Boolean True if a file exists 39 | */ 40 | public function has($resource) 41 | { 42 | return file_exists($this->getSourcePath($resource)); 43 | } 44 | 45 | /** 46 | * Writes a value to a file. 47 | * 48 | * @param string $resource A cache key 49 | * @param mixed $value A value to cache 50 | */ 51 | public function set($resource, $value) 52 | { 53 | $path = $this->getSourcePath($resource); 54 | 55 | if (!is_dir($dir = dirname($path)) && false === @mkdir($dir, 0777, true)) { 56 | // @codeCoverageIgnoreStart 57 | throw new \RuntimeException('Unable to create directory '.$dir); 58 | // @codeCoverageIgnoreEnd 59 | } 60 | 61 | if (false === @file_put_contents($path, sprintf("getSourcePath($resource); 78 | 79 | if (!file_exists($path)) { 80 | throw new \RuntimeException('There is no cached value for '.$resource); 81 | } 82 | 83 | return include $path; 84 | } 85 | 86 | /** 87 | * Returns a timestamp for when the cache was created. 88 | * 89 | * @param string $resource A cache key 90 | * 91 | * @return integer A UNIX timestamp 92 | */ 93 | public function getTimestamp($resource) 94 | { 95 | $path = $this->getSourcePath($resource); 96 | 97 | if (!file_exists($path)) { 98 | throw new \RuntimeException('There is no cached value for '.$resource); 99 | } 100 | 101 | if (false === $mtime = @filemtime($path)) { 102 | // @codeCoverageIgnoreStart 103 | throw new \RuntimeException('Unable to determine file mtime for '.$path); 104 | // @codeCoverageIgnoreEnd 105 | } 106 | 107 | return $mtime; 108 | } 109 | 110 | /** 111 | * Returns the path where the file corresponding to the supplied cache key can be included from. 112 | * 113 | * @param string $resource A cache key 114 | * 115 | * @return string A file path 116 | */ 117 | private function getSourcePath($resource) 118 | { 119 | $key = md5($resource); 120 | 121 | return $this->dir.'/'.$key[0].'/'.$key.'.php'; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 30 | } 31 | 32 | /** 33 | * Returns an array of javascript URLs. 34 | * 35 | * @param array|string $inputs Input strings 36 | * @param array|string $filters Filter names 37 | * @param array $options An array of options 38 | * 39 | * @return array An array of javascript URLs 40 | */ 41 | function assetic_javascripts($inputs = array(), $filters = array(), array $options = array()) 42 | { 43 | if (!isset($options['output'])) { 44 | $options['output'] = 'js/*.js'; 45 | } 46 | 47 | return _assetic_urls($inputs, $filters, $options); 48 | } 49 | 50 | /** 51 | * Returns an array of stylesheet URLs. 52 | * 53 | * @param array|string $inputs Input strings 54 | * @param array|string $filters Filter names 55 | * @param array $options An array of options 56 | * 57 | * @return array An array of stylesheet URLs 58 | */ 59 | function assetic_stylesheets($inputs = array(), $filters = array(), array $options = array()) 60 | { 61 | if (!isset($options['output'])) { 62 | $options['output'] = 'css/*.css'; 63 | } 64 | 65 | return _assetic_urls($inputs, $filters, $options); 66 | } 67 | 68 | /** 69 | * Returns an image URL. 70 | * 71 | * @param string $input An input 72 | * @param array|string $filters Filter names 73 | * @param array $options An array of options 74 | * 75 | * @return string An image URL 76 | */ 77 | function assetic_image($input, $filters = array(), array $options = array()) 78 | { 79 | if (!isset($options['output'])) { 80 | $options['output'] = 'images/*'; 81 | } 82 | 83 | $urls = _assetic_urls($input, $filters, $options); 84 | 85 | return current($urls); 86 | } 87 | 88 | /** 89 | * Returns an array of asset urls. 90 | * 91 | * @param array|string $inputs Input strings 92 | * @param array|string $filters Filter names 93 | * @param array $options An array of options 94 | * 95 | * @return array An array of URLs 96 | */ 97 | function _assetic_urls($inputs = array(), $filters = array(), array $options = array()) 98 | { 99 | global $_assetic; 100 | 101 | if (!is_array($inputs)) { 102 | $inputs = array_filter(array_map('trim', explode(',', $inputs))); 103 | } 104 | 105 | if (!is_array($filters)) { 106 | $filters = array_filter(array_map('trim', explode(',', $filters))); 107 | } 108 | 109 | $coll = $_assetic->factory->createAsset($inputs, $filters, $options); 110 | 111 | $debug = isset($options['debug']) ? $options['debug'] : $_assetic->factory->isDebug(); 112 | $combine = isset($options['combine']) ? $options['combine'] : !$debug; 113 | 114 | $one = $coll->getTargetPath(); 115 | if ($combine) { 116 | $many = array($one); 117 | } else { 118 | $many = array(); 119 | foreach ($coll as $leaf) { 120 | $many[] = $leaf->getTargetPath(); 121 | } 122 | } 123 | 124 | return new TraversableString($one, $many); 125 | } 126 | -------------------------------------------------------------------------------- /src/Assetic/Filter/PngoutFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class PngoutFilter extends BaseProcessFilter 25 | { 26 | // -c# 27 | const COLOR_GREY = '0'; 28 | const COLOR_RGB = '2'; 29 | const COLOR_PAL = '3'; 30 | const COLOR_GRAY_ALPHA = '4'; 31 | const COLOR_RGB_ALPHA = '6'; 32 | 33 | // -f# 34 | const FILTER_NONE = '0'; 35 | const FILTER_X = '1'; 36 | const FILTER_Y = '2'; 37 | const FILTER_X_Y = '3'; 38 | const FILTER_PAETH = '4'; 39 | const FILTER_MIXED = '5'; 40 | 41 | // -s# 42 | const STRATEGY_XTREME = '0'; 43 | const STRATEGY_INTENSE = '1'; 44 | const STRATEGY_LONGEST_MATCH = '2'; 45 | const STRATEGY_HUFFMAN_ONLY = '3'; 46 | const STRATEGY_UNCOMPRESSED = '4'; 47 | 48 | private $pngoutBin; 49 | private $color; 50 | private $filter; 51 | private $strategy; 52 | private $blockSplitThreshold; 53 | 54 | /** 55 | * Constructor. 56 | * 57 | * @param string $pngoutBin Path to the pngout binary 58 | */ 59 | public function __construct($pngoutBin = '/usr/bin/pngout') 60 | { 61 | $this->pngoutBin = $pngoutBin; 62 | } 63 | 64 | public function setColor($color) 65 | { 66 | $this->color = $color; 67 | } 68 | 69 | public function setFilter($filter) 70 | { 71 | $this->filter = $filter; 72 | } 73 | 74 | public function setStrategy($strategy) 75 | { 76 | $this->strategy = $strategy; 77 | } 78 | 79 | public function setBlockSplitThreshold($blockSplitThreshold) 80 | { 81 | $this->blockSplitThreshold = $blockSplitThreshold; 82 | } 83 | 84 | public function filterLoad(AssetInterface $asset) 85 | { 86 | } 87 | 88 | public function filterDump(AssetInterface $asset) 89 | { 90 | $pb = $this->createProcessBuilder(array($this->pngoutBin)); 91 | 92 | if (null !== $this->color) { 93 | $pb->add('-c'.$this->color); 94 | } 95 | 96 | if (null !== $this->filter) { 97 | $pb->add('-f'.$this->filter); 98 | } 99 | 100 | if (null !== $this->strategy) { 101 | $pb->add('-s'.$this->strategy); 102 | } 103 | 104 | if (null !== $this->blockSplitThreshold) { 105 | $pb->add('-b'.$this->blockSplitThreshold); 106 | } 107 | 108 | $pb->add($input = FilesystemUtils::createTemporaryFile('pngout_in')); 109 | file_put_contents($input, $asset->getContent()); 110 | 111 | $output = FilesystemUtils::createTemporaryFile('pngout_out'); 112 | unlink($output); 113 | $pb->add($output .= '.png'); 114 | 115 | $proc = $pb->getProcess(); 116 | $code = $proc->run(); 117 | 118 | if (0 !== $code) { 119 | unlink($input); 120 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 121 | } 122 | 123 | $asset->setContent(file_get_contents($output)); 124 | 125 | unlink($input); 126 | unlink($output); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Assetic/Filter/GoogleClosure/CompilerJarFilter.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | class CompilerJarFilter extends BaseCompilerFilter 26 | { 27 | private $jarPath; 28 | private $javaPath; 29 | private $flagFile; 30 | 31 | public function __construct($jarPath, $javaPath = '/usr/bin/java') 32 | { 33 | $this->jarPath = $jarPath; 34 | $this->javaPath = $javaPath; 35 | } 36 | 37 | public function setFlagFile($flagFile) 38 | { 39 | $this->flagFile = $flagFile; 40 | } 41 | 42 | public function filterDump(AssetInterface $asset) 43 | { 44 | $is64bit = PHP_INT_SIZE === 8; 45 | $cleanup = array(); 46 | 47 | $pb = new ProcessBuilder(array_merge( 48 | array($this->javaPath), 49 | $is64bit 50 | ? array('-server', '-XX:+TieredCompilation') 51 | : array('-client', '-d32'), 52 | array('-jar', $this->jarPath) 53 | )); 54 | 55 | if (null !== $this->timeout) { 56 | $pb->setTimeout($this->timeout); 57 | } 58 | 59 | if (null !== $this->compilationLevel) { 60 | $pb->add('--compilation_level')->add($this->compilationLevel); 61 | } 62 | 63 | if (null !== $this->jsExterns) { 64 | $cleanup[] = $externs = FilesystemUtils::createTemporaryFile('google_closure'); 65 | file_put_contents($externs, $this->jsExterns); 66 | $pb->add('--externs')->add($externs); 67 | } 68 | 69 | if (null !== $this->externsUrl) { 70 | $cleanup[] = $externs = FilesystemUtils::createTemporaryFile('google_closure'); 71 | file_put_contents($externs, file_get_contents($this->externsUrl)); 72 | $pb->add('--externs')->add($externs); 73 | } 74 | 75 | if (null !== $this->excludeDefaultExterns) { 76 | $pb->add('--use_only_custom_externs'); 77 | } 78 | 79 | if (null !== $this->formatting) { 80 | $pb->add('--formatting')->add($this->formatting); 81 | } 82 | 83 | if (null !== $this->useClosureLibrary) { 84 | $pb->add('--manage_closure_dependencies'); 85 | } 86 | 87 | if (null !== $this->warningLevel) { 88 | $pb->add('--warning_level')->add($this->warningLevel); 89 | } 90 | 91 | if (null !== $this->language) { 92 | $pb->add('--language_in')->add($this->language); 93 | } 94 | 95 | if (null !== $this->flagFile) { 96 | $pb->add('--flagfile')->add($this->flagFile); 97 | } 98 | 99 | $pb->add('--js')->add($cleanup[] = $input = FilesystemUtils::createTemporaryFile('google_closure')); 100 | file_put_contents($input, $asset->getContent()); 101 | 102 | $proc = $pb->getProcess(); 103 | $code = $proc->run(); 104 | array_map('unlink', $cleanup); 105 | 106 | if (0 !== $code) { 107 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 108 | } 109 | 110 | $asset->setContent($proc->getOutput()); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Assetic/Asset/Iterator/AssetCollectionIterator.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class AssetCollectionIterator implements \RecursiveIterator 25 | { 26 | private $assets; 27 | private $filters; 28 | private $vars; 29 | private $output; 30 | private $clones; 31 | 32 | public function __construct(AssetCollectionInterface $coll, \SplObjectStorage $clones) 33 | { 34 | $this->assets = $coll->all(); 35 | $this->filters = $coll->getFilters(); 36 | $this->vars = $coll->getVars(); 37 | $this->output = $coll->getTargetPath(); 38 | $this->clones = $clones; 39 | 40 | if (false === $pos = strrpos($this->output, '.')) { 41 | $this->output .= '_*'; 42 | } else { 43 | $this->output = substr($this->output, 0, $pos).'_*'.substr($this->output, $pos); 44 | } 45 | } 46 | 47 | /** 48 | * Returns a copy of the current asset with filters and a target URL applied. 49 | * 50 | * @param Boolean $raw Returns the unmodified asset if true 51 | * 52 | * @return \Assetic\Asset\AssetInterface 53 | */ 54 | public function current($raw = false) 55 | { 56 | $asset = current($this->assets); 57 | 58 | if ($raw) { 59 | return $asset; 60 | } 61 | 62 | // clone once 63 | if (!isset($this->clones[$asset])) { 64 | $clone = $this->clones[$asset] = clone $asset; 65 | 66 | // generate a target path based on asset name 67 | $name = sprintf('%s_%d', pathinfo($asset->getSourcePath(), PATHINFO_FILENAME) ?: 'part', $this->key() + 1); 68 | 69 | $name = $this->removeDuplicateVar($name); 70 | 71 | $clone->setTargetPath(str_replace('*', $name, $this->output)); 72 | } else { 73 | $clone = $this->clones[$asset]; 74 | } 75 | 76 | // cascade filters 77 | foreach ($this->filters as $filter) { 78 | $clone->ensureFilter($filter); 79 | } 80 | 81 | return $clone; 82 | } 83 | 84 | public function key() 85 | { 86 | return key($this->assets); 87 | } 88 | 89 | public function next() 90 | { 91 | return next($this->assets); 92 | } 93 | 94 | public function rewind() 95 | { 96 | return reset($this->assets); 97 | } 98 | 99 | public function valid() 100 | { 101 | return false !== current($this->assets); 102 | } 103 | 104 | public function hasChildren() 105 | { 106 | return current($this->assets) instanceof AssetCollectionInterface; 107 | } 108 | 109 | /** 110 | * @uses current() 111 | */ 112 | public function getChildren() 113 | { 114 | return new self($this->current(), $this->clones); 115 | } 116 | 117 | private function removeDuplicateVar($name) 118 | { 119 | foreach ($this->vars as $var) { 120 | $var = '{'.$var.'}'; 121 | if (false !== strpos($name, $var) && false !== strpos($this->output, $var)) { 122 | $name = str_replace($var, '', $name); 123 | } 124 | } 125 | 126 | return $name; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Assetic/Factory/Resource/DirectoryResource.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class DirectoryResource implements IteratorResourceInterface 20 | { 21 | private $path; 22 | private $pattern; 23 | 24 | /** 25 | * Constructor. 26 | * 27 | * @param string $path A directory path 28 | * @param string $pattern A filename pattern 29 | */ 30 | public function __construct($path, $pattern = null) 31 | { 32 | if (DIRECTORY_SEPARATOR != substr($path, -1)) { 33 | $path .= DIRECTORY_SEPARATOR; 34 | } 35 | 36 | $this->path = $path; 37 | $this->pattern = $pattern; 38 | } 39 | 40 | public function isFresh($timestamp) 41 | { 42 | if (!is_dir($this->path) || filemtime($this->path) > $timestamp) { 43 | return false; 44 | } 45 | 46 | foreach ($this as $resource) { 47 | if (!$resource->isFresh($timestamp)) { 48 | return false; 49 | } 50 | } 51 | 52 | return true; 53 | } 54 | 55 | /** 56 | * Returns the combined content of all inner resources. 57 | */ 58 | public function getContent() 59 | { 60 | $content = array(); 61 | foreach ($this as $resource) { 62 | $content[] = $resource->getContent(); 63 | } 64 | 65 | return implode("\n", $content); 66 | } 67 | 68 | public function __toString() 69 | { 70 | return $this->path; 71 | } 72 | 73 | public function getIterator() 74 | { 75 | return is_dir($this->path) 76 | ? new DirectoryResourceIterator($this->getInnerIterator()) 77 | : new \EmptyIterator(); 78 | } 79 | 80 | protected function getInnerIterator() 81 | { 82 | return new DirectoryResourceFilterIterator(new \RecursiveDirectoryIterator($this->path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS), $this->pattern); 83 | } 84 | } 85 | 86 | /** 87 | * An iterator that converts file objects into file resources. 88 | * 89 | * @author Kris Wallsmith 90 | * @access private 91 | */ 92 | class DirectoryResourceIterator extends \RecursiveIteratorIterator 93 | { 94 | public function current() 95 | { 96 | return new FileResource(parent::current()->getPathname()); 97 | } 98 | } 99 | 100 | /** 101 | * Filters files by a basename pattern. 102 | * 103 | * @author Kris Wallsmith 104 | * @access private 105 | */ 106 | class DirectoryResourceFilterIterator extends \RecursiveFilterIterator 107 | { 108 | protected $pattern; 109 | 110 | public function __construct(\RecursiveDirectoryIterator $iterator, $pattern = null) 111 | { 112 | parent::__construct($iterator); 113 | 114 | $this->pattern = $pattern; 115 | } 116 | 117 | public function accept() 118 | { 119 | $file = $this->current(); 120 | $name = $file->getBasename(); 121 | 122 | if ($file->isDir()) { 123 | return '.' != $name[0]; 124 | } 125 | 126 | return null === $this->pattern || 0 < preg_match($this->pattern, $name); 127 | } 128 | 129 | public function getChildren() 130 | { 131 | return new self(new \RecursiveDirectoryIterator($this->current()->getPathname(), \RecursiveDirectoryIterator::FOLLOW_SYMLINKS), $this->pattern); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Assetic/Extension/Twig/TwigFormulaLoader.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class TwigFormulaLoader implements FormulaLoaderInterface 24 | { 25 | private $twig; 26 | private $logger; 27 | 28 | public function __construct(\Twig_Environment $twig, LoggerInterface $logger = null) 29 | { 30 | $this->twig = $twig; 31 | $this->logger = $logger; 32 | } 33 | 34 | public function load(ResourceInterface $resource) 35 | { 36 | try { 37 | $tokens = $this->twig->tokenize(new \Twig_Source($resource->getContent(), (string) $resource)); 38 | $nodes = $this->twig->parse($tokens); 39 | } catch (\Exception $e) { 40 | if ($this->logger) { 41 | $this->logger->error(sprintf('The template "%s" contains an error: %s', $resource, $e->getMessage())); 42 | } 43 | 44 | return array(); 45 | } 46 | 47 | return $this->loadNode($nodes); 48 | } 49 | 50 | /** 51 | * Loads assets from the supplied node. 52 | * 53 | * @param \Twig_Node $node 54 | * 55 | * @return array An array of asset formulae indexed by name 56 | */ 57 | private function loadNode(\Twig_Node $node) 58 | { 59 | $formulae = array(); 60 | 61 | if ($node instanceof AsseticNode) { 62 | $formulae[$node->getAttribute('name')] = array( 63 | $node->getAttribute('inputs'), 64 | $node->getAttribute('filters'), 65 | array( 66 | 'output' => $node->getAttribute('asset')->getTargetPath(), 67 | 'name' => $node->getAttribute('name'), 68 | 'debug' => $node->getAttribute('debug'), 69 | 'combine' => $node->getAttribute('combine'), 70 | 'vars' => $node->getAttribute('vars'), 71 | ), 72 | ); 73 | } elseif ($node instanceof AsseticFilterNode) { 74 | $name = $node->getAttribute('name'); 75 | 76 | $arguments = array(); 77 | foreach ($node->getNode('arguments') as $argument) { 78 | $arguments[] = eval('return '.$this->twig->compile($argument).';'); 79 | } 80 | 81 | $invoker = $this->twig->getExtension('Assetic\Extension\Twig\AsseticExtension')->getFilterInvoker($name); 82 | 83 | $inputs = isset($arguments[0]) ? (array) $arguments[0] : array(); 84 | $filters = $invoker->getFilters(); 85 | $options = array_replace($invoker->getOptions(), isset($arguments[1]) ? $arguments[1] : array()); 86 | 87 | if (!isset($options['name'])) { 88 | $options['name'] = $invoker->getFactory()->generateAssetName($inputs, $filters, $options); 89 | } 90 | 91 | $formulae[$options['name']] = array($inputs, $filters, $options); 92 | } 93 | 94 | foreach ($node as $child) { 95 | if ($child instanceof \Twig_Node) { 96 | $formulae += $this->loadNode($child); 97 | } 98 | } 99 | 100 | if ($node->hasAttribute('embedded_templates')) { 101 | foreach ($node->getAttribute('embedded_templates') as $child) { 102 | $formulae += $this->loadNode($child); 103 | } 104 | } 105 | 106 | return $formulae; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Assetic/Filter/CssRewriteFilter.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class CssRewriteFilter extends BaseCssFilter 22 | { 23 | public function filterLoad(AssetInterface $asset) 24 | { 25 | } 26 | 27 | public function filterDump(AssetInterface $asset) 28 | { 29 | $sourceBase = $asset->getSourceRoot(); 30 | $sourcePath = $asset->getSourcePath(); 31 | $targetPath = $asset->getTargetPath(); 32 | 33 | if (null === $sourcePath || null === $targetPath || $sourcePath == $targetPath) { 34 | return; 35 | } 36 | 37 | // learn how to get from the target back to the source 38 | if (false !== strpos($sourceBase, '://')) { 39 | list($scheme, $url) = explode('://', $sourceBase.'/'.$sourcePath, 2); 40 | list($host, $path) = explode('/', $url, 2); 41 | 42 | $host = $scheme.'://'.$host.'/'; 43 | $path = false === strpos($path, '/') ? '' : dirname($path); 44 | $path .= '/'; 45 | } else { 46 | // assume source and target are on the same host 47 | $host = ''; 48 | 49 | // pop entries off the target until it fits in the source 50 | if ('.' == dirname($sourcePath)) { 51 | $path = str_repeat('../', substr_count($targetPath, '/')); 52 | } elseif ('.' == $targetDir = dirname($targetPath)) { 53 | $path = dirname($sourcePath).'/'; 54 | } else { 55 | $path = ''; 56 | while (0 !== strpos($sourcePath, $targetDir)) { 57 | if (false !== $pos = strrpos($targetDir, '/')) { 58 | $targetDir = substr($targetDir, 0, $pos); 59 | $path .= '../'; 60 | } else { 61 | $targetDir = ''; 62 | $path .= '../'; 63 | break; 64 | } 65 | } 66 | $path .= ltrim(substr(dirname($sourcePath).'/', strlen($targetDir)), '/'); 67 | } 68 | } 69 | 70 | $content = $this->filterReferences($asset->getContent(), function ($matches) use ($host, $path) { 71 | if (false !== strpos($matches['url'], '://') || 0 === strpos($matches['url'], '//') || 0 === strpos($matches['url'], 'data:')) { 72 | // absolute or protocol-relative or data uri 73 | return $matches[0]; 74 | } 75 | 76 | if (isset($matches['url'][0]) && '/' == $matches['url'][0]) { 77 | // root relative 78 | return str_replace($matches['url'], $host.$matches['url'], $matches[0]); 79 | } 80 | 81 | // document relative 82 | $url = $matches['url']; 83 | while (0 === strpos($url, '../') && 2 <= substr_count($path, '/')) { 84 | $path = substr($path, 0, strrpos(rtrim($path, '/'), '/') + 1); 85 | $url = substr($url, 3); 86 | } 87 | 88 | $parts = array(); 89 | foreach (explode('/', $host.$path.$url) as $part) { 90 | if ('..' === $part && count($parts) && '..' !== end($parts)) { 91 | array_pop($parts); 92 | } else { 93 | $parts[] = $part; 94 | } 95 | } 96 | 97 | return str_replace($matches['url'], implode('/', $parts), $matches[0]); 98 | }); 99 | 100 | $asset->setContent($content); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Assetic/Filter/CssImportFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class CssImportFilter extends BaseCssFilter implements DependencyExtractorInterface 25 | { 26 | private $importFilter; 27 | 28 | /** 29 | * Constructor. 30 | * 31 | * @param FilterInterface $importFilter Filter for each imported asset 32 | */ 33 | public function __construct(FilterInterface $importFilter = null) 34 | { 35 | $this->importFilter = $importFilter ?: new CssRewriteFilter(); 36 | } 37 | 38 | public function filterLoad(AssetInterface $asset) 39 | { 40 | $importFilter = $this->importFilter; 41 | $sourceRoot = $asset->getSourceRoot(); 42 | $sourcePath = $asset->getSourcePath(); 43 | 44 | $callback = function ($matches) use ($importFilter, $sourceRoot, $sourcePath) { 45 | if (!$matches['url'] || null === $sourceRoot) { 46 | return $matches[0]; 47 | } 48 | 49 | $importRoot = $sourceRoot; 50 | 51 | if (false !== strpos($matches['url'], '://')) { 52 | // absolute 53 | list($importScheme, $tmp) = explode('://', $matches['url'], 2); 54 | list($importHost, $importPath) = explode('/', $tmp, 2); 55 | $importRoot = $importScheme.'://'.$importHost; 56 | } elseif (0 === strpos($matches['url'], '//')) { 57 | // protocol-relative 58 | list($importHost, $importPath) = explode('/', substr($matches['url'], 2), 2); 59 | $importRoot = '//'.$importHost; 60 | } elseif ('/' == $matches['url'][0]) { 61 | // root-relative 62 | $importPath = substr($matches['url'], 1); 63 | } elseif (null !== $sourcePath) { 64 | // document-relative 65 | $importPath = $matches['url']; 66 | if ('.' != $sourceDir = dirname($sourcePath)) { 67 | $importPath = $sourceDir.'/'.$importPath; 68 | } 69 | } else { 70 | return $matches[0]; 71 | } 72 | 73 | $importSource = $importRoot.'/'.$importPath; 74 | if (false !== strpos($importSource, '://') || 0 === strpos($importSource, '//')) { 75 | $import = new HttpAsset($importSource, array($importFilter), true); 76 | } elseif ('css' != pathinfo($importPath, PATHINFO_EXTENSION) || !file_exists($importSource)) { 77 | // ignore non-css and non-existant imports 78 | return $matches[0]; 79 | } else { 80 | $import = new FileAsset($importSource, array($importFilter), $importRoot, $importPath); 81 | } 82 | 83 | $import->setTargetPath($sourcePath); 84 | 85 | return $import->dump(); 86 | }; 87 | 88 | $content = $asset->getContent(); 89 | $lastHash = md5($content); 90 | 91 | do { 92 | $content = $this->filterImports($content, $callback); 93 | $hash = md5($content); 94 | } while ($lastHash != $hash && $lastHash = $hash); 95 | 96 | $asset->setContent($content); 97 | } 98 | 99 | public function filterDump(AssetInterface $asset) 100 | { 101 | } 102 | 103 | public function getChildren(AssetFactory $factory, $content, $loadPath = null) 104 | { 105 | // todo 106 | return array(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Assetic/Filter/SassphpFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class SassphpFilter implements DependencyExtractorInterface 25 | { 26 | private $includePaths = array(); 27 | private $outputStyle; 28 | 29 | public function filterLoad(AssetInterface $asset) 30 | { 31 | $sass = new \Sass(); 32 | 33 | $includePaths = array_merge( 34 | array($asset->getSourceDirectory()), 35 | $this->includePaths 36 | ); 37 | $sass->setIncludePath(implode(':', $includePaths)); 38 | 39 | if ($this->outputStyle) { 40 | $sass->setStyle($this->outputStyle); 41 | } 42 | 43 | $css = $sass->compile($asset->getContent()); 44 | 45 | $asset->setContent($css); 46 | } 47 | 48 | public function filterDump(AssetInterface $asset) 49 | { 50 | } 51 | 52 | public function setOutputStyle($outputStyle) 53 | { 54 | $this->outputStyle = $outputStyle; 55 | } 56 | 57 | public function setIncludePaths(array $paths) 58 | { 59 | $this->includePaths = $paths; 60 | } 61 | 62 | public function addIncludePath($path) 63 | { 64 | $this->includePaths[] = $path; 65 | } 66 | 67 | public function getChildren(AssetFactory $factory, $content, $loadPath = null) 68 | { 69 | $children = array(); 70 | 71 | $includePaths = $this->includePaths; 72 | if (null !== $loadPath && !in_array($loadPath, $includePaths)) { 73 | array_unshift($includePaths, $loadPath); 74 | } 75 | 76 | if (empty($includePaths)) { 77 | return $children; 78 | } 79 | 80 | foreach (CssUtils::extractImports($content) as $reference) { 81 | if ('.css' === substr($reference, -4)) { 82 | continue; 83 | } 84 | 85 | // the reference may or may not have an extension or be a partial 86 | if (pathinfo($reference, PATHINFO_EXTENSION)) { 87 | $needles = array( 88 | $reference, 89 | $this->partialize($reference), 90 | ); 91 | } else { 92 | $needles = array( 93 | $reference . '.scss', 94 | $this->partialize($reference) . '.scss', 95 | ); 96 | } 97 | 98 | foreach ($includePaths as $includePath) { 99 | foreach ($needles as $needle) { 100 | if (file_exists($file = $includePath . '/' . $needle)) { 101 | $child = $factory->createAsset($file, array(), array('root' => $includePath)); 102 | $children[] = $child; 103 | $child->load(); 104 | $children = array_merge( 105 | $children, 106 | $this->getChildren($factory, $child->getContent(), $includePath) 107 | ); 108 | } 109 | } 110 | } 111 | } 112 | 113 | return $children; 114 | } 115 | 116 | private function partialize($reference) 117 | { 118 | $parts = pathinfo($reference); 119 | 120 | if ('.' === $parts['dirname']) { 121 | $partial = '_' . $parts['filename']; 122 | } else { 123 | $partial = $parts['dirname'] . DIRECTORY_SEPARATOR . '_' . $parts['filename']; 124 | } 125 | 126 | if (isset($parts['extension'])) { 127 | $partial .= '.' . $parts['extension']; 128 | } 129 | 130 | return $partial; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Assetic/Asset/AssetReference.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class AssetReference implements AssetInterface 23 | { 24 | private $am; 25 | private $name; 26 | private $filters = array(); 27 | private $clone = false; 28 | private $asset; 29 | 30 | public function __construct(AssetManager $am, $name) 31 | { 32 | $this->am = $am; 33 | $this->name = $name; 34 | } 35 | 36 | public function __clone() 37 | { 38 | $this->clone = true; 39 | 40 | if ($this->asset) { 41 | $this->asset = clone $this->asset; 42 | } 43 | } 44 | 45 | public function ensureFilter(FilterInterface $filter) 46 | { 47 | $this->filters[] = $filter; 48 | } 49 | 50 | public function getFilters() 51 | { 52 | $this->flushFilters(); 53 | 54 | return $this->callAsset(__FUNCTION__); 55 | } 56 | 57 | public function clearFilters() 58 | { 59 | $this->filters = array(); 60 | $this->callAsset(__FUNCTION__); 61 | } 62 | 63 | public function load(FilterInterface $additionalFilter = null) 64 | { 65 | $this->flushFilters(); 66 | 67 | return $this->callAsset(__FUNCTION__, array($additionalFilter)); 68 | } 69 | 70 | public function dump(FilterInterface $additionalFilter = null) 71 | { 72 | $this->flushFilters(); 73 | 74 | return $this->callAsset(__FUNCTION__, array($additionalFilter)); 75 | } 76 | 77 | public function getContent() 78 | { 79 | return $this->callAsset(__FUNCTION__); 80 | } 81 | 82 | public function setContent($content) 83 | { 84 | $this->callAsset(__FUNCTION__, array($content)); 85 | } 86 | 87 | public function getSourceRoot() 88 | { 89 | return $this->callAsset(__FUNCTION__); 90 | } 91 | 92 | public function getSourcePath() 93 | { 94 | return $this->callAsset(__FUNCTION__); 95 | } 96 | 97 | public function getSourceDirectory() 98 | { 99 | return $this->callAsset(__FUNCTION__); 100 | } 101 | 102 | public function getTargetPath() 103 | { 104 | return $this->callAsset(__FUNCTION__); 105 | } 106 | 107 | public function setTargetPath($targetPath) 108 | { 109 | $this->callAsset(__FUNCTION__, array($targetPath)); 110 | } 111 | 112 | public function getLastModified() 113 | { 114 | return $this->callAsset(__FUNCTION__); 115 | } 116 | 117 | public function getVars() 118 | { 119 | return $this->callAsset(__FUNCTION__); 120 | } 121 | 122 | public function getValues() 123 | { 124 | return $this->callAsset(__FUNCTION__); 125 | } 126 | 127 | public function setValues(array $values) 128 | { 129 | $this->callAsset(__FUNCTION__, array($values)); 130 | } 131 | 132 | // private 133 | 134 | private function callAsset($method, $arguments = array()) 135 | { 136 | $asset = $this->resolve(); 137 | 138 | return call_user_func_array(array($asset, $method), $arguments); 139 | } 140 | 141 | private function flushFilters() 142 | { 143 | $asset = $this->resolve(); 144 | 145 | while ($filter = array_shift($this->filters)) { 146 | $asset->ensureFilter($filter); 147 | } 148 | } 149 | 150 | private function resolve() 151 | { 152 | if ($this->asset) { 153 | return $this->asset; 154 | } 155 | 156 | $asset = $this->am->get($this->name); 157 | 158 | if ($this->clone) { 159 | $asset = $this->asset = clone $asset; 160 | } 161 | 162 | return $asset; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/Assetic/Filter/UglifyJs2Filter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class UglifyJs2Filter extends BaseNodeFilter 25 | { 26 | private $uglifyjsBin; 27 | private $nodeBin; 28 | private $compress; 29 | private $beautify; 30 | private $mangle; 31 | private $screwIe8; 32 | private $comments; 33 | private $wrap; 34 | private $defines; 35 | 36 | public function __construct($uglifyjsBin = '/usr/bin/uglifyjs', $nodeBin = null) 37 | { 38 | $this->uglifyjsBin = $uglifyjsBin; 39 | $this->nodeBin = $nodeBin; 40 | } 41 | 42 | public function setCompress($compress) 43 | { 44 | $this->compress = $compress; 45 | } 46 | 47 | public function setBeautify($beautify) 48 | { 49 | $this->beautify = $beautify; 50 | } 51 | 52 | public function setMangle($mangle) 53 | { 54 | $this->mangle = $mangle; 55 | } 56 | 57 | public function setScrewIe8($screwIe8) 58 | { 59 | $this->screwIe8 = $screwIe8; 60 | } 61 | 62 | public function setComments($comments) 63 | { 64 | $this->comments = $comments; 65 | } 66 | 67 | public function setWrap($wrap) 68 | { 69 | $this->wrap = $wrap; 70 | } 71 | 72 | public function setDefines(array $defines) 73 | { 74 | $this->defines = $defines; 75 | } 76 | 77 | public function filterLoad(AssetInterface $asset) 78 | { 79 | } 80 | 81 | public function filterDump(AssetInterface $asset) 82 | { 83 | $pb = $this->createProcessBuilder( 84 | $this->nodeBin 85 | ? array($this->nodeBin, $this->uglifyjsBin) 86 | : array($this->uglifyjsBin) 87 | ); 88 | 89 | if ($this->compress) { 90 | $pb->add('--compress'); 91 | 92 | if (is_string($this->compress) && !empty($this->compress)) { 93 | $pb->add($this->compress); 94 | } 95 | } 96 | 97 | if ($this->beautify) { 98 | $pb->add('--beautify'); 99 | } 100 | 101 | if ($this->mangle) { 102 | $pb->add('--mangle'); 103 | } 104 | 105 | if ($this->screwIe8) { 106 | $pb->add('--screw-ie8'); 107 | } 108 | 109 | if ($this->comments) { 110 | $pb->add('--comments')->add(true === $this->comments ? 'all' : $this->comments); 111 | } 112 | 113 | if ($this->wrap) { 114 | $pb->add('--wrap')->add($this->wrap); 115 | } 116 | 117 | if ($this->defines) { 118 | $pb->add('--define')->add(implode(',', $this->defines)); 119 | } 120 | 121 | // input and output files 122 | $input = FilesystemUtils::createTemporaryFile('uglifyjs2_in'); 123 | $output = FilesystemUtils::createTemporaryFile('uglifyjs2_out'); 124 | 125 | file_put_contents($input, $asset->getContent()); 126 | $pb->add('-o')->add($output)->add($input); 127 | 128 | $proc = $pb->getProcess(); 129 | $code = $proc->run(); 130 | unlink($input); 131 | 132 | if (0 !== $code) { 133 | if (file_exists($output)) { 134 | unlink($output); 135 | } 136 | 137 | if (127 === $code) { 138 | throw new \RuntimeException('Path to node executable could not be resolved.'); 139 | } 140 | 141 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 142 | } 143 | 144 | if (!file_exists($output)) { 145 | throw new \RuntimeException('Error creating output file.'); 146 | } 147 | 148 | $asset->setContent(file_get_contents($output)); 149 | 150 | unlink($output); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Assetic/Filter/CssEmbedFilter.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | class CssEmbedFilter extends BaseProcessFilter implements DependencyExtractorInterface 26 | { 27 | private $jarPath; 28 | private $javaPath; 29 | private $charset; 30 | private $mhtml; // Enable MHTML mode. 31 | private $mhtmlRoot; // Use as the MHTML root for the file. 32 | private $root; // Prepends to all relative URLs. 33 | private $skipMissing; // Don't throw an error for missing image files. 34 | private $maxUriLength; // Maximum length for a data URI. Defaults to 32768. 35 | private $maxImageSize; // Maximum image size (in bytes) to convert. 36 | 37 | public function __construct($jarPath, $javaPath = '/usr/bin/java') 38 | { 39 | $this->jarPath = $jarPath; 40 | $this->javaPath = $javaPath; 41 | } 42 | 43 | public function setCharset($charset) 44 | { 45 | $this->charset = $charset; 46 | } 47 | 48 | public function setMhtml($mhtml) 49 | { 50 | $this->mhtml = $mhtml; 51 | } 52 | 53 | public function setMhtmlRoot($mhtmlRoot) 54 | { 55 | $this->mhtmlRoot = $mhtmlRoot; 56 | } 57 | 58 | public function setRoot($root) 59 | { 60 | $this->root = $root; 61 | } 62 | 63 | public function setSkipMissing($skipMissing) 64 | { 65 | $this->skipMissing = $skipMissing; 66 | } 67 | 68 | public function setMaxUriLength($maxUriLength) 69 | { 70 | $this->maxUriLength = $maxUriLength; 71 | } 72 | 73 | public function setMaxImageSize($maxImageSize) 74 | { 75 | $this->maxImageSize = $maxImageSize; 76 | } 77 | 78 | public function filterLoad(AssetInterface $asset) 79 | { 80 | } 81 | 82 | public function filterDump(AssetInterface $asset) 83 | { 84 | $pb = $this->createProcessBuilder(array( 85 | $this->javaPath, 86 | '-jar', 87 | $this->jarPath, 88 | )); 89 | 90 | if (null !== $this->charset) { 91 | $pb->add('--charset')->add($this->charset); 92 | } 93 | 94 | if ($this->mhtml) { 95 | $pb->add('--mhtml'); 96 | } 97 | 98 | if (null !== $this->mhtmlRoot) { 99 | $pb->add('--mhtmlroot')->add($this->mhtmlRoot); 100 | } 101 | 102 | // automatically define root if not already defined 103 | if (null === $this->root) { 104 | if ($dir = $asset->getSourceDirectory()) { 105 | $pb->add('--root')->add($dir); 106 | } 107 | } else { 108 | $pb->add('--root')->add($this->root); 109 | } 110 | 111 | if ($this->skipMissing) { 112 | $pb->add('--skip-missing'); 113 | } 114 | 115 | if (null !== $this->maxUriLength) { 116 | $pb->add('--max-uri-length')->add($this->maxUriLength); 117 | } 118 | 119 | if (null !== $this->maxImageSize) { 120 | $pb->add('--max-image-size')->add($this->maxImageSize); 121 | } 122 | 123 | // input 124 | $pb->add($input = FilesystemUtils::createTemporaryFile('cssembed')); 125 | file_put_contents($input, $asset->getContent()); 126 | 127 | $proc = $pb->getProcess(); 128 | $code = $proc->run(); 129 | unlink($input); 130 | 131 | if (0 !== $code) { 132 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 133 | } 134 | 135 | $asset->setContent($proc->getOutput()); 136 | } 137 | 138 | public function getChildren(AssetFactory $factory, $content, $loadPath = null) 139 | { 140 | // todo 141 | return array(); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Assetic/Filter/SprocketsFilter.php: -------------------------------------------------------------------------------- 1 | 28 | */ 29 | class SprocketsFilter extends BaseProcessFilter implements DependencyExtractorInterface 30 | { 31 | private $sprocketsLib; 32 | private $rubyBin; 33 | private $includeDirs; 34 | private $assetRoot; 35 | 36 | /** 37 | * Constructor. 38 | * 39 | * @param string $sprocketsLib Path to the Sprockets lib/ directory 40 | * @param string $rubyBin Path to the ruby binary 41 | */ 42 | public function __construct($sprocketsLib = null, $rubyBin = '/usr/bin/ruby') 43 | { 44 | $this->sprocketsLib = $sprocketsLib; 45 | $this->rubyBin = $rubyBin; 46 | $this->includeDirs = array(); 47 | } 48 | 49 | public function addIncludeDir($directory) 50 | { 51 | $this->includeDirs[] = $directory; 52 | } 53 | 54 | public function setAssetRoot($assetRoot) 55 | { 56 | $this->assetRoot = $assetRoot; 57 | } 58 | 59 | /** 60 | * Hack around a bit, get the job done. 61 | */ 62 | public function filterLoad(AssetInterface $asset) 63 | { 64 | static $format = <<<'EOF' 65 | #!/usr/bin/env ruby 66 | 67 | require %s 68 | %s 69 | options = { :load_path => [], 70 | :source_files => [%s], 71 | :expand_paths => false } 72 | 73 | %ssecretary = Sprockets::Secretary.new(options) 74 | secretary.install_assets if options[:asset_root] 75 | print secretary.concatenation 76 | 77 | EOF; 78 | 79 | $more = ''; 80 | 81 | foreach ($this->includeDirs as $directory) { 82 | $more .= 'options[:load_path] << '.var_export($directory, true)."\n"; 83 | } 84 | 85 | if (null !== $this->assetRoot) { 86 | $more .= 'options[:asset_root] = '.var_export($this->assetRoot, true)."\n"; 87 | } 88 | 89 | if ($more) { 90 | $more .= "\n"; 91 | } 92 | 93 | $tmpAsset = FilesystemUtils::createTemporaryFile('sprockets_asset'); 94 | file_put_contents($tmpAsset, $asset->getContent()); 95 | 96 | $input = FilesystemUtils::createTemporaryFile('sprockets_in'); 97 | file_put_contents($input, sprintf($format, 98 | $this->sprocketsLib 99 | ? sprintf('File.join(%s, \'sprockets\')', var_export($this->sprocketsLib, true)) 100 | : '\'sprockets\'', 101 | $this->getHack($asset), 102 | var_export($tmpAsset, true), 103 | $more 104 | )); 105 | 106 | $pb = $this->createProcessBuilder(array( 107 | $this->rubyBin, 108 | $input, 109 | )); 110 | 111 | $proc = $pb->getProcess(); 112 | $code = $proc->run(); 113 | unlink($tmpAsset); 114 | unlink($input); 115 | 116 | if (0 !== $code) { 117 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 118 | } 119 | 120 | $asset->setContent($proc->getOutput()); 121 | } 122 | 123 | public function filterDump(AssetInterface $asset) 124 | { 125 | } 126 | 127 | public function getChildren(AssetFactory $factory, $content, $loadPath = null) 128 | { 129 | // todo 130 | return array(); 131 | } 132 | 133 | private function getHack(AssetInterface $asset) 134 | { 135 | static $format = <<<'EOF' 136 | 137 | module Sprockets 138 | class Preprocessor 139 | protected 140 | def pathname_for_relative_require_from(source_line) 141 | Sprockets::Pathname.new(@environment, File.join(%s, location_from(source_line))) 142 | end 143 | end 144 | end 145 | 146 | EOF; 147 | 148 | if ($dir = $asset->getSourceDirectory()) { 149 | return sprintf($format, var_export($dir, true)); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Assetic/Filter/GssFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class GssFilter extends BaseProcessFilter 25 | { 26 | private $jarPath; 27 | private $javaPath; 28 | private $allowUnrecognizedFunctions; 29 | private $allowedNonStandardFunctions; 30 | private $copyrightNotice; 31 | private $define; 32 | private $gssFunctionMapProvider; 33 | private $inputOrientation; 34 | private $outputOrientation; 35 | private $prettyPrint; 36 | 37 | public function __construct($jarPath, $javaPath = '/usr/bin/java') 38 | { 39 | $this->jarPath = $jarPath; 40 | $this->javaPath = $javaPath; 41 | } 42 | 43 | public function setAllowUnrecognizedFunctions($allowUnrecognizedFunctions) 44 | { 45 | $this->allowUnrecognizedFunctions = $allowUnrecognizedFunctions; 46 | } 47 | 48 | public function setAllowedNonStandardFunctions($allowNonStandardFunctions) 49 | { 50 | $this->allowedNonStandardFunctions = $allowNonStandardFunctions; 51 | } 52 | 53 | public function setCopyrightNotice($copyrightNotice) 54 | { 55 | $this->copyrightNotice = $copyrightNotice; 56 | } 57 | 58 | public function setDefine($define) 59 | { 60 | $this->define = $define; 61 | } 62 | 63 | public function setGssFunctionMapProvider($gssFunctionMapProvider) 64 | { 65 | $this->gssFunctionMapProvider = $gssFunctionMapProvider; 66 | } 67 | 68 | public function setInputOrientation($inputOrientation) 69 | { 70 | $this->inputOrientation = $inputOrientation; 71 | } 72 | 73 | public function setOutputOrientation($outputOrientation) 74 | { 75 | $this->outputOrientation = $outputOrientation; 76 | } 77 | 78 | public function setPrettyPrint($prettyPrint) 79 | { 80 | $this->prettyPrint = $prettyPrint; 81 | } 82 | 83 | public function filterLoad(AssetInterface $asset) 84 | { 85 | $cleanup = array(); 86 | 87 | $pb = $this->createProcessBuilder(array( 88 | $this->javaPath, 89 | '-jar', 90 | $this->jarPath, 91 | )); 92 | 93 | if (null !== $this->allowUnrecognizedFunctions) { 94 | $pb->add('--allow-unrecognized-functions'); 95 | } 96 | 97 | if (null !== $this->allowedNonStandardFunctions) { 98 | $pb->add('--allowed_non_standard_functions')->add($this->allowedNonStandardFunctions); 99 | } 100 | 101 | if (null !== $this->copyrightNotice) { 102 | $pb->add('--copyright-notice')->add($this->copyrightNotice); 103 | } 104 | 105 | if (null !== $this->define) { 106 | $pb->add('--define')->add($this->define); 107 | } 108 | 109 | if (null !== $this->gssFunctionMapProvider) { 110 | $pb->add('--gss-function-map-provider')->add($this->gssFunctionMapProvider); 111 | } 112 | 113 | if (null !== $this->inputOrientation) { 114 | $pb->add('--input-orientation')->add($this->inputOrientation); 115 | } 116 | 117 | if (null !== $this->outputOrientation) { 118 | $pb->add('--output-orientation')->add($this->outputOrientation); 119 | } 120 | 121 | if (null !== $this->prettyPrint) { 122 | $pb->add('--pretty-print'); 123 | } 124 | 125 | $pb->add($cleanup[] = $input = FilesystemUtils::createTemporaryFile('gss')); 126 | file_put_contents($input, $asset->getContent()); 127 | 128 | $proc = $pb->getProcess(); 129 | $code = $proc->run(); 130 | array_map('unlink', $cleanup); 131 | 132 | if (0 !== $code) { 133 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 134 | } 135 | 136 | $asset->setContent($proc->getOutput()); 137 | } 138 | 139 | public function filterDump(AssetInterface $asset) 140 | { 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Assetic/Filter/ScssphpFilter.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | class ScssphpFilter implements DependencyExtractorInterface 29 | { 30 | private $compass = false; 31 | private $importPaths = array(); 32 | private $customFunctions = array(); 33 | private $formatter; 34 | private $variables = array(); 35 | 36 | public function enableCompass($enable = true) 37 | { 38 | $this->compass = (Boolean) $enable; 39 | } 40 | 41 | public function isCompassEnabled() 42 | { 43 | return $this->compass; 44 | } 45 | 46 | public function setFormatter($formatter) 47 | { 48 | $legacyFormatters = array( 49 | 'scss_formatter' => 'Leafo\ScssPhp\Formatter\Expanded', 50 | 'scss_formatter_nested' => 'Leafo\ScssPhp\Formatter\Nested', 51 | 'scss_formatter_compressed' => 'Leafo\ScssPhp\Formatter\Compressed', 52 | 'scss_formatter_crunched' => 'Leafo\ScssPhp\Formatter\Crunched', 53 | ); 54 | 55 | if (isset($legacyFormatters[$formatter])) { 56 | @trigger_error(sprintf('The scssphp formatter `%s` is deprecated. Use `%s` instead.', $formatter, $legacyFormatters[$formatter]), E_USER_DEPRECATED); 57 | 58 | $formatter = $legacyFormatters[$formatter]; 59 | } 60 | 61 | $this->formatter = $formatter; 62 | } 63 | 64 | public function setVariables(array $variables) 65 | { 66 | $this->variables = $variables; 67 | } 68 | 69 | public function addVariable($variable) 70 | { 71 | $this->variables[] = $variable; 72 | } 73 | 74 | public function setImportPaths(array $paths) 75 | { 76 | $this->importPaths = $paths; 77 | } 78 | 79 | public function addImportPath($path) 80 | { 81 | $this->importPaths[] = $path; 82 | } 83 | 84 | public function registerFunction($name, $callable) 85 | { 86 | $this->customFunctions[$name] = $callable; 87 | } 88 | 89 | public function filterLoad(AssetInterface $asset) 90 | { 91 | $sc = new Compiler(); 92 | 93 | if ($this->compass) { 94 | new \scss_compass($sc); 95 | } 96 | 97 | if ($dir = $asset->getSourceDirectory()) { 98 | $sc->addImportPath($dir); 99 | } 100 | 101 | foreach ($this->importPaths as $path) { 102 | $sc->addImportPath($path); 103 | } 104 | 105 | foreach ($this->customFunctions as $name => $callable) { 106 | $sc->registerFunction($name, $callable); 107 | } 108 | 109 | if ($this->formatter) { 110 | $sc->setFormatter($this->formatter); 111 | } 112 | 113 | if (!empty($this->variables)) { 114 | $sc->setVariables($this->variables); 115 | } 116 | 117 | $asset->setContent($sc->compile($asset->getContent())); 118 | } 119 | 120 | public function filterDump(AssetInterface $asset) 121 | { 122 | } 123 | 124 | public function getChildren(AssetFactory $factory, $content, $loadPath = null) 125 | { 126 | $sc = new Compiler(); 127 | if ($loadPath !== null) { 128 | $sc->addImportPath($loadPath); 129 | } 130 | 131 | foreach ($this->importPaths as $path) { 132 | $sc->addImportPath($path); 133 | } 134 | 135 | $children = array(); 136 | foreach (CssUtils::extractImports($content) as $match) { 137 | $file = $sc->findImport($match); 138 | if ($file) { 139 | $children[] = $child = $factory->createAsset($file, array(), array('root' => $loadPath)); 140 | $child->load(); 141 | $children = array_merge($children, $this->getChildren($factory, $child->getContent(), $loadPath)); 142 | } 143 | } 144 | 145 | return $children; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Assetic/Filter/UglifyJsFilter.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class UglifyJsFilter extends BaseNodeFilter 25 | { 26 | private $uglifyjsBin; 27 | private $nodeBin; 28 | 29 | private $noCopyright; 30 | private $beautify; 31 | private $unsafe; 32 | private $mangle; 33 | private $defines; 34 | 35 | /** 36 | * @param string $uglifyjsBin Absolute path to the uglifyjs executable 37 | * @param string $nodeBin Absolute path to the folder containg node.js executable 38 | */ 39 | public function __construct($uglifyjsBin = '/usr/bin/uglifyjs', $nodeBin = null) 40 | { 41 | $this->uglifyjsBin = $uglifyjsBin; 42 | $this->nodeBin = $nodeBin; 43 | } 44 | 45 | /** 46 | * Removes the first block of comments as well 47 | * @param bool $noCopyright True to enable 48 | */ 49 | public function setNoCopyright($noCopyright) 50 | { 51 | $this->noCopyright = $noCopyright; 52 | } 53 | 54 | /** 55 | * Output indented code 56 | * @param bool $beautify True to enable 57 | */ 58 | public function setBeautify($beautify) 59 | { 60 | $this->beautify = $beautify; 61 | } 62 | 63 | /** 64 | * Enable additional optimizations that are known to be unsafe in some situations. 65 | * @param bool $unsafe True to enable 66 | */ 67 | public function setUnsafe($unsafe) 68 | { 69 | $this->unsafe = $unsafe; 70 | } 71 | 72 | /** 73 | * Safely mangle variable and function names for greater file compress. 74 | * @param bool $mangle True to enable 75 | */ 76 | public function setMangle($mangle) 77 | { 78 | $this->mangle = $mangle; 79 | } 80 | 81 | public function setDefines(array $defines) 82 | { 83 | $this->defines = $defines; 84 | } 85 | 86 | /** 87 | * @see Assetic\Filter\FilterInterface::filterLoad() 88 | */ 89 | public function filterLoad(AssetInterface $asset) 90 | { 91 | } 92 | 93 | /** 94 | * Run the asset through UglifyJs 95 | * 96 | * @see Assetic\Filter\FilterInterface::filterDump() 97 | */ 98 | public function filterDump(AssetInterface $asset) 99 | { 100 | $pb = $this->createProcessBuilder( 101 | $this->nodeBin 102 | ? array($this->nodeBin, $this->uglifyjsBin) 103 | : array($this->uglifyjsBin) 104 | ); 105 | 106 | if ($this->noCopyright) { 107 | $pb->add('--no-copyright'); 108 | } 109 | 110 | if ($this->beautify) { 111 | $pb->add('--beautify'); 112 | } 113 | 114 | if ($this->unsafe) { 115 | $pb->add('--unsafe'); 116 | } 117 | 118 | if (false === $this->mangle) { 119 | $pb->add('--no-mangle'); 120 | } 121 | 122 | if ($this->defines) { 123 | foreach ($this->defines as $define) { 124 | $pb->add('-d')->add($define); 125 | } 126 | } 127 | 128 | // input and output files 129 | $input = FilesystemUtils::createTemporaryFile('uglifyjs_in'); 130 | $output = FilesystemUtils::createTemporaryFile('uglifyjs_out'); 131 | 132 | file_put_contents($input, $asset->getContent()); 133 | $pb->add('-o')->add($output)->add($input); 134 | 135 | $proc = $pb->getProcess(); 136 | $code = $proc->run(); 137 | unlink($input); 138 | 139 | if (0 !== $code) { 140 | if (file_exists($output)) { 141 | unlink($output); 142 | } 143 | 144 | if (127 === $code) { 145 | throw new \RuntimeException('Path to node executable could not be resolved.'); 146 | } 147 | 148 | throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 149 | } 150 | 151 | if (!file_exists($output)) { 152 | throw new \RuntimeException('Error creating output file.'); 153 | } 154 | 155 | $uglifiedJs = file_get_contents($output); 156 | unlink($output); 157 | 158 | $asset->setContent($uglifiedJs); 159 | } 160 | } 161 | --------------------------------------------------------------------------------