Completed
Push — master ( fcc34f...fa84a1 )
by Nazar
08:40
created

Collecting   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 286
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 286
ccs 116
cts 116
cp 1
rs 8.2857
c 0
b 0
f 0
wmc 39
lcom 1
cbo 2

10 Methods

Rating   Name   Duplication   Size   Complexity  
A get_assets_dependencies_and_map() 0 50 2
A get_assets_list() 0 16 2
A fill_assets() 0 5 1
A fill_assets_internal() 0 3 2
B process_meta() 0 38 6
B process_assets_map() 0 27 5
C normalize_dependencies() 0 32 8
A array_flatten() 0 8 3
B webcomponents_support_filter() 0 17 6
A clean_assets_arrays_without_files() 0 11 4
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2014-2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs\Page\Assets;
9
use
10
	cs\Config,
11
	cs\Event;
12
13
class Collecting {
14
	/**
15
	 * Get dependencies of components between each other (only that contains some HTML, JS and CSS files) and mapping HTML, JS and CSS files to URL paths
16
	 *
17
	 * @param Config $Config
18
	 * @param string $theme
19
	 *
20
	 * @return array[] [$dependencies, $assets_map]
21
	 */
22 6
	public static function get_assets_dependencies_and_map ($Config, $theme) {
23 6
		$installed_modules = array_filter(
24 6
			$Config->components['modules'],
25
			function ($module_data) {
26 6
				return $module_data['active'] != Config\Module_Properties::UNINSTALLED;
27 6
			}
28
		);
29
		/**
30
		 * Get all assets
31
		 */
32 6
		$all_assets = static::get_assets_list(array_keys($installed_modules), $theme);
33 6
		$assets_map = [];
34
		/**
35
		 * Array [package => [list of packages it depends on]]
36
		 */
37 6
		$dependencies    = [];
38 6
		$functionalities = [];
39
		/**
40
		 * According to components's maps some files should be included only on specific pages.
41
		 * Here we read this rules, and remove from whole assets list such items, that should be included only on specific pages.
42
		 * Also collect dependencies.
43
		 */
44 6
		foreach ($installed_modules as $module => $module_data) {
45 6
			$not_enabled = $module_data['active'] != Config\Module_Properties::ENABLED;
46 6
			static::process_meta(MODULES."/$module", $dependencies, $functionalities, $assets_map, $all_assets, $not_enabled);
47
		}
48 6
		unset($module, $module_data);
49
		/**
50
		 * For consistency
51
		 */
52 6
		$assets_map['System'] = $all_assets;
53 6
		Event::instance()->fire(
54 6
			'System/Page/assets_dependencies_and_map',
55
			[
56 6
				'dependencies' => &$dependencies,
57 6
				'assets_map'   => &$assets_map
58
			]
59
		);
60 6
		$assets_map   = static::webcomponents_support_filter($assets_map, $theme, (bool)$Config->core['disable_webcomponents']);
61 6
		$dependencies = static::normalize_dependencies($dependencies, $functionalities);
62 6
		$assets_map   = static::clean_assets_arrays_without_files($dependencies, $assets_map);
63 6
		$assets_map   = array_map(
64
			function ($assets) {
65 6
				return array_map('array_values', $assets);
66 6
			},
67
			$assets_map
68
		);
69 6
		$dependencies = array_filter(array_map('array_values', $dependencies));
70 6
		return [$dependencies, $assets_map];
71
	}
72
	/**
73
	 * Getting of HTML, JS and CSS files list to be included
74
	 *
75
	 * @param string[] $modules
76
	 * @param string   $theme
77
	 *
78
	 * @return string[][]
79
	 */
80 6
	protected static function get_assets_list ($modules, $theme) {
81 6
		$assets = [];
82
		/**
83
		 * Get assets of system and theme
84
		 */
85 6
		static::fill_assets(DIR.'/assets', $assets);
86 6
		static::fill_assets(THEMES."/$theme", $assets);
87 6
		foreach ($modules as $module) {
88 6
			static::fill_assets(MODULES."/$module/assets", $assets);
89
		}
90
		return [
91 6
			'html' => array_merge(...$assets['html']),
92 6
			'js'   => array_merge(...$assets['js']),
93 6
			'css'  => array_merge(...$assets['css'])
94
		];
95
	}
96
	/**
97
	 * @param string     $base_dir
98
	 * @param string[][] $assets
99
	 */
100 6
	protected static function fill_assets ($base_dir, &$assets) {
101 6
		$assets['html'][] = static::fill_assets_internal($base_dir, 'html');
102 6
		$assets['js'][]   = static::fill_assets_internal($base_dir, 'js');
103 6
		$assets['css'][]  = static::fill_assets_internal($base_dir, 'css');
104 6
	}
105
	/**
106
	 * @param string $base_dir
107
	 * @param string $ext
108
	 *
109
	 * @return array
110
	 */
111 6
	protected static function fill_assets_internal ($base_dir, $ext) {
112 6
		return get_files_list("$base_dir/$ext", "/.*\\.$ext\$/i", 'f', true, true, 'name', '!include') ?: [];
113
	}
114
	/**
115
	 * Process meta information and corresponding entries to dependencies and functionalities, fill assets map and remove files from list of all assets
116
	 * (remaining files will be included on all pages)
117
	 *
118
	 * @param string $base_dir
119
	 * @param array  $dependencies
120
	 * @param array  $functionalities
121
	 * @param array  $assets_map
122
	 * @param array  $all_assets
123
	 * @param bool   $skip_functionalities
124
	 */
125 6
	protected static function process_meta ($base_dir, &$dependencies, &$functionalities, &$assets_map, &$all_assets, $skip_functionalities = false) {
126 6
		if (!file_exists("$base_dir/meta.json")) {
127 2
			return;
128
		}
129 6
		$meta = file_get_json("$base_dir/meta.json");
130
		$meta += [
131 6
			'assets'   => [],
132
			'require'  => [],
133
			'optional' => [],
134
			'provide'  => []
135
		];
136 6
		$package    = $meta['package'];
137 6
		$depends_on = array_merge((array)$meta['require'], (array)$meta['optional']);
138 6
		foreach ($depends_on as $d) {
139
			/**
140
			 * Get only name of package or functionality
141
			 */
142 6
			$dependencies[$package][] = preg_split('/[=<>]/', $d, 2)[0];
143
		}
144 6
		if (!$skip_functionalities) {
145 6
			foreach ((array)$meta['provide'] as $p) {
146
				/**
147
				 * If provides sub-functionality for other component (for instance, `Blog/post_patch`) - inverse "providing" to "dependency"
148
				 * Otherwise it is just functionality alias to package name
149
				 */
150 6
				if (strpos($p, '/') !== false) {
151
					/**
152
					 * Get name of package or functionality
153
					 */
154 2
					$p                  = explode('/', $p)[0];
155 2
					$dependencies[$p][] = $package;
156
				} else {
157 6
					$functionalities[$p] = $package;
158
				}
159
			}
160
		}
161 6
		static::process_assets_map($meta['assets'], "$base_dir/assets", $assets_map, $all_assets);
162 6
	}
163
	/**
164
	 * Process map structure, fill assets map and remove files from list of all assets (remaining files will be included on all pages)
165
	 *
166
	 * @param array  $map
167
	 * @param string $assets_dir
168
	 * @param array  $assets_map
169
	 * @param array  $all_assets
170
	 */
171 6
	protected static function process_assets_map ($map, $assets_dir, &$assets_map, &$all_assets) {
172 6
		foreach ($map as $path => $files) {
173 6
			foreach ((array)$files as $file) {
174 6
				$extension = file_extension($file);
175 6
				if (in_array($extension, ['css', 'js', 'html'])) {
176 6
					$file                            = "$assets_dir/$extension/$file";
177 6
					$assets_map[$path][$extension][] = $file;
178 6
					$all_assets[$extension]          = array_diff($all_assets[$extension], [$file]);
179
				} else {
180 6
					$file = rtrim($file, '*');
181
					/**
182
					 * Wildcard support, it is possible to specify just path prefix and all files with this prefix will be included
183
					 */
184 6
					$found_files = array_filter(
185 6
						get_files_list($assets_dir, '/.*\.(css|js|html)$/i', 'f', '', true, 'name', '!include') ?: [],
186 6
						function ($f) use ($file) {
187
							// We need only files with specified mask and only those located in directory that corresponds to file's extension
188 6
							return preg_match("#^(css|js|html)/$file.*\\1$#i", $f);
189 6
						}
190
					);
191
					// Drop first level directory
192 6
					$found_files = _preg_replace('#^[^/]+/(.*)#', '$1', $found_files);
193 6
					static::process_assets_map([$path => $found_files], $assets_dir, $assets_map, $all_assets);
194
				}
195
			}
196
		}
197 6
	}
198
	/**
199
	 * Replace functionalities by real packages names, take into account recursive dependencies
200
	 *
201
	 * @param array[] $dependencies
202
	 * @param array   $functionalities
203
	 *
204
	 * @return array
205
	 */
206 6
	protected static function normalize_dependencies ($dependencies, $functionalities) {
207
		/**
208
		 * First of all remove packages without any dependencies
209
		 */
210 6
		$dependencies = array_filter($dependencies);
211
		/**
212
		 * First round, process aliases among dependencies
213
		 */
214 6
		foreach ($dependencies as &$depends_on) {
215 6
			foreach ($depends_on as &$dependency) {
216 6
				if (isset($functionalities[$dependency])) {
217 6
					$dependency = $functionalities[$dependency];
218
				}
219
			}
220 6
			unset($dependency);
221
		}
222 6
		unset($depends_on);
223
		/**
224
		 * Second round, build dependencies tree using references to corresponding recursive dependencies
225
		 */
226 6
		foreach ($dependencies as &$depends_on) {
227 6
			foreach ($depends_on as &$dependency) {
228 6
				if ($dependency != 'System' && isset($dependencies[$dependency])) {
229 6
					$dependency = [&$dependencies[$dependency], $dependency];
230
				}
231
			}
232 6
			unset($dependency);
233
		}
234 6
		unset($depends_on);
235 6
		$dependencies = array_map([static::class, 'array_flatten'], $dependencies);
236 6
		return array_map('array_unique', $dependencies);
237
	}
238
	/**
239
	 * Convert array of arbitrary nested structure into flat array
240
	 *
241
	 * @param array $array
242
	 *
243
	 * @return string[]
244
	 */
245 6
	protected static function array_flatten ($array) {
246 6
		foreach ($array as &$a) {
247 6
			if (is_array($a)) {
248 6
				$a = static::array_flatten($a);
249
			}
250
		}
251 6
		return array_merge(..._array($array));
252
	}
253
	/**
254
	 * If system is configured to not use Web Components - all HTML imports and Polymer-related JS code will be removed from assets map
255
	 *
256
	 * @param array[] $assets_map
257
	 * @param string  $theme
258
	 * @param bool    $disable_webcomponents
259
	 *
260
	 * @return array[]
261
	 */
262 6
	protected static function webcomponents_support_filter ($assets_map, $theme, $disable_webcomponents) {
263 6
		if ($theme != Config::SYSTEM_THEME && $disable_webcomponents) {
264 2
			foreach ($assets_map as &$assets) {
265 2
				unset($assets['html']);
266
			}
267 2
			unset($assets);
268 2
			$prefix    = DIR.'/assets/js/Polymer';
269 2
			$system_js = &$assets_map[Config::SYSTEM_MODULE]['js'];
270 2
			foreach ($system_js as $index => $file) {
271 2
				if (strpos($file, $prefix) === 0) {
272 2
					unset($system_js[$index]);
273
				}
274
			}
275 2
			$system_js = array_values($system_js);
276
		}
277 6
		return $assets_map;
278
	}
279
	/**
280
	 * Assets array is composed from dependencies and sometimes dependencies doesn't have any files, so we'll clean that
281
	 *
282
	 * @param array $dependencies
283
	 * @param array $assets_map
284
	 *
285
	 * @return array
286
	 */
287 6
	protected static function clean_assets_arrays_without_files ($dependencies, $assets_map) {
288 6
		foreach ($dependencies as &$depends_on) {
289 6
			foreach ($depends_on as $index => &$dependency) {
290 6
				if (!isset($assets_map[$dependency])) {
291 6
					unset($depends_on[$index]);
292
				}
293
			}
294 6
			unset($dependency);
295
		}
296 6
		return $assets_map;
297
	}
298
}
299