Completed
Push — master ( f95f18...47bd7a )
by Nazar
03:53
created

Collecting::normalize_dependencies()   C

Complexity

Conditions 12
Paths 80

Size

Total Lines 49
Code Lines 20

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 49
rs 5.1474
cc 12
eloc 20
nc 80
nop 2

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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