|
1 <?php |
|
2 |
|
3 /* |
|
4 * This file is part of the Assetic package, an OpenSky project. |
|
5 * |
|
6 * (c) 2010-2011 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 |
|
12 namespace Assetic\Asset; |
|
13 |
|
14 use Assetic\Filter\FilterCollection; |
|
15 use Assetic\Filter\FilterInterface; |
|
16 |
|
17 /** |
|
18 * A collection of assets. |
|
19 * |
|
20 * @author Kris Wallsmith <kris.wallsmith@gmail.com> |
|
21 */ |
|
22 class AssetCollection implements AssetInterface, \IteratorAggregate |
|
23 { |
|
24 private $assets; |
|
25 private $filters; |
|
26 private $sourceRoot; |
|
27 private $targetPath; |
|
28 private $content; |
|
29 private $clones; |
|
30 |
|
31 /** |
|
32 * Constructor. |
|
33 * |
|
34 * @param array $assets Assets for the current collection |
|
35 * @param array $filters Filters for the current collection |
|
36 * @param string $sourceRoot The root directory |
|
37 */ |
|
38 public function __construct($assets = array(), $filters = array(), $sourceRoot = null) |
|
39 { |
|
40 $this->assets = array(); |
|
41 foreach ($assets as $asset) { |
|
42 $this->add($asset); |
|
43 } |
|
44 |
|
45 $this->filters = new FilterCollection($filters); |
|
46 $this->sourceRoot = $sourceRoot; |
|
47 $this->clones = new \SplObjectStorage(); |
|
48 } |
|
49 |
|
50 /** |
|
51 * Adds an asset to the current collection. |
|
52 * |
|
53 * @param AssetInterface $asset An asset |
|
54 */ |
|
55 public function add(AssetInterface $asset) |
|
56 { |
|
57 $this->assets[] = $asset; |
|
58 } |
|
59 |
|
60 public function all() |
|
61 { |
|
62 return $this->assets; |
|
63 } |
|
64 |
|
65 public function ensureFilter(FilterInterface $filter) |
|
66 { |
|
67 $this->filters->ensure($filter); |
|
68 } |
|
69 |
|
70 public function getFilters() |
|
71 { |
|
72 return $this->filters->all(); |
|
73 } |
|
74 |
|
75 public function clearFilters() |
|
76 { |
|
77 $this->filters->clear(); |
|
78 } |
|
79 |
|
80 public function load(FilterInterface $additionalFilter = null) |
|
81 { |
|
82 // loop through leaves and load each asset |
|
83 $parts = array(); |
|
84 foreach ($this as $asset) { |
|
85 $asset->load($additionalFilter); |
|
86 $parts[] = $asset->getContent(); |
|
87 } |
|
88 |
|
89 $this->content = implode("\n", $parts); |
|
90 } |
|
91 |
|
92 public function dump(FilterInterface $additionalFilter = null) |
|
93 { |
|
94 // loop through leaves and dump each asset |
|
95 $parts = array(); |
|
96 foreach ($this as $asset) { |
|
97 $parts[] = $asset->dump($additionalFilter); |
|
98 } |
|
99 |
|
100 return implode("\n", $parts); |
|
101 } |
|
102 |
|
103 public function getContent() |
|
104 { |
|
105 return $this->content; |
|
106 } |
|
107 |
|
108 public function setContent($content) |
|
109 { |
|
110 $this->content = $content; |
|
111 } |
|
112 |
|
113 public function getSourceRoot() |
|
114 { |
|
115 return $this->sourceRoot; |
|
116 } |
|
117 |
|
118 public function getSourcePath() |
|
119 { |
|
120 } |
|
121 |
|
122 public function getTargetPath() |
|
123 { |
|
124 return $this->targetPath; |
|
125 } |
|
126 |
|
127 public function setTargetPath($targetPath) |
|
128 { |
|
129 $this->targetPath = $targetPath; |
|
130 } |
|
131 |
|
132 /** |
|
133 * Returns the highest last-modified value of all assets in the current collection. |
|
134 * |
|
135 * @return integer|null A UNIX timestamp |
|
136 */ |
|
137 public function getLastModified() |
|
138 { |
|
139 if (!count($this->assets)) { |
|
140 return; |
|
141 } |
|
142 |
|
143 $mapper = function (AssetInterface $asset) |
|
144 { |
|
145 return $asset->getLastModified(); |
|
146 }; |
|
147 |
|
148 return max(array_map($mapper, $this->assets)); |
|
149 } |
|
150 |
|
151 /** |
|
152 * Returns an iterator for looping recursively over unique leaves. |
|
153 */ |
|
154 public function getIterator() |
|
155 { |
|
156 return new \RecursiveIteratorIterator(new AssetCollectionFilterIterator(new AssetCollectionIterator($this, $this->clones))); |
|
157 } |
|
158 } |
|
159 |
|
160 /** |
|
161 * Asset collection filter iterator. |
|
162 * |
|
163 * The filter iterator is responsible for de-duplication of leaf assets based |
|
164 * on both strict equality and source URL. |
|
165 * |
|
166 * @author Kris Wallsmith <kris.wallsmith@gmail.com> |
|
167 * @access private |
|
168 */ |
|
169 class AssetCollectionFilterIterator extends \RecursiveFilterIterator |
|
170 { |
|
171 private $visited; |
|
172 private $sources; |
|
173 |
|
174 /** |
|
175 * Constructor. |
|
176 * |
|
177 * @param AssetCollectionIterator $iterator The inner iterator |
|
178 * @param array $visited An array of visited asset objects |
|
179 * @param array $sources An array of visited source strings |
|
180 */ |
|
181 public function __construct(AssetCollectionIterator $iterator, array $visited = array(), array $sources = array()) |
|
182 { |
|
183 parent::__construct($iterator); |
|
184 |
|
185 $this->visited = $visited; |
|
186 $this->sources = $sources; |
|
187 } |
|
188 |
|
189 /** |
|
190 * Determines whether the current asset is a duplicate. |
|
191 * |
|
192 * De-duplication is performed based on either strict equality or by |
|
193 * matching sources. |
|
194 * |
|
195 * @return Boolean Returns true if we have not seen this asset yet |
|
196 */ |
|
197 public function accept() |
|
198 { |
|
199 $asset = $this->getInnerIterator()->current(true); |
|
200 $duplicate = false; |
|
201 |
|
202 // check strict equality |
|
203 if (in_array($asset, $this->visited, true)) { |
|
204 $duplicate = true; |
|
205 } else { |
|
206 $this->visited[] = $asset; |
|
207 } |
|
208 |
|
209 // check source |
|
210 $sourceRoot = $asset->getSourceRoot(); |
|
211 $sourcePath = $asset->getSourcePath(); |
|
212 if ($sourceRoot && $sourcePath) { |
|
213 $source = $sourceRoot.'/'.$sourcePath; |
|
214 if (in_array($source, $this->sources)) { |
|
215 $duplicate = true; |
|
216 } else { |
|
217 $this->sources[] = $source; |
|
218 } |
|
219 } |
|
220 |
|
221 return !$duplicate; |
|
222 } |
|
223 |
|
224 /** |
|
225 * Passes visited objects and source URLs to the child iterator. |
|
226 */ |
|
227 public function getChildren() |
|
228 { |
|
229 return new self($this->getInnerIterator()->getChildren(), $this->visited, $this->sources); |
|
230 } |
|
231 } |
|
232 |
|
233 /** |
|
234 * Iterates over an asset collection. |
|
235 * |
|
236 * The iterator is responsible for cascading filters and target URL patterns |
|
237 * from parent to child assets. |
|
238 * |
|
239 * @author Kris Wallsmith <kris.wallsmith@gmail.com> |
|
240 * @access private |
|
241 */ |
|
242 class AssetCollectionIterator implements \RecursiveIterator |
|
243 { |
|
244 private $assets; |
|
245 private $filters; |
|
246 private $output; |
|
247 private $clones; |
|
248 |
|
249 public function __construct(AssetCollection $coll, \SplObjectStorage $clones) |
|
250 { |
|
251 $this->assets = $coll->all(); |
|
252 $this->filters = $coll->getFilters(); |
|
253 $this->output = $coll->getTargetPath(); |
|
254 $this->clones = $clones; |
|
255 |
|
256 if (false === $pos = strpos($this->output, '.')) { |
|
257 $this->output .= '_*'; |
|
258 } else { |
|
259 $this->output = substr($this->output, 0, $pos).'_*'.substr($this->output, $pos); |
|
260 } |
|
261 } |
|
262 |
|
263 /** |
|
264 * Returns a copy of the current asset with filters and a target URL applied. |
|
265 * |
|
266 * @param Boolean $raw Returns the unmodified asset if true |
|
267 */ |
|
268 public function current($raw = false) |
|
269 { |
|
270 $asset = current($this->assets); |
|
271 |
|
272 if ($raw) { |
|
273 return $asset; |
|
274 } |
|
275 |
|
276 // clone once |
|
277 if (!isset($this->clones[$asset])) { |
|
278 $clone = $this->clones[$asset] = clone $asset; |
|
279 |
|
280 // generate a target path based on asset name |
|
281 $name = sprintf('%s_%d', pathinfo($asset->getSourcePath(), PATHINFO_FILENAME) ?: 'part', $this->key() + 1); |
|
282 $clone->setTargetPath(str_replace('*', $name, $this->output)); |
|
283 } else { |
|
284 $clone = $this->clones[$asset]; |
|
285 } |
|
286 |
|
287 // cascade filters |
|
288 foreach ($this->filters as $filter) { |
|
289 $clone->ensureFilter($filter); |
|
290 } |
|
291 |
|
292 return $clone; |
|
293 } |
|
294 |
|
295 public function key() |
|
296 { |
|
297 return key($this->assets); |
|
298 } |
|
299 |
|
300 public function next() |
|
301 { |
|
302 return next($this->assets); |
|
303 } |
|
304 |
|
305 public function rewind() |
|
306 { |
|
307 return reset($this->assets); |
|
308 } |
|
309 |
|
310 public function valid() |
|
311 { |
|
312 return false !== current($this->assets); |
|
313 } |
|
314 |
|
315 public function hasChildren() |
|
316 { |
|
317 return current($this->assets) instanceof AssetCollection; |
|
318 } |
|
319 |
|
320 /** |
|
321 * @uses current() |
|
322 */ |
|
323 public function getChildren() |
|
324 { |
|
325 return new self($this->current(), $this->clones); |
|
326 } |
|
327 } |