Completed
Push — master ( 215c97...c1aac3 )
by Nazar
04:30
created

Collecting::clean_includes_arrays_without_files()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

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