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; 16use Assetic\Filter\Sass\BaseSassFilter; 17use Assetic\Util\FilesystemUtils; 18 19/** 20 * Loads Compass files. 21 * 22 * @link http://compass-style.org/ 23 * @author Maxime Thirouin <maxime.thirouin@gmail.com> 24 */ 25class CompassFilter extends BaseSassFilter 26{ 27 private $compassPath; 28 private $rubyPath; 29 private $scss; 30 31 // sass options 32 private $unixNewlines; 33 private $debugInfo; 34 private $cacheLocation; 35 private $noCache; 36 37 // compass options 38 private $force; 39 private $style; 40 private $quiet; 41 private $boring; 42 private $noLineComments; 43 private $imagesDir; 44 private $javascriptsDir; 45 private $fontsDir; 46 private $relativeAssets; 47 48 // compass configuration file options 49 private $plugins = array(); 50 private $httpPath; 51 private $httpImagesPath; 52 private $httpFontsPath; 53 private $httpGeneratedImagesPath; 54 private $generatedImagesPath; 55 private $httpJavascriptsPath; 56 private $homeEnv = true; 57 58 public function __construct($compassPath = '/usr/bin/compass', $rubyPath = null) 59 { 60 $this->compassPath = $compassPath; 61 $this->rubyPath = $rubyPath; 62 $this->cacheLocation = FilesystemUtils::getTemporaryDirectory(); 63 64 if ('cli' !== php_sapi_name()) { 65 $this->boring = true; 66 } 67 } 68 69 public function setScss($scss) 70 { 71 $this->scss = $scss; 72 } 73 74 // sass options setters 75 public function setUnixNewlines($unixNewlines) 76 { 77 $this->unixNewlines = $unixNewlines; 78 } 79 80 public function setDebugInfo($debugInfo) 81 { 82 $this->debugInfo = $debugInfo; 83 } 84 85 public function setCacheLocation($cacheLocation) 86 { 87 $this->cacheLocation = $cacheLocation; 88 } 89 90 public function setNoCache($noCache) 91 { 92 $this->noCache = $noCache; 93 } 94 95 // compass options setters 96 public function setForce($force) 97 { 98 $this->force = $force; 99 } 100 101 public function setStyle($style) 102 { 103 $this->style = $style; 104 } 105 106 public function setQuiet($quiet) 107 { 108 $this->quiet = $quiet; 109 } 110 111 public function setBoring($boring) 112 { 113 $this->boring = $boring; 114 } 115 116 public function setNoLineComments($noLineComments) 117 { 118 $this->noLineComments = $noLineComments; 119 } 120 121 public function setImagesDir($imagesDir) 122 { 123 $this->imagesDir = $imagesDir; 124 } 125 126 public function setJavascriptsDir($javascriptsDir) 127 { 128 $this->javascriptsDir = $javascriptsDir; 129 } 130 131 public function setFontsDir($fontsDir) 132 { 133 $this->fontsDir = $fontsDir; 134 } 135 136 // compass configuration file options setters 137 public function setPlugins(array $plugins) 138 { 139 $this->plugins = $plugins; 140 } 141 142 public function addPlugin($plugin) 143 { 144 $this->plugins[] = $plugin; 145 } 146 147 public function setHttpPath($httpPath) 148 { 149 $this->httpPath = $httpPath; 150 } 151 152 public function setHttpImagesPath($httpImagesPath) 153 { 154 $this->httpImagesPath = $httpImagesPath; 155 } 156 157 public function setHttpFontsPath($httpFontsPath) 158 { 159 $this->httpFontsPath = $httpFontsPath; 160 } 161 162 public function setHttpGeneratedImagesPath($httpGeneratedImagesPath) 163 { 164 $this->httpGeneratedImagesPath = $httpGeneratedImagesPath; 165 } 166 167 public function setGeneratedImagesPath($generatedImagesPath) 168 { 169 $this->generatedImagesPath = $generatedImagesPath; 170 } 171 172 public function setHttpJavascriptsPath($httpJavascriptsPath) 173 { 174 $this->httpJavascriptsPath = $httpJavascriptsPath; 175 } 176 177 public function setHomeEnv($homeEnv) 178 { 179 $this->homeEnv = $homeEnv; 180 } 181 182 public function setRelativeAssets($relativeAssets) 183 { 184 $this->relativeAssets = $relativeAssets; 185 } 186 187 public function filterLoad(AssetInterface $asset) 188 { 189 $loadPaths = $this->loadPaths; 190 if ($dir = $asset->getSourceDirectory()) { 191 $loadPaths[] = $dir; 192 } 193 194 $tempDir = $this->cacheLocation ? $this->cacheLocation : FilesystemUtils::getTemporaryDirectory(); 195 196 $compassProcessArgs = array( 197 $this->compassPath, 198 'compile', 199 $tempDir, 200 ); 201 if (null !== $this->rubyPath) { 202 $compassProcessArgs = array_merge(explode(' ', $this->rubyPath), $compassProcessArgs); 203 } 204 205 $pb = $this->createProcessBuilder($compassProcessArgs); 206 207 if ($this->force) { 208 $pb->add('--force'); 209 } 210 211 if ($this->style) { 212 $pb->add('--output-style')->add($this->style); 213 } 214 215 if ($this->quiet) { 216 $pb->add('--quiet'); 217 } 218 219 if ($this->boring) { 220 $pb->add('--boring'); 221 } 222 223 if ($this->noLineComments) { 224 $pb->add('--no-line-comments'); 225 } 226 227 // these three options are not passed into the config file 228 // because like this, compass adapts this to be xxx_dir or xxx_path 229 // whether it's an absolute path or not 230 if ($this->imagesDir) { 231 $pb->add('--images-dir')->add($this->imagesDir); 232 } 233 234 if ($this->relativeAssets) { 235 $pb->add('--relative-assets'); 236 } 237 238 if ($this->javascriptsDir) { 239 $pb->add('--javascripts-dir')->add($this->javascriptsDir); 240 } 241 242 if ($this->fontsDir) { 243 $pb->add('--fonts-dir')->add($this->fontsDir); 244 } 245 246 // options in config file 247 $optionsConfig = array(); 248 249 if (!empty($loadPaths)) { 250 $optionsConfig['additional_import_paths'] = $loadPaths; 251 } 252 253 if ($this->unixNewlines) { 254 $optionsConfig['sass_options']['unix_newlines'] = true; 255 } 256 257 if ($this->debugInfo) { 258 $optionsConfig['sass_options']['debug_info'] = true; 259 } 260 261 if ($this->cacheLocation) { 262 $optionsConfig['sass_options']['cache_location'] = $this->cacheLocation; 263 } 264 265 if ($this->noCache) { 266 $optionsConfig['sass_options']['no_cache'] = true; 267 } 268 269 if ($this->httpPath) { 270 $optionsConfig['http_path'] = $this->httpPath; 271 } 272 273 if ($this->httpImagesPath) { 274 $optionsConfig['http_images_path'] = $this->httpImagesPath; 275 } 276 277 if ($this->httpFontsPath) { 278 $optionsConfig['http_fonts_path'] = $this->httpFontsPath; 279 } 280 281 if ($this->httpGeneratedImagesPath) { 282 $optionsConfig['http_generated_images_path'] = $this->httpGeneratedImagesPath; 283 } 284 285 if ($this->generatedImagesPath) { 286 $optionsConfig['generated_images_path'] = $this->generatedImagesPath; 287 } 288 289 if ($this->httpJavascriptsPath) { 290 $optionsConfig['http_javascripts_path'] = $this->httpJavascriptsPath; 291 } 292 293 // options in configuration file 294 if (count($optionsConfig)) { 295 $config = array(); 296 foreach ($this->plugins as $plugin) { 297 $config[] = sprintf("require '%s'", addcslashes($plugin, '\\')); 298 } 299 foreach ($optionsConfig as $name => $value) { 300 if (!is_array($value)) { 301 $config[] = sprintf('%s = "%s"', $name, addcslashes($value, '\\')); 302 } elseif (!empty($value)) { 303 $config[] = sprintf('%s = %s', $name, $this->formatArrayToRuby($value)); 304 } 305 } 306 307 $configFile = tempnam($tempDir, 'assetic_compass'); 308 file_put_contents($configFile, implode("\n", $config)."\n"); 309 $pb->add('--config')->add($configFile); 310 } 311 312 $pb->add('--sass-dir')->add('')->add('--css-dir')->add(''); 313 314 // compass choose the type (sass or scss from the filename) 315 if (null !== $this->scss) { 316 $type = $this->scss ? 'scss' : 'sass'; 317 } elseif ($path = $asset->getSourcePath()) { 318 // FIXME: what if the extension is something else? 319 $type = pathinfo($path, PATHINFO_EXTENSION); 320 } else { 321 $type = 'scss'; 322 } 323 324 $tempName = tempnam($tempDir, 'assetic_compass'); 325 unlink($tempName); // FIXME: don't use tempnam() here 326 327 // input 328 $input = $tempName.'.'.$type; 329 330 // work-around for https://github.com/chriseppstein/compass/issues/748 331 if (defined('PHP_WINDOWS_VERSION_MAJOR')) { 332 $input = str_replace('\\', '/', $input); 333 } 334 335 $pb->add($input); 336 file_put_contents($input, $asset->getContent()); 337 338 // output 339 $output = $tempName.'.css'; 340 341 if ($this->homeEnv) { 342 // it's not really usefull but... https://github.com/chriseppstein/compass/issues/376 343 $pb->setEnv('HOME', FilesystemUtils::getTemporaryDirectory()); 344 $this->mergeEnv($pb); 345 } 346 347 $proc = $pb->getProcess(); 348 $code = $proc->run(); 349 350 if (0 !== $code) { 351 unlink($input); 352 if (isset($configFile)) { 353 unlink($configFile); 354 } 355 356 throw FilterException::fromProcess($proc)->setInput($asset->getContent()); 357 } 358 359 $asset->setContent(file_get_contents($output)); 360 361 unlink($input); 362 unlink($output); 363 if (isset($configFile)) { 364 unlink($configFile); 365 } 366 } 367 368 public function filterDump(AssetInterface $asset) 369 { 370 } 371 372 private function formatArrayToRuby($array) 373 { 374 $output = array(); 375 376 // does we have an associative array ? 377 if (count(array_filter(array_keys($array), "is_numeric")) != count($array)) { 378 foreach ($array as $name => $value) { 379 $output[] = sprintf(' :%s => "%s"', $name, addcslashes($value, '\\')); 380 } 381 $output = "{\n".implode(",\n", $output)."\n}"; 382 } else { 383 foreach ($array as $name => $value) { 384 $output[] = sprintf(' "%s"', addcslashes($value, '\\')); 385 } 386 $output = "[\n".implode(",\n", $output)."\n]"; 387 } 388 389 return $output; 390 } 391} 392