Completed
Push — master ( 67c76e...d70dad )
by Nazar
05:34
created

Collecting::array_flatten()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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