Completed
Push — master ( 0f36f7...67c76e )
by Nazar
04:34
created

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