Completed
Push — master ( 4c24ff...8a852c )
by Nazar
04:10
created

Collecting::process_map_internal()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 27
rs 8.439
cc 5
eloc 16
nc 4
nop 4
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[][]
1 ignored issue
show
Documentation introduced by
Should the return type not be array<string,string[]>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
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);
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
	 */
121
	protected function process_meta ($base_dir, &$dependencies, &$functionalities) {
122
		if (!file_exists("$base_dir/meta.json")) {
123
			return;
124
		}
125
		$meta = file_get_json("$base_dir/meta.json");
126
		$meta += [
127
			'require'  => [],
128
			'optional' => [],
129
			'provide'  => []
130
		];
131
		$package = $meta['package'];
132
		foreach ((array)$meta['require'] as $r) {
133
			/**
134
			 * Get only name of package or functionality
135
			 */
136
			$r                        = preg_split('/[=<>]/', $r, 2)[0];
137
			$dependencies[$package][] = $r;
138
		}
139
		foreach ((array)$meta['optional'] as $o) {
140
			/**
141
			 * Get only name of package or functionality
142
			 */
143
			$o                        = preg_split('/[=<>]/', $o, 2)[0];
144
			$dependencies[$package][] = $o;
145
		}
146
		foreach ((array)$meta['provide'] as $p) {
147
			/**
148
			 * If provides sub-functionality for other component (for instance, `Blog/post_patch`) - inverse "providing" to "dependency"
149
			 * Otherwise it is just functionality alias to package name
150
			 */
151
			if (strpos($p, '/') !== false) {
152
				/**
153
				 * Get name of package or functionality
154
				 */
155
				$p                  = explode('/', $p)[0];
156
				$dependencies[$p][] = $package;
157
			} else {
158
				$functionalities[$p] = $package;
159
			}
160
		}
161
	}
162
	/**
163
	 * Process map structure, fill includes map and remove files from list of all includes (remaining files will be included on all pages)
164
	 *
165
	 * @param string $base_dir
166
	 * @param array  $includes_map
167
	 * @param array  $all_includes
168
	 */
169
	protected function process_map ($base_dir, &$includes_map, &$all_includes) {
170
		if (!file_exists("$base_dir/includes/map.json")) {
171
			return;
172
		}
173
		$this->process_map_internal(file_get_json("$base_dir/includes/map.json"), "$base_dir/includes", $includes_map, $all_includes);
174
	}
175
	/**
176
	 * Process map structure, fill includes map and remove files from list of all includes (remaining files will be included on all pages)
177
	 *
178
	 * @param array  $map
179
	 * @param string $includes_dir
180
	 * @param array  $includes_map
181
	 * @param array  $all_includes
182
	 */
183
	protected function process_map_internal ($map, $includes_dir, &$includes_map, &$all_includes) {
184
		foreach ($map as $path => $files) {
185
			foreach ((array)$files as $file) {
186
				$extension = file_extension($file);
187
				if (in_array($extension, ['css', 'js', 'html'])) {
188
					$file                              = "$includes_dir/$extension/$file";
189
					$includes_map[$path][$extension][] = $file;
190
					$all_includes[$extension]          = array_diff($all_includes[$extension], [$file]);
191
				} else {
192
					$file = rtrim($file, '*');
193
					/**
194
					 * Wildcard support, it is possible to specify just path prefix and all files with this prefix will be included
195
					 */
196
					$found_files = array_filter(
197
						get_files_list($includes_dir, '/.*\.(css|js|html)$/i', 'f', '', true, 'name', '!include') ?: [],
198
						function ($f) use ($file) {
199
							// We need only files with specified mask and only those located in directory that corresponds to file's extension
200
							return preg_match("#^(css|js|html)/$file.*\\1$#i", $f);
201
						}
202
					);
203
					// Drop first level directory
204
					$found_files = _preg_replace('#^[^/]+/(.*)#', '$1', $found_files);
205
					$this->process_map_internal([$path => $found_files], $includes_dir, $includes_map, $all_includes);
206
				}
207
			}
208
		}
209
	}
210
	/**
211
	 * Replace functionalities by real packages names, take into account recursive dependencies
212
	 *
213
	 * @param array $dependencies
214
	 * @param array $functionalities
215
	 *
216
	 * @return array
217
	 */
218
	protected function normalize_dependencies ($dependencies, $functionalities) {
219
		/**
220
		 * First of all remove packages without any dependencies
221
		 */
222
		$dependencies = array_filter($dependencies);
223
		/**
224
		 * First round, process aliases among keys
225
		 */
226
		foreach (array_keys($dependencies) as $d) {
227
			if (isset($functionalities[$d])) {
228
				$package = $functionalities[$d];
229
				/**
230
				 * Add dependencies to existing package dependencies
231
				 */
232
				foreach ($dependencies[$d] as $dependency) {
233
					$dependencies[$package][] = $dependency;
234
				}
235
				/**
236
				 * Drop alias
237
				 */
238
				unset($dependencies[$d]);
239
			}
240
		}
241
		unset($d, $dependency);
242
		/**
243
		 * Second round, process aliases among dependencies
244
		 */
245
		foreach ($dependencies as &$depends_on) {
246
			foreach ($depends_on as &$dependency) {
247
				if (isset($functionalities[$dependency])) {
248
					$dependency = $functionalities[$dependency];
249
				}
250
			}
251
		}
252
		unset($depends_on, $dependency);
253
		/**
254
		 * Third round, process recursive dependencies
255
		 */
256
		foreach ($dependencies as &$depends_on) {
257
			foreach ($depends_on as &$dependency) {
258
				if ($dependency != 'System' && isset($dependencies[$dependency])) {
259
					foreach (array_diff($dependencies[$dependency], $depends_on) as $new_dependency) {
260
						$depends_on[] = $new_dependency;
261
					}
262
				}
263
			}
264
		}
265
		return array_map('array_unique', $dependencies);
266
	}
267
	/**
268
	 * Includes array is composed from dependencies and sometimes dependencies doesn't have any files, so we'll clean that
269
	 *
270
	 * @param array $dependencies
271
	 * @param array $includes_map
272
	 *
273
	 * @return array
274
	 */
275
	protected function clean_includes_arrays_without_files ($dependencies, $includes_map) {
276
		foreach ($dependencies as &$depends_on) {
277
			foreach ($depends_on as $index => &$dependency) {
278
				if (!isset($includes_map[$dependency])) {
279
					unset($depends_on[$index]);
280
				}
281
			}
282
			unset($dependency);
283
		}
284
		return $includes_map;
285
	}
286
}
287