Completed
Push — master ( e9b6cb...03bdf3 )
by Nazar
05:39
created

Collecting::process_assets_map()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 16
nc 4
nop 4
dl 0
loc 27
ccs 17
cts 17
cp 1
crap 5
rs 8.439
c 0
b 0
f 0
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\Includes;
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, $includes_map]
21
	 */
22 6
	public static function get_includes_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 includes
31
		 */
32 6
		$all_includes = static::get_includes_list(array_keys($installed_modules), $theme);
33 6
		$includes_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 includes 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, $includes_map, $all_includes, $not_enabled);
47
		}
48 6
		unset($module, $module_data);
49
		/**
50
		 * For consistency
51
		 */
52 6
		$includes_map['System'] = $all_includes;
53 6
		Event::instance()->fire(
54 6
			'System/Page/includes_dependencies_and_map',
55
			[
56 6
				'dependencies' => &$dependencies,
57 6
				'includes_map' => &$includes_map
58
			]
59
		);
60 6
		$includes_map = static::webcomponents_support_filter($includes_map, $theme, (bool)$Config->core['disable_webcomponents']);
61 6
		$dependencies = static::normalize_dependencies($dependencies, $functionalities);
62 6
		$includes_map = static::clean_includes_arrays_without_files($dependencies, $includes_map);
63 6
		$includes_map = array_map(
64
			function ($includes) {
65 6
				return array_map('array_values', $includes);
66 6
			},
67
			$includes_map
68
		);
69 6
		$dependencies = array_filter(array_map('array_values', $dependencies));
70 6
		return [$dependencies, $includes_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_includes_list ($modules, $theme) {
81 6
		$includes = [];
82
		/**
83
		 * Get includes of system and theme
84
		 */
85 6
		static::fill_includes(DIR.'/includes', $includes);
86 6
		static::fill_includes(THEMES."/$theme", $includes);
87 6
		foreach ($modules as $module) {
88 6
			static::fill_includes(MODULES."/$module/includes", $includes);
89
		}
90
		return [
91 6
			'html' => array_merge(...$includes['html']),
92 6
			'js'   => array_merge(...$includes['js']),
93 6
			'css'  => array_merge(...$includes['css'])
94
		];
95
	}
96
	/**
97
	 * @param string     $base_dir
98
	 * @param string[][] $includes
99
	 */
100 6
	protected static function fill_includes ($base_dir, &$includes) {
101 6
		$includes['html'][] = static::fill_includes_internal($base_dir, 'html');
102 6
		$includes['js'][]   = static::fill_includes_internal($base_dir, 'js');
103 6
		$includes['css'][]  = static::fill_includes_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_includes_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  $includes_map
122
	 * @param array  $all_includes
123
	 * @param bool   $skip_functionalities
124
	 */
125 6
	protected static function process_meta ($base_dir, &$dependencies, &$functionalities, &$includes_map, &$all_includes, $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
			'require'  => [],
132
			'optional' => [],
133
			'provide'  => []
134
		];
135 6
		$package    = $meta['package'];
136 6
		$depends_on = array_merge((array)$meta['require'], (array)$meta['optional']);
137 6
		foreach ($depends_on as $d) {
138
			/**
139
			 * Get only name of package or functionality
140
			 */
141 6
			$dependencies[$package][] = preg_split('/[=<>]/', $d, 2)[0];
142
		}
143 6
		if (!$skip_functionalities) {
144 6
			foreach ((array)$meta['provide'] as $p) {
145
				/**
146
				 * If provides sub-functionality for other component (for instance, `Blog/post_patch`) - inverse "providing" to "dependency"
147
				 * Otherwise it is just functionality alias to package name
148
				 */
149 6
				if (strpos($p, '/') !== false) {
150
					/**
151
					 * Get name of package or functionality
152
					 */
153 2
					$p                  = explode('/', $p)[0];
154 2
					$dependencies[$p][] = $package;
155
				} else {
156 6
					$functionalities[$p] = $package;
157
				}
158
			}
159
		}
160 6
		if (isset($meta['assets'])) {
161 6
			static::process_assets_map($meta['assets'], "$base_dir/includes", $includes_map, $all_includes);
162
		}
163 6
	}
164
	/**
165
	 * Process map structure, fill includes map and remove files from list of all includes (remaining files will be included on all pages)
166
	 *
167
	 * @param array  $map
168
	 * @param string $includes_dir
169
	 * @param array  $includes_map
170
	 * @param array  $all_includes
171
	 */
172 6
	protected static function process_assets_map ($map, $includes_dir, &$includes_map, &$all_includes) {
173 6
		foreach ($map as $path => $files) {
174 6
			foreach ((array)$files as $file) {
175 6
				$extension = file_extension($file);
176 6
				if (in_array($extension, ['css', 'js', 'html'])) {
177 6
					$file                              = "$includes_dir/$extension/$file";
178 6
					$includes_map[$path][$extension][] = $file;
179 6
					$all_includes[$extension]          = array_diff($all_includes[$extension], [$file]);
180
				} else {
181 6
					$file = rtrim($file, '*');
182
					/**
183
					 * Wildcard support, it is possible to specify just path prefix and all files with this prefix will be included
184
					 */
185 6
					$found_files = array_filter(
186 6
						get_files_list($includes_dir, '/.*\.(css|js|html)$/i', 'f', '', true, 'name', '!include') ?: [],
187 6
						function ($f) use ($file) {
188
							// We need only files with specified mask and only those located in directory that corresponds to file's extension
189 6
							return preg_match("#^(css|js|html)/$file.*\\1$#i", $f);
190 6
						}
191
					);
192
					// Drop first level directory
193 6
					$found_files = _preg_replace('#^[^/]+/(.*)#', '$1', $found_files);
194 6
					static::process_assets_map([$path => $found_files], $includes_dir, $includes_map, $all_includes);
195
				}
196
			}
197
		}
198 6
	}
199
	/**
200
	 * Replace functionalities by real packages names, take into account recursive dependencies
201
	 *
202
	 * @param array[] $dependencies
203
	 * @param array   $functionalities
204
	 *
205
	 * @return array
206
	 */
207 6
	protected static function normalize_dependencies ($dependencies, $functionalities) {
208
		/**
209
		 * First of all remove packages without any dependencies
210
		 */
211 6
		$dependencies = array_filter($dependencies);
212
		/**
213
		 * First round, process aliases among dependencies
214
		 */
215 6
		foreach ($dependencies as &$depends_on) {
216 6
			foreach ($depends_on as &$dependency) {
217 6
				if (isset($functionalities[$dependency])) {
218 6
					$dependency = $functionalities[$dependency];
219
				}
220
			}
221 6
			unset($dependency);
222
		}
223 6
		unset($depends_on);
224
		/**
225
		 * Second round, build dependencies tree using references to corresponding recursive dependencies
226
		 */
227 6
		foreach ($dependencies as &$depends_on) {
228 6
			foreach ($depends_on as &$dependency) {
229 6
				if ($dependency != 'System' && isset($dependencies[$dependency])) {
230 6
					$dependency = [&$dependencies[$dependency], $dependency];
231
				}
232
			}
233 6
			unset($dependency);
234
		}
235 6
		unset($depends_on);
236 6
		$dependencies = array_map([static::class, 'array_flatten'], $dependencies);
237 6
		return array_map('array_unique', $dependencies);
238
	}
239
	/**
240
	 * Convert array of arbitrary nested structure into flat array
241
	 *
242
	 * @param array $array
243
	 *
244
	 * @return string[]
245
	 */
246 6
	protected static function array_flatten ($array) {
247 6
		foreach ($array as &$a) {
248 6
			if (is_array($a)) {
249 6
				$a = static::array_flatten($a);
250
			}
251
		}
252 6
		return array_merge(..._array($array));
253
	}
254
	/**
255
	 * If system is configured to not use Web Components - all HTML imports and Polymer-related JS code will be removed from includes map
256
	 *
257
	 * @param array[] $includes_map
258
	 * @param string  $theme
259
	 * @param bool    $disable_webcomponents
260
	 *
261
	 * @return array[]
262
	 */
263 6
	protected static function webcomponents_support_filter ($includes_map, $theme, $disable_webcomponents) {
264 6
		if ($theme != Config::SYSTEM_THEME && $disable_webcomponents) {
265 2
			foreach ($includes_map as &$includes) {
266 2
				unset($includes['html']);
267
			}
268 2
			unset($includes);
269 2
			$prefix    = DIR.'/includes/js/Polymer';
270 2
			$system_js = &$includes_map[Config::SYSTEM_MODULE]['js'];
271 2
			foreach ($system_js as $index => $file) {
272 2
				if (strpos($file, $prefix) === 0) {
273 2
					unset($system_js[$index]);
274
				}
275
			}
276 2
			$system_js = array_values($system_js);
277
		}
278 6
		return $includes_map;
279
	}
280
	/**
281
	 * Includes array is composed from dependencies and sometimes dependencies doesn't have any files, so we'll clean that
282
	 *
283
	 * @param array $dependencies
284
	 * @param array $includes_map
285
	 *
286
	 * @return array
287
	 */
288 6
	protected static function clean_includes_arrays_without_files ($dependencies, $includes_map) {
289 6
		foreach ($dependencies as &$depends_on) {
290 6
			foreach ($depends_on as $index => &$dependency) {
291 6
				if (!isset($includes_map[$dependency])) {
292 6
					unset($depends_on[$index]);
293
				}
294
			}
295 6
			unset($dependency);
296
		}
297 6
		return $includes_map;
298
	}
299
}
300