1<?php
2
3/*
4 * This file is part of the Assetic package, an OpenSky project.
5 *
6 * (c) 2010-2015 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\Factory\AssetFactory;
15use Assetic\Asset\AssetInterface;
16use Assetic\Filter\DependencyExtractorInterface;
17use Assetic\Util\CssUtils;
18
19/**
20 * Compiles Sass to CSS.
21 *
22 * @author Mikey Clarke <mikey.clarke@me.com>
23 */
24class 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