1 <?php
2 3 4 5 6 7
8 namespace Skimia\Assets\Scanner;
9
10 use Illuminate\Contracts\Foundation\Application;
11 use Skimia\Assets\Events\BeforeMergeCollectionFiles;
12 use Symfony\Component\Finder\Finder;
13 use File;
14 use Cache;
15
16 class Scanner
17 {
18 19 20
21 protected $app;
22
23 24 25
26 protected $directories;
27
28 29 30
31 protected $builded_collections = [];
32
33 34 35
36 protected $last_builded_collections = [];
37
38 39 40
41 protected $directories_options;
42
43 44 45
46 public function __construct(Application $app)
47 {
48 $this->app = $app;
49 }
50
51 52 53
54 public function getScannedPath()
55 {
56 return $this->app['path.storage'].'/framework/assets.generation.scanned.php';
57 }
58
59 public function isScanned()
60 {
61 return $this->app['files']->exists($this->getScannedPath());
62 }
63
64 public function loadScanned()
65 {
66 if ($this->isScanned()) {
67 require $this->getScannedPath();
68
69 return true;
70 }
71
72 return false;
73 }
74
75 public function setDirectoriesToScan($directories)
76 {
77 $dirsToScan = [];
78 foreach ($directories as $path => $directory) {
79 if (is_string($path) && File::exists($path)) {
80 $dirsToScan[] = $path;
81 $this->directories_options[$path] = $directory;
82 } elseif (is_string($directory) && File::exists($directory)) {
83 $dirsToScan[] = $directory;
84 }
85 }
86 $this->directories = $dirsToScan;
87 }
88
89 public function scan()
90 {
91 file_put_contents(
92 $this->getScannedPath(), '<?php '.$this->getDefinitions()
93 );
94 }
95
96 protected function getOrderedFileDefinitions()
97 {
98 $files_defs = [];
99 foreach ($this->directories as $path) {
100 if (! \File::exists($path)) {
101 continue;
102 }
103
104 $finder = Finder::create()->files()->ignoreDotFiles(false)->in($path);
105
106 $max_depth = $this->app['config']->get('assets.max_depth', 3);
107 if (isset($this->directories_options[$path]['max_depth'])) {
108 $max_depth = $this->directories_options[$path]['max_depth'];
109 }
110
111 $files = $finder->depth('<= '.$max_depth)->name('.assets.json');
112
113 foreach ($files as $file) {
114 $content = $this->filterFile(json_decode($file->getContents(), true));
115 $content['__dir'] = dirname($file->getRealpath());
116 $files_defs[isset($content['alias']) ? $content['alias'] : $content['name']] = $content;
117 }
118 }
119
120 return $this->orderBydeps($files_defs);
121 }
122
123 protected function filterFile($file)
124 {
125 $file = array_merge(
126 [
127 'name' => 'must_be_defined',
128 'alias' => 'directory',
129 ],
130 $file
131 );
132
133 return $file;
134 }
135
136 public function getDefinitions()
137 {
138 $output = '$makeCollections = function($container){'.PHP_EOL;
139
140 $files = $this->getOrderedFileDefinitions();
141
142 $event = new BeforeMergeCollectionFiles($files);
143 event($event);
144
145 $collections = $this->mergeFiles($files);
146
147 foreach ($collections as $name => $assets) {
148 $output .= $this->buildCollection($name, $assets);
149 }
150
151 $this->saveBuildedCollections();
152
153 $output .= '};'.PHP_EOL;
154
155 $output .= $this->makeGroups();
156
157 return trim($output);
158 }
159
160 protected function getAssetsGroups()
161 {
162 $groups = [];
163 $config = $this->app['config']->get('assets.groups', []);
164
165 if (! isset($config['default'])) {
166 return['default'];
167 }
168
169 foreach ($config as $groupName => $groupConfig) {
170 $groups[] = $groupName;
171 }
172
173 return $groups;
174 }
175
176 protected function makeGroups()
177 {
178 $output = '';
179
180 $groups = $this->getAssetsGroups();
181
182 foreach ($groups as $groupName) {
183 $output .= '$makeCollections(\''.$groupName.'\');'.PHP_EOL;
184 }
185
186 return $output;
187 }
188
189 protected function mergeFiles($files)
190 {
191 $collections = [];
192
193 foreach ($files as $file) {
194 $file = $this->updateFile($file);
195 $file_collections = $file['collections'];
196 $collections = array_merge($collections, $file_collections);
197 }
198
199 return $collections;
200 }
201
202 protected function updateFile($file)
203 {
204 $collections = $file['collections'];
205 $file['__collections'] = $collections;
206 foreach ($collections as $name => &$files) {
207 foreach ($files as &$f) {
208
209
210 if (\File::exists($file['__dir'].'/'.$f)) {
211 $f = $file['alias'].'#'.$f;
212 }
213 }
214 }
215
216 $file['collections'] = $collections;
217
218 $this->copyCollections($file);
219
220 return $file;
221 }
222
223 protected function copyCollections($file)
224 {
225 $mode = $this->app['config']->get('assets.copy_mode', 'copy');
226 $collections_dir = $this->app['config']->get('assets.collections_dir', 'collections');
227 $collections_dir = public_path($collections_dir);
228
229 if (isset($file['copy'])) {
230 foreach ($file['copy'] as $directory) {
231 $input = $file['__dir'].'/'.$directory;
232
233 $output = $collections_dir.'/'.$file['alias'].'/'.$directory;
234
235 if ($mode == 'copy') {
236 $this->copyAssets($input, $output);
237 } else {
238 $this->symlinkAssets($input, $output);
239 }
240 }
241 }
242 }
243
244 protected function copyAssets($in, $out)
245 {
246 $out_dir = dirname($out);
247 \File::makeDirectory($out_dir, 0777, true, true);
248 \File::copyDirectory($in, $out);
249
250 return true;
251 }
252
253 protected function symlinkAssets($in, $out)
254 {
255 $out_dir = dirname($out);
256 \File::makeDirectory($out_dir, 0777, true, true);
257 symlink($in, $out);
258
259 return true;
260 }
261
262 protected function orderBydeps($list)
263 {
264 $resolved = [];
265 $seen = [];
266 $element = [
267 'name' => false,
268 'resolve_this' => false,
269 'require' => array_keys($list),
270 ];
271 $this->dep_resolve($list, $element, $resolved, $seen);
272
273 return $resolved;
274 }
275
276 protected function dep_resolve($list, $node, &$resolved, &$unresolved)
277 {
278
279 $unresolved[] = $node;
280 if (isset($node['require'])) {
281 foreach ($node['require'] as $required) {
282 if (! isset($list[$required])) {
283 throw new \Exception('Unknown or not accessible dep: '.$required.' for '.$node['name'].(isset($node['alias']) ? '('.$node['alias'].')' : ''));
284 }
285 if (! in_array($list[$required], $resolved)) {
286 if (in_array($list[$required], $unresolved)) {
287 throw new \Exception('Circular reference detected: '.$node['name'].' -> '.$list[$required]['name'].(isset($list[$required]['alias']) ? '('.$list[$required]['alias'].')' : ''));
288 }
289 $this->dep_resolve($list, $list[$required], $resolved, $unresolved);
290 }
291 }
292 }
293 if (! isset($node['resolve_this']) || $node['resolve_this'] === true) {
294 $resolved[] = $node;
295 }
296 unset($unresolved[array_search($node, $unresolved)]);
297 }
298
299 protected function buildCollection($name, $files)
300 {
301 $this->builded_collections[] = $name;
302
303 return sprintf(' Assets::group($container)->registerCollection(\'%s\', %s);'.PHP_EOL,
304 $name,
305 var_export($files, true));
306 }
307
308 309 310
311 protected function saveBuildedCollections()
312 {
313 $this->last_builded_collections = Cache::get('skimia.assets.collections.builded', []);
314 Cache::forever('skimia.assets.collections.builded', $this->builded_collections);
315 }
316
317 318 319
320 public function getNewlyBuildedCollections()
321 {
322 return array_diff($this->builded_collections, $this->last_builded_collections);
323 }
324
325 326 327
328 public function getRemovedBuildedCollections()
329 {
330 return array_diff($this->last_builded_collections, $this->builded_collections);
331 }
332 }
333