1<?php 2 3/* 4 * This file is part of the Assetic package, an OpenSky project. 5 * 6 * (c) 2010-2014 OpenSky Project Inc 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Assetic\Filter; 13 14use Assetic\Asset\AssetInterface; 15use Assetic\Exception\FilterException; 16 17/** 18 * CleanCss filter. 19 * 20 * @link https://github.com/jakubpawlowicz/clean-css 21 * @author Jakub Pawlowicz <http://JakubPawlowicz.com> 22 */ 23class CleanCssFilter extends BaseNodeFilter 24{ 25 private $cleanCssBin; 26 private $nodeBin; 27 28 private $keepLineBreaks; 29 private $compatibility; 30 private $debug; 31 private $rootPath; 32 private $skipImport = true; 33 private $timeout; 34 private $semanticMerging; 35 private $roundingPrecision; 36 private $removeSpecialComments; 37 private $onlyKeepFirstSpecialComment; 38 private $skipAdvanced; 39 private $skipAggresiveMerging; 40 private $skipImportFrom; 41 private $mediaMerging; 42 private $skipRebase; 43 private $skipRestructuring; 44 private $skipShorthandCompacting; 45 private $sourceMap; 46 private $sourceMapInlineSources; 47 48 49 /** 50 * @param string $cleanCssBin Absolute path to the cleancss executable 51 * @param string $nodeBin Absolute path to the folder containg node.js executable 52 */ 53 public function __construct($cleanCssBin = '/usr/bin/cleancss', $nodeBin = null) 54 { 55 $this->cleanCssBin = $cleanCssBin; 56 $this->nodeBin = $nodeBin; 57 } 58 59 /** 60 * Keep line breaks 61 * @param bool $keepLineBreaks True to enable 62 */ 63 public function setKeepLineBreaks($keepLineBreaks) 64 { 65 $this->keepLineBreaks = $keepLineBreaks; 66 } 67 68 /** 69 * Remove all special comments 70 * @param bool $removeSpecialComments True to enable 71 */ // i.e. /*! comment */ 72 public function setRemoveSpecialComments($removeSpecialComments) 73 { 74 $this->removeSpecialComments = $removeSpecialComments; 75 } 76 77 /** 78 * Remove all special comments except the first one 79 * @param bool $onlyKeepFirstSpecialComment True to enable 80 */ 81 public function setOnlyKeepFirstSpecialComment($onlyKeepFirstSpecialComment) 82 { 83 $this->onlyKeepFirstSpecialComment = $onlyKeepFirstSpecialComment; 84 } 85 /** 86 * Enables unsafe mode by assuming BEM-like semantic stylesheets (warning, this may break your styling!) 87 * @param bool $semanticMerging True to enable 88 */ 89 public function setSemanticMerging($semanticMerging) 90 { 91 $this->semanticMerging = $semanticMerging; 92 } 93 94 /** 95 * A root path to which resolve absolute @import rules 96 * @param string $rootPath 97 */ 98 public function setRootPath($rootPath) 99 { 100 $this->rootPath = $rootPath; 101 } 102 103 /** 104 * Disable @import processing 105 * @param bool $skipImport True to enable 106 */ 107 public function setSkipImport($skipImport) 108 { 109 $this->skipImport = $skipImport; 110 } 111 /** 112 * Per connection timeout when fetching remote @imports; defaults to 5 seconds 113 * @param int $timeout 114 */ 115 public function setTimeout($timeout) 116 { 117 $this->timeout = $timeout; 118 } 119 120 /** 121 * Disable URLs rebasing 122 * @param bool $skipRebase True to enable 123 */ 124 public function setSkipRebase($skipRebase) 125 { 126 $this->skipRebase = $skipRebase; 127 } 128 129 /** 130 * Disable restructuring optimizations 131 * @param bool $skipRestructuring True to enable 132 */ 133 public function setSkipRestructuring($skipRestructuring) 134 { 135 $this->skipRestructuring = $skipRestructuring; 136 } 137 138 /** 139 * Disable shorthand compacting 140 * @param bool $skipShorthandCompacting True to enable 141 */ 142 public function setSkipShorthandCompacting($skipShorthandCompacting) 143 { 144 $this->skipShorthandCompacting = $skipShorthandCompacting; 145 } 146 147 /** 148 * Enables building input's source map 149 * @param bool $sourceMap True to enable 150 */ 151 public function setSourceMap($sourceMap) 152 { 153 $this->sourceMap = $sourceMap; 154 } 155 156 /** 157 * Enables inlining sources inside source maps 158 * @param bool $sourceMapInlineSources True to enable 159 */ 160 public function setSourceMapInlineSources($sourceMapInlineSources) 161 { 162 $this->sourceMapInlineSources = $sourceMapInlineSources; 163 } 164 165 /** 166 * Disable advanced optimizations - selector & property merging, reduction, etc. 167 * @param bool $skipAdvanced True to enable 168 */ 169 public function setSkipAdvanced($skipAdvanced) 170 { 171 $this->skipAdvanced = $skipAdvanced; 172 } 173 174 /** 175 * Disable properties merging based on their order 176 * @param bool $skipAggresiveMerging True to enable 177 */ 178 public function setSkipAggresiveMerging($skipAggresiveMerging) 179 { 180 $this->skipAggresiveMerging = $skipAggresiveMerging; 181 } 182 183 /** 184 * Disable @import processing for specified rules 185 * @param string $skipImportFrom 186 */ 187 public function setSkipImportFrom($skipImportFrom) 188 { 189 $this->skipImportFrom = $skipImportFrom; 190 } 191 192 /** 193 * Disable @media merging 194 * @param bool $mediaMerging True to enable 195 */ 196 public function setMediaMerging($mediaMerging) 197 { 198 $this->mediaMerging = $mediaMerging; 199 } 200 201 /** 202 * Rounds to `N` decimal places. Defaults to 2. -1 disables rounding. 203 * @param int $roundingPrecision 204 */ 205 public function setRoundingPrecision($roundingPrecision) 206 { 207 $this->roundingPrecision = $roundingPrecision; 208 } 209 210 /** 211 * Force compatibility mode (see https://github.com/jakubpawlowicz/clean-css/blob/master/README.md#how-to-set-compatibility-mode for advanced examples) 212 * @param string $compatibility 213 */ 214 public function setCompatibility($compatibility) 215 { 216 $this->compatibility = $compatibility; 217 } 218 219 /** 220 * Shows debug information (minification time & compression efficiency) 221 * @param bool $debug True to enable 222 */ 223 public function setDebug($debug) 224 { 225 $this->debug = $debug; 226 } 227 228 229 /** 230 * @see Assetic\Filter\FilterInterface::filterLoad() 231 */ 232 public function filterLoad(AssetInterface $asset) 233 { 234 } 235 236 237 /** 238 * Run the asset through CleanCss 239 * 240 * @see Assetic\Filter\FilterInterface::filterDump() 241 */ 242 public function filterDump(AssetInterface $asset) 243 { 244 $pb = $this->createProcessBuilder($this->nodeBin 245 ? array($this->nodeBin, $this->cleanCssBin) 246 : array($this->cleanCssBin)); 247 248 if ($this->keepLineBreaks) { 249 $pb->add('--keep-line-breaks'); 250 } 251 252 if ($this->compatibility) { 253 $pb->add('--compatibility ' .$this->compatibility); 254 } 255 256 if ($this->debug) { 257 $pb->add('--debug'); 258 } 259 260 if ($this->rootPath) { 261 $pb->add('--root ' .$this->rootPath); 262 } 263 264 if ($this->skipImport) { 265 $pb->add('--skip-import'); 266 } 267 268 if ($this->timeout) { 269 $pb->add('--timeout ' .$this->timeout); 270 } 271 272 if ($this->roundingPrecision) { 273 $pb->add('--rounding-precision ' .$this->roundingPrecision); 274 } 275 276 if ($this->removeSpecialComments) { 277 $pb->add('--s0'); 278 } 279 280 if ($this->onlyKeepFirstSpecialComment) { 281 $pb->add('--s1'); 282 } 283 284 if ($this->semanticMerging) { 285 $pb->add('--semantic-merging'); 286 } 287 288 if ($this->skipAdvanced) { 289 $pb->add('--skip-advanced'); 290 } 291 292 if ($this->skipAggresiveMerging) { 293 $pb->add('--skip-aggressive-merging'); 294 } 295 296 if ($this->skipImportFrom) { 297 $pb->add('--skip-import-from ' .$this->skipImportFrom); 298 } 299 300 if ($this->mediaMerging) { 301 $pb->add('--skip-media-merging'); 302 } 303 304 if ($this->skipRebase) { 305 $pb->add('--skip-rebase'); 306 } 307 308 if ($this->skipRestructuring) { 309 $pb->add('--skip-restructuring'); 310 } 311 312 if ($this->skipShorthandCompacting) { 313 $pb->add('--skip-shorthand-compacting'); 314 } 315 316 if ($this->sourceMap) { 317 $pb->add('--source-map'); 318 } 319 320 if ($this->sourceMapInlineSources) { 321 $pb->add('--source-map-inline-sources'); 322 } 323 // input and output files 324 $input = tempnam(sys_get_temp_dir(), 'input'); 325 326 file_put_contents($input, $asset->getContent()); 327 $pb->add($input); 328 329 $proc = $pb->getProcess(); 330 $code = $proc->run(); 331 unlink($input); 332 333 if (127 === $code) { 334 throw new \RuntimeException('Path to node executable could not be resolved.'); 335 } 336 337 if (0 !== $code) { 338 throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 339 } 340 341 $asset->setContent($proc->getOutput()); 342 } 343} 344