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; |
9
|
|
|
use |
10
|
|
|
cs\App, |
11
|
|
|
cs\Core, |
12
|
|
|
cs\Config, |
13
|
|
|
cs\Event, |
14
|
|
|
cs\Language, |
15
|
|
|
cs\Request, |
16
|
|
|
cs\User, |
17
|
|
|
h; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Provides next events: |
21
|
|
|
* System/Page/includes_dependencies_and_map |
22
|
|
|
* [ |
23
|
|
|
* 'dependencies' => &$dependencies, |
24
|
|
|
* 'includes_map' => &$includes_map |
25
|
|
|
* ] |
26
|
|
|
* |
27
|
|
|
* System/Page/rebuild_cache |
28
|
|
|
* [ |
29
|
|
|
* 'key' => &$key //Reference to the key, that will be appended to all css and js files, can be changed to reflect JavaScript and CSS changes |
30
|
|
|
* ] |
31
|
|
|
* |
32
|
|
|
* System/Page/requirejs |
33
|
|
|
* [ |
34
|
|
|
* 'paths' => &$paths, // The same as `paths` in requirejs.config() |
35
|
|
|
* 'directories_to_browse' => &$directories_to_browse // Where to look for AMD modules (typically bower_components and node_modules directories) |
36
|
|
|
* ] |
37
|
|
|
* |
38
|
|
|
* Includes management for `cs\Page` class |
39
|
|
|
* |
40
|
|
|
* @property string $Title |
41
|
|
|
* @property string $Description |
42
|
|
|
* @property string $canonical_url |
43
|
|
|
* @property string $Head |
44
|
|
|
* @property string $post_Body |
45
|
|
|
* @property string $theme |
46
|
|
|
*/ |
47
|
|
|
trait Includes { |
48
|
|
|
/** |
49
|
|
|
* @var array[] |
50
|
|
|
*/ |
51
|
|
|
protected $core_html; |
52
|
|
|
/** |
53
|
|
|
* @var array[] |
54
|
|
|
*/ |
55
|
|
|
protected $core_js; |
56
|
|
|
/** |
57
|
|
|
* @var array[] |
58
|
|
|
*/ |
59
|
|
|
protected $core_css; |
60
|
|
|
/** |
61
|
|
|
* @var string |
62
|
|
|
*/ |
63
|
|
|
protected $core_config; |
64
|
|
|
/** |
65
|
|
|
* @var array[] |
66
|
|
|
*/ |
67
|
|
|
protected $html; |
68
|
|
|
/** |
69
|
|
|
* @var array[] |
70
|
|
|
*/ |
71
|
|
|
protected $js; |
72
|
|
|
/** |
73
|
|
|
* @var array[] |
74
|
|
|
*/ |
75
|
|
|
protected $css; |
76
|
|
|
/** |
77
|
|
|
* @var string |
78
|
|
|
*/ |
79
|
|
|
protected $config; |
80
|
|
|
/** |
81
|
|
|
* Base name is used as prefix when creating CSS/JS/HTML cache files in order to avoid collisions when having several themes and languages |
82
|
|
|
* @var string |
83
|
|
|
*/ |
84
|
|
|
protected $pcache_basename; |
85
|
|
|
protected function init_includes () { |
86
|
|
|
$this->core_html = [0 => [], 1 => []]; |
87
|
|
|
$this->core_js = [0 => [], 1 => []]; |
88
|
|
|
$this->core_css = [0 => [], 1 => []]; |
89
|
|
|
$this->core_config = ''; |
90
|
|
|
$this->html = [0 => [], 1 => []]; |
91
|
|
|
$this->js = [0 => [], 1 => []]; |
92
|
|
|
$this->css = [0 => [], 1 => []]; |
93
|
|
|
$this->config = ''; |
94
|
|
|
$this->pcache_basename = ''; |
95
|
|
|
} |
96
|
|
|
/** |
97
|
|
|
* Including of Web Components |
98
|
|
|
* |
99
|
|
|
* @param string|string[] $add Path to including file, or code |
100
|
|
|
* @param string $mode Can be <b>file</b> or <b>code</b> |
101
|
|
|
* |
102
|
|
|
* @return \cs\Page |
103
|
|
|
*/ |
104
|
|
|
function html ($add, $mode = 'file') { |
105
|
|
|
return $this->html_internal($add, $mode); |
106
|
|
|
} |
107
|
|
|
/** |
108
|
|
|
* @param string|string[] $add |
109
|
|
|
* @param string $mode |
110
|
|
|
* @param bool $core |
111
|
|
|
* |
112
|
|
|
* @return \cs\Page |
113
|
|
|
*/ |
114
|
|
|
protected function html_internal ($add, $mode = 'file', $core = false) { |
115
|
|
|
if (!$add) { |
116
|
|
|
return $this; |
117
|
|
|
} |
118
|
|
|
if (is_array($add)) { |
119
|
|
|
foreach (array_filter($add) as $script) { |
120
|
|
|
$this->html_internal($script, $mode, $core); |
121
|
|
|
} |
122
|
|
|
} else { |
123
|
|
|
if ($core) { |
124
|
|
|
$html = &$this->core_html; |
125
|
|
|
} else { |
126
|
|
|
$html = &$this->html; |
127
|
|
|
} |
128
|
|
|
if ($mode == 'file') { |
129
|
|
|
$html[0][] = h::link( |
130
|
|
|
[ |
131
|
|
|
'href' => $add, |
132
|
|
|
'rel' => 'import' |
133
|
|
|
] |
134
|
|
|
); |
135
|
|
|
} elseif ($mode == 'code') { |
136
|
|
|
$html[1][] = "$add\n"; |
137
|
|
|
} |
138
|
|
|
} |
139
|
|
|
return $this; |
140
|
|
|
} |
141
|
|
|
/** |
142
|
|
|
* Including of JavaScript |
143
|
|
|
* |
144
|
|
|
* @param string|string[] $add Path to including file, or code |
145
|
|
|
* @param string $mode Can be <b>file</b> or <b>code</b> |
146
|
|
|
* |
147
|
|
|
* @return \cs\Page |
148
|
|
|
*/ |
149
|
|
|
function js ($add, $mode = 'file') { |
150
|
|
|
return $this->js_internal($add, $mode); |
151
|
|
|
} |
152
|
|
|
/** |
153
|
|
|
* @param string|string[] $add |
154
|
|
|
* @param string $mode |
155
|
|
|
* @param bool $core |
156
|
|
|
* |
157
|
|
|
* @return \cs\Page |
158
|
|
|
*/ |
159
|
|
|
protected function js_internal ($add, $mode = 'file', $core = false) { |
160
|
|
|
if (!$add) { |
161
|
|
|
return $this; |
162
|
|
|
} |
163
|
|
|
if (is_array($add)) { |
164
|
|
|
foreach (array_filter($add) as $script) { |
165
|
|
|
$this->js_internal($script, $mode, $core); |
166
|
|
|
} |
167
|
|
|
} else { |
168
|
|
|
if ($core) { |
169
|
|
|
$js = &$this->core_js; |
170
|
|
|
} else { |
171
|
|
|
$js = &$this->js; |
172
|
|
|
} |
173
|
|
|
if ($mode == 'file') { |
174
|
|
|
$js[0][] = h::script( |
175
|
|
|
[ |
176
|
|
|
'src' => $add |
177
|
|
|
] |
178
|
|
|
); |
179
|
|
|
} elseif ($mode == 'code') { |
180
|
|
|
$js[1][] = "$add\n"; |
181
|
|
|
} |
182
|
|
|
} |
183
|
|
|
return $this; |
184
|
|
|
} |
185
|
|
|
/** |
186
|
|
|
* Including of CSS |
187
|
|
|
* |
188
|
|
|
* @param string|string[] $add Path to including file, or code |
189
|
|
|
* @param string $mode Can be <b>file</b> or <b>code</b> |
190
|
|
|
* |
191
|
|
|
* @return \cs\Page |
192
|
|
|
*/ |
193
|
|
|
function css ($add, $mode = 'file') { |
194
|
|
|
return $this->css_internal($add, $mode); |
195
|
|
|
} |
196
|
|
|
/** |
197
|
|
|
* @param string|string[] $add |
198
|
|
|
* @param string $mode |
199
|
|
|
* @param bool $core |
200
|
|
|
* |
201
|
|
|
* @return \cs\Page |
202
|
|
|
*/ |
203
|
|
|
protected function css_internal ($add, $mode = 'file', $core = false) { |
204
|
|
|
if (!$add) { |
205
|
|
|
return $this; |
206
|
|
|
} |
207
|
|
|
if (is_array($add)) { |
208
|
|
|
foreach (array_filter($add) as $style) { |
209
|
|
|
$this->css_internal($style, $mode, $core); |
210
|
|
|
} |
211
|
|
|
} else { |
212
|
|
|
if ($core) { |
213
|
|
|
$css = &$this->core_css; |
214
|
|
|
} else { |
215
|
|
|
$css = &$this->css; |
216
|
|
|
} |
217
|
|
|
if ($mode == 'file') { |
218
|
|
|
$css[0][] = h::link( |
219
|
|
|
[ |
220
|
|
|
'href' => $add, |
221
|
|
|
'rel' => 'stylesheet', |
222
|
|
|
'shim-shadowdom' => true |
223
|
|
|
] |
224
|
|
|
); |
225
|
|
|
} elseif ($mode == 'code') { |
226
|
|
|
$css[1][] = "$add\n"; |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
return $this; |
230
|
|
|
} |
231
|
|
|
/** |
232
|
|
|
* Add config on page to make it available on frontend |
233
|
|
|
* |
234
|
|
|
* @param mixed $config_structure Any scalar type or array |
235
|
|
|
* @param string $target Target is property of `window` object where config will be inserted as value, nested properties like `cs.sub.prop` |
236
|
|
|
* are supported and all nested properties are created on demand. It is recommended to use sub-properties of `cs` |
237
|
|
|
* |
238
|
|
|
* @return \cs\Page |
239
|
|
|
*/ |
240
|
|
|
function config ($config_structure, $target) { |
241
|
|
|
return $this->config_internal($config_structure, $target); |
242
|
|
|
} |
243
|
|
|
/** |
244
|
|
|
* @param mixed $config_structure |
245
|
|
|
* @param string $target |
246
|
|
|
* @param bool $core |
247
|
|
|
* |
248
|
|
|
* @return \cs\Page |
249
|
|
|
*/ |
250
|
|
|
protected function config_internal ($config_structure, $target, $core = false) { |
251
|
|
|
$config = h::script( |
252
|
|
|
json_encode($config_structure, JSON_UNESCAPED_UNICODE), |
253
|
|
|
[ |
254
|
|
|
'target' => $target, |
255
|
|
|
'class' => 'cs-config', |
256
|
|
|
'type' => 'application/json' |
257
|
|
|
] |
258
|
|
|
); |
259
|
|
|
if ($core) { |
260
|
|
|
$this->core_config .= $config; |
261
|
|
|
} else { |
262
|
|
|
$this->config .= $config; |
263
|
|
|
} |
264
|
|
|
return $this; |
265
|
|
|
} |
266
|
|
|
/** |
267
|
|
|
* Getting of HTML, JS and CSS includes |
268
|
|
|
* |
269
|
|
|
* @return \cs\Page |
270
|
|
|
*/ |
271
|
|
|
protected function add_includes_on_page () { |
272
|
|
|
$Config = Config::instance(true); |
273
|
|
|
if (!$Config) { |
274
|
|
|
return $this; |
275
|
|
|
} |
276
|
|
|
/** |
277
|
|
|
* Base name for cache files |
278
|
|
|
*/ |
279
|
|
|
$this->pcache_basename = "_{$this->theme}_".Language::instance()->clang; |
280
|
|
|
/** |
281
|
|
|
* Some JS configs required by system |
282
|
|
|
*/ |
283
|
|
|
$this->add_system_configs(); |
284
|
|
|
// TODO: I hope some day we'll get rid of this sh*t :( |
285
|
|
|
$this->ie_edge(); |
286
|
|
|
$Request = Request::instance(); |
287
|
|
|
/** |
288
|
|
|
* If CSS and JavaScript compression enabled |
289
|
|
|
*/ |
290
|
|
|
if ($Config->core['cache_compress_js_css'] && !($Request->admin_path && isset($Request->query['debug']))) { |
291
|
|
|
$this->webcomponents_polyfill($Request, true); |
292
|
|
|
$includes = $this->get_includes_for_page_with_compression(); |
293
|
|
|
} else { |
294
|
|
|
$this->webcomponents_polyfill($Request, false); |
295
|
|
|
/** |
296
|
|
|
* Language translation is added explicitly only when compression is disabled, otherwise it will be in compressed JS file |
297
|
|
|
*/ |
298
|
|
|
/** |
299
|
|
|
* @var \cs\Page $this |
300
|
|
|
*/ |
301
|
|
|
$this->config_internal(Language::instance(), 'cs.Language', true); |
302
|
|
|
$this->config_internal($this->get_requirejs_paths(), 'requirejs.paths', true); |
303
|
|
|
$includes = $this->get_includes_for_page_without_compression($Config); |
304
|
|
|
} |
305
|
|
|
$this->css_internal($includes['css'], 'file', true); |
306
|
|
|
$this->js_internal($includes['js'], 'file', true); |
307
|
|
|
$this->html_internal($includes['html'], 'file', true); |
308
|
|
|
$this->add_includes_on_page_manually_added($Config); |
309
|
|
|
return $this; |
310
|
|
|
} |
311
|
|
|
/** |
312
|
|
|
* @return string[] |
313
|
|
|
*/ |
314
|
|
|
protected function get_requirejs_paths () { |
315
|
|
|
$Config = Config::instance(); |
316
|
|
|
$paths = []; |
317
|
|
|
foreach ($Config->components['modules'] as $module_name => $module_data) { |
318
|
|
|
if ($module_data['active'] == Config\Module_Properties::UNINSTALLED) { |
319
|
|
|
continue; |
320
|
|
|
} |
321
|
|
|
$this->get_requirejs_paths_add_aliases(MODULES."/$module_name", $paths); |
322
|
|
|
} |
323
|
|
|
foreach ($Config->components['plugins'] as $plugin_name) { |
324
|
|
|
$this->get_requirejs_paths_add_aliases(PLUGINS."/$plugin_name", $paths); |
325
|
|
|
} |
326
|
|
|
$directories_to_browse = [ |
327
|
|
|
DIR.'/bower_components', |
328
|
|
|
DIR.'/node_modules' |
329
|
|
|
]; |
330
|
|
|
Event::instance()->fire( |
331
|
|
|
'System/Page/requirejs', |
332
|
|
|
[ |
333
|
|
|
'paths' => &$paths, |
334
|
|
|
'directories_to_browse' => &$directories_to_browse |
335
|
|
|
] |
336
|
|
|
); |
337
|
|
|
foreach ($directories_to_browse as $dir) { |
338
|
|
|
foreach (get_files_list($dir, false, 'd', true) as $d) { |
339
|
|
|
$this->get_requirejs_paths_find_package($d, $paths); |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
return $paths; |
343
|
|
|
} |
344
|
|
|
/** |
345
|
|
|
* @param string $dir |
346
|
|
|
* @param string[] $paths |
347
|
|
|
*/ |
348
|
|
|
protected function get_requirejs_paths_add_aliases ($dir, &$paths) { |
349
|
|
|
if (is_dir("$dir/includes/js")) { |
350
|
|
|
$name = basename($dir); |
351
|
|
|
$paths[$name] = $this->absolute_path_to_relative("$dir/includes/js"); |
352
|
|
|
foreach ((array)@file_get_json("$dir/meta.json")['provide'] as $p) { |
353
|
|
|
if (strpos($p, '/') !== false) { |
354
|
|
|
$paths[$p] = $paths[$name]; |
355
|
|
|
} |
356
|
|
|
} |
357
|
|
|
} |
358
|
|
|
} |
359
|
|
|
/** |
360
|
|
|
* @param string $dir |
361
|
|
|
* @param string[] $paths |
362
|
|
|
*/ |
363
|
|
|
protected function get_requirejs_paths_find_package ($dir, &$paths) { |
364
|
|
|
$path = $this->get_requirejs_paths_find_package_bower($dir) ?: $this->get_requirejs_paths_find_package_npm($dir); |
365
|
|
|
if ($path) { |
366
|
|
|
$paths[basename($dir)] = $this->absolute_path_to_relative(substr($path, 0, -3)); |
367
|
|
|
} |
368
|
|
|
} |
369
|
|
|
/** |
370
|
|
|
* @param string $dir |
371
|
|
|
* |
372
|
|
|
* @return string |
373
|
|
|
*/ |
374
|
|
|
protected function get_requirejs_paths_find_package_bower ($dir) { |
375
|
|
|
$bower = @file_get_json("$dir/bower.json"); |
376
|
|
|
foreach (@(array)$bower['main'] as $main) { |
377
|
|
|
if (preg_match('/\.js$/', $main)) { |
378
|
|
|
$main = substr($main, 0, -3); |
379
|
|
|
// There is a chance that minified file is present |
380
|
|
|
$main = file_exists_with_extension("$dir/$main", ['min.js', 'js']); |
381
|
|
|
if ($main) { |
382
|
|
|
return $main; |
383
|
|
|
} |
384
|
|
|
} |
385
|
|
|
} |
386
|
|
|
return null; |
387
|
|
|
} |
388
|
|
|
/** |
389
|
|
|
* @param string $dir |
390
|
|
|
* |
391
|
|
|
* @return false|string |
392
|
|
|
*/ |
393
|
|
|
protected function get_requirejs_paths_find_package_npm ($dir) { |
394
|
|
|
$package = @file_get_json("$dir/package.json"); |
395
|
|
|
// If we have browser-specific declaration - use it |
396
|
|
|
$main = @$package['browser'] ?: (@$package['jspm']['main'] ?: @$package['main']); |
397
|
|
|
if (preg_match('/\.js$/', $main)) { |
398
|
|
|
$main = substr($main, 0, -3); |
399
|
|
|
} |
400
|
|
|
if ($main) { |
401
|
|
|
// There is a chance that minified file is present |
402
|
|
|
return file_exists_with_extension("$dir/$main", ['min.js', 'js']) ?: file_exists_with_extension("$dir/dist/$main", ['min.js', 'js']); |
403
|
|
|
} |
404
|
|
|
} |
405
|
|
|
/** |
406
|
|
|
* Since modules, plugins and storage directories can be (at least theoretically) moved from default location - let's do proper path conversion |
407
|
|
|
* |
408
|
|
|
* @param string|string[] $path |
409
|
|
|
* |
410
|
|
|
* @return string|string[] |
|
|
|
|
411
|
|
|
*/ |
412
|
|
|
protected function absolute_path_to_relative ($path) { |
413
|
|
|
if (is_array($path)) { |
414
|
|
|
foreach ($path as &$p) { |
415
|
|
|
$p = $this->absolute_path_to_relative($p); |
416
|
|
|
} |
417
|
|
|
return $path; |
418
|
|
|
} |
419
|
|
|
if (strpos($path, MODULES) === 0) { |
420
|
|
|
return 'components/modules'.substr($path, strlen(MODULES)); |
421
|
|
|
} |
422
|
|
|
if (strpos($path, PLUGINS) === 0) { |
423
|
|
|
return 'components/plugins'.substr($path, strlen(PLUGINS)); |
424
|
|
|
} |
425
|
|
|
if (strpos($path, STORAGE) === 0) { |
426
|
|
|
return 'storage'.substr($path, strlen(STORAGE)); |
427
|
|
|
} |
428
|
|
|
return substr($path, strlen(DIR) + 1); |
429
|
|
|
} |
430
|
|
|
/** |
431
|
|
|
* Add JS polyfills for IE/Edge |
432
|
|
|
*/ |
433
|
|
|
protected function ie_edge () { |
434
|
|
|
if (preg_match('/Trident|Edge/', Request::instance()->header('user-agent'))) { |
435
|
|
|
$this->js_internal( |
436
|
|
|
get_files_list(DIR."/includes/js/microsoft_sh*t", "/.*\\.js$/i", 'f', "includes/js/microsoft_sh*t", true), |
437
|
|
|
'file', |
438
|
|
|
true |
439
|
|
|
); |
440
|
|
|
} |
441
|
|
|
} |
442
|
|
|
/** |
443
|
|
|
* Hack: Add WebComponents Polyfill for browsers without native Shadow DOM support |
444
|
|
|
* |
445
|
|
|
* TODO: Probably, some effective User Agent-based check might be used here |
446
|
|
|
* |
447
|
|
|
* @param Request $Request |
448
|
|
|
* @param bool $with_compression |
449
|
|
|
*/ |
450
|
|
|
protected function webcomponents_polyfill ($Request, $with_compression) { |
451
|
|
|
if ($Request->cookie('shadow_dom') != 1) { |
452
|
|
|
$file = 'includes/js/WebComponents-polyfill/webcomponents-custom.min.js'; |
453
|
|
|
if ($with_compression) { |
454
|
|
|
$compressed_file = PUBLIC_CACHE.'/webcomponents.js'; |
455
|
|
|
if (!file_exists($compressed_file)) { |
456
|
|
|
$content = file_get_contents(DIR."/$file"); |
457
|
|
|
file_put_contents($compressed_file, gzencode($content, 9), LOCK_EX | FILE_BINARY); |
458
|
|
|
file_put_contents("$compressed_file.hash", substr(md5($content), 0, 5)); |
459
|
|
|
} |
460
|
|
|
$hash = file_get_contents("$compressed_file.hash"); |
461
|
|
|
$this->js_internal("storage/pcache/webcomponents.js?$hash", 'file', true); |
462
|
|
|
} else { |
463
|
|
|
$this->js_internal($file, 'file', true); |
464
|
|
|
} |
465
|
|
|
} |
466
|
|
|
} |
467
|
|
|
protected function add_system_configs () { |
468
|
|
|
$Config = Config::instance(); |
469
|
|
|
$Request = Request::instance(); |
470
|
|
|
$User = User::instance(); |
471
|
|
|
$current_module = $Request->current_module; |
472
|
|
|
$this->config_internal( |
473
|
|
|
[ |
474
|
|
|
'base_url' => $Config->base_url(), |
475
|
|
|
'current_base_url' => $Config->base_url().'/'.($Request->admin_path ? 'admin/' : '').$current_module, |
476
|
|
|
'public_key' => Core::instance()->public_key, |
477
|
|
|
'module' => $current_module, |
478
|
|
|
'in_admin' => (int)$Request->admin_path, |
479
|
|
|
'is_admin' => (int)$User->admin(), |
480
|
|
|
'is_user' => (int)$User->user(), |
481
|
|
|
'is_guest' => (int)$User->guest(), |
482
|
|
|
'password_min_length' => (int)$Config->core['password_min_length'], |
483
|
|
|
'password_min_strength' => (int)$Config->core['password_min_strength'], |
484
|
|
|
'debug' => (int)DEBUG, |
485
|
|
|
'route' => $Request->route, |
486
|
|
|
'route_path' => $Request->route_path, |
487
|
|
|
'route_ids' => $Request->route_ids |
488
|
|
|
], |
489
|
|
|
'cs', |
490
|
|
|
true |
491
|
|
|
); |
492
|
|
|
if ($User->admin()) { |
493
|
|
|
$this->config_internal((int)$Config->core['simple_admin_mode'], 'cs.simple_admin_mode', true); |
494
|
|
|
} |
495
|
|
|
} |
496
|
|
|
/** |
497
|
|
|
* @return array[] |
498
|
|
|
*/ |
499
|
|
|
protected function get_includes_for_page_with_compression () { |
500
|
|
|
/** |
501
|
|
|
* Rebuild cache if necessary |
502
|
|
|
*/ |
503
|
|
|
if (!file_exists(PUBLIC_CACHE."/$this->pcache_basename.json")) { |
504
|
|
|
$this->rebuild_cache(); |
505
|
|
|
} |
506
|
|
|
list($dependencies, $structure) = file_get_json(PUBLIC_CACHE."/$this->pcache_basename.json"); |
507
|
|
|
$system_includes = [ |
508
|
|
|
'css' => ["storage/pcache/$this->pcache_basename.css?{$structure['']['css']}"], |
509
|
|
|
'js' => ["storage/pcache/$this->pcache_basename.js?{$structure['']['js']}"], |
510
|
|
|
'html' => ["storage/pcache/$this->pcache_basename.html?{$structure['']['html']}"] |
511
|
|
|
]; |
512
|
|
|
list($includes, $dependencies_includes, $dependencies, $current_url) = $this->get_includes_prepare($dependencies, '+'); |
513
|
|
|
foreach ($structure as $filename_prefix => $hashes) { |
514
|
|
|
if (!$filename_prefix) { |
515
|
|
|
continue; |
516
|
|
|
} |
517
|
|
|
$is_dependency = $this->get_includes_is_dependency($dependencies, $filename_prefix, '+'); |
518
|
|
|
if ($is_dependency || mb_strpos($current_url, $filename_prefix) === 0) { |
519
|
|
|
foreach ($hashes as $extension => $hash) { |
520
|
|
|
if ($is_dependency) { |
521
|
|
|
$dependencies_includes[$extension][] = "storage/pcache/$filename_prefix$this->pcache_basename.$extension?$hash"; |
522
|
|
|
} else { |
523
|
|
|
$includes[$extension][] = "storage/pcache/$filename_prefix$this->pcache_basename.$extension?$hash"; |
524
|
|
|
} |
525
|
|
|
} |
526
|
|
|
} |
527
|
|
|
} |
528
|
|
|
return array_merge_recursive($system_includes, $dependencies_includes, $includes); |
529
|
|
|
} |
530
|
|
|
/** |
531
|
|
|
* @param Config $Config |
532
|
|
|
* |
533
|
|
|
* @return array[] |
534
|
|
|
*/ |
535
|
|
|
protected function get_includes_for_page_without_compression ($Config) { |
536
|
|
|
// To determine all dependencies and stuff we need `$Config` object to be already created |
537
|
|
|
if ($Config) { |
538
|
|
|
list($dependencies, $includes_map) = $this->includes_dependencies_and_map(); |
539
|
|
|
$system_includes = $includes_map['']; |
540
|
|
|
list($includes, $dependencies_includes, $dependencies, $current_url) = $this->get_includes_prepare($dependencies, '/'); |
541
|
|
|
foreach ($includes_map as $url => $local_includes) { |
542
|
|
|
if (!$url) { |
543
|
|
|
continue; |
544
|
|
|
} |
545
|
|
|
$is_dependency = $this->get_includes_is_dependency($dependencies, $url, '/'); |
546
|
|
|
if ($is_dependency) { |
547
|
|
|
$dependencies_includes = array_merge_recursive($dependencies_includes, $local_includes); |
548
|
|
|
} elseif (mb_strpos($current_url, $url) === 0) { |
549
|
|
|
$includes = array_merge_recursive($includes, $local_includes); |
550
|
|
|
} |
551
|
|
|
} |
552
|
|
|
$includes = array_merge_recursive($system_includes, $dependencies_includes, $includes); |
553
|
|
|
$includes = $this->absolute_path_to_relative($includes); |
554
|
|
|
} else { |
555
|
|
|
$includes = $this->get_includes_list(); |
556
|
|
|
} |
557
|
|
|
return $this->add_versions_hash($includes); |
558
|
|
|
} |
559
|
|
|
/** |
560
|
|
|
* @param array $dependencies |
561
|
|
|
* @param string $separator `+` or `/` |
562
|
|
|
* |
563
|
|
|
* @return array |
|
|
|
|
564
|
|
|
*/ |
565
|
|
|
protected function get_includes_prepare ($dependencies, $separator) { |
566
|
|
|
$Request = Request::instance(); |
567
|
|
|
$includes = [ |
568
|
|
|
'css' => [], |
569
|
|
|
'js' => [], |
570
|
|
|
'html' => [] |
571
|
|
|
]; |
572
|
|
|
$dependencies_includes = $includes; |
573
|
|
|
$current_module = $Request->current_module; |
574
|
|
|
/** |
575
|
|
|
* Current URL based on controller path (it better represents how page was rendered) |
576
|
|
|
*/ |
577
|
|
|
$current_url = array_slice(App::instance()->controller_path, 1); |
578
|
|
|
$current_url = ($Request->admin_path ? "admin$separator" : '')."$current_module$separator".implode($separator, $current_url); |
579
|
|
|
/** |
580
|
|
|
* Narrow the dependencies to current module only |
581
|
|
|
*/ |
582
|
|
|
$dependencies = array_merge( |
583
|
|
|
isset($dependencies[$current_module]) ? $dependencies[$current_module] : [], |
584
|
|
|
$dependencies['System'] |
585
|
|
|
); |
586
|
|
|
return [$includes, $dependencies_includes, $dependencies, $current_url]; |
587
|
|
|
} |
588
|
|
|
/** |
589
|
|
|
* @param array $dependencies |
590
|
|
|
* @param string $url |
591
|
|
|
* @param string $separator `+` or `/` |
592
|
|
|
* |
593
|
|
|
* @return bool |
594
|
|
|
*/ |
595
|
|
|
protected function get_includes_is_dependency ($dependencies, $url, $separator) { |
596
|
|
|
$url_exploded = explode($separator, $url); |
597
|
|
|
/** @noinspection NestedTernaryOperatorInspection */ |
598
|
|
|
$url_module = $url_exploded[0] != 'admin' ? $url_exploded[0] : (@$url_exploded[1] ?: ''); |
599
|
|
|
$Request = Request::instance(); |
600
|
|
|
return |
601
|
|
|
$url_module !== Config::SYSTEM_MODULE && |
602
|
|
|
in_array($url_module, $dependencies) && |
603
|
|
|
( |
604
|
|
|
$Request->admin_path || $Request->admin_path == ($url_exploded[0] == 'admin') |
605
|
|
|
); |
606
|
|
|
} |
607
|
|
|
protected function add_versions_hash ($includes) { |
608
|
|
|
$content = ''; |
609
|
|
|
foreach (get_files_list(MODULES, false, 'd') as $module) { |
610
|
|
|
if (file_exists(MODULES."/$module/meta.json")) { |
611
|
|
|
$content .= file_get_contents(MODULES."/$module/meta.json"); |
612
|
|
|
} |
613
|
|
|
} |
614
|
|
|
foreach (get_files_list(PLUGINS, false, 'd') as $plugin) { |
615
|
|
|
if (file_exists(PLUGINS."/$plugin/meta.json")) { |
616
|
|
|
$content .= file_get_contents(PLUGINS."/$plugin/meta.json"); |
617
|
|
|
} |
618
|
|
|
} |
619
|
|
|
$hash = substr(md5($content), 0, 5); |
620
|
|
|
foreach ($includes as &$files) { |
621
|
|
|
foreach ($files as &$file) { |
622
|
|
|
$file .= "?$hash"; |
623
|
|
|
} |
624
|
|
|
unset($file); |
625
|
|
|
} |
626
|
|
|
return $includes; |
627
|
|
|
} |
628
|
|
|
/** |
629
|
|
|
* @param Config $Config |
630
|
|
|
*/ |
631
|
|
|
protected function add_includes_on_page_manually_added ($Config) { |
632
|
|
|
foreach (['core_html', 'core_js', 'core_css', 'html', 'js', 'css'] as $type) { |
633
|
|
|
foreach ($this->$type as &$elements) { |
634
|
|
|
$elements = implode('', array_unique($elements)); |
635
|
|
|
} |
636
|
|
|
unset($elements); |
637
|
|
|
} |
638
|
|
|
$this->Head .= |
639
|
|
|
$this->core_config. |
640
|
|
|
$this->config. |
641
|
|
|
$this->core_css[0].$this->css[0]. |
642
|
|
|
h::style($this->core_css[1].$this->css[1] ?: false); |
643
|
|
|
$js_html_insert_to = $Config->core['put_js_after_body'] ? 'post_Body' : 'Head'; |
644
|
|
|
$js_html = |
645
|
|
|
$this->core_js[0]. |
646
|
|
|
h::script($this->core_js[1] ?: false). |
647
|
|
|
$this->js[0]. |
648
|
|
|
h::script($this->js[1] ?: false). |
649
|
|
|
$this->core_html[0].$this->html[0]. |
650
|
|
|
$this->core_html[1].$this->html[1]; |
651
|
|
|
$this->$js_html_insert_to .= $js_html; |
652
|
|
|
} |
653
|
|
|
/** |
654
|
|
|
* Getting of HTML, JS and CSS files list to be included |
655
|
|
|
* |
656
|
|
|
* @param bool $absolute If <i>true</i> - absolute paths to files will be returned |
657
|
|
|
* |
658
|
|
|
* @return array |
659
|
|
|
*/ |
660
|
|
|
protected function get_includes_list ($absolute = false) { |
661
|
|
|
$theme_dir = THEMES."/$this->theme"; |
662
|
|
|
$theme_pdir = "themes/$this->theme"; |
663
|
|
|
$get_files = function ($dir, $prefix_path) { |
664
|
|
|
$extension = basename($dir); |
665
|
|
|
$list = get_files_list($dir, "/.*\\.$extension$/i", 'f', $prefix_path, true, 'name', '!include') ?: []; |
666
|
|
|
sort($list); |
667
|
|
|
return $list; |
668
|
|
|
}; |
669
|
|
|
/** |
670
|
|
|
* Get includes of system and theme |
671
|
|
|
*/ |
672
|
|
|
$includes = []; |
673
|
|
|
foreach (['html', 'js', 'css'] as $type) { |
674
|
|
|
$includes[$type] = array_merge( |
675
|
|
|
$get_files(DIR."/includes/$type", $absolute ? true : "includes/$type"), |
676
|
|
|
$get_files("$theme_dir/$type", $absolute ? true : "$theme_pdir/$type") |
677
|
|
|
); |
678
|
|
|
} |
679
|
|
|
unset($theme_dir, $theme_pdir); |
680
|
|
|
$Config = Config::instance(); |
681
|
|
|
foreach ($Config->components['modules'] as $module_name => $module_data) { |
682
|
|
|
if ($module_data['active'] == Config\Module_Properties::UNINSTALLED) { |
683
|
|
|
continue; |
684
|
|
|
} |
685
|
|
|
foreach (['html', 'js', 'css'] as $type) { |
686
|
|
|
/** @noinspection SlowArrayOperationsInLoopInspection */ |
687
|
|
|
$includes[$type] = array_merge( |
688
|
|
|
$includes[$type], |
689
|
|
|
$get_files(MODULES."/$module_name/includes/$type", $absolute ? true : "components/modules/$module_name/includes/$type") |
690
|
|
|
); |
691
|
|
|
} |
692
|
|
|
} |
693
|
|
|
foreach ($Config->components['plugins'] as $plugin_name) { |
694
|
|
|
foreach (['html', 'js', 'css'] as $type) { |
695
|
|
|
/** @noinspection SlowArrayOperationsInLoopInspection */ |
696
|
|
|
$includes[$type] = array_merge( |
697
|
|
|
$includes[$type], |
698
|
|
|
$get_files(PLUGINS."/$plugin_name/includes/$type", $absolute ? true : "components/plugins/$plugin_name/includes/$type") |
699
|
|
|
); |
700
|
|
|
} |
701
|
|
|
} |
702
|
|
|
return $includes; |
703
|
|
|
} |
704
|
|
|
/** |
705
|
|
|
* Rebuilding of HTML, JS and CSS cache |
706
|
|
|
* |
707
|
|
|
* @return \cs\Page |
708
|
|
|
*/ |
709
|
|
|
protected function rebuild_cache () { |
710
|
|
|
list($dependencies, $includes_map) = $this->includes_dependencies_and_map(); |
711
|
|
|
$structure = []; |
712
|
|
|
foreach ($includes_map as $filename_prefix => $includes) { |
713
|
|
|
// We replace `/` by `+` to make it suitable for filename |
714
|
|
|
$filename_prefix = str_replace('/', '+', $filename_prefix); |
715
|
|
|
$structure[$filename_prefix] = $this->create_cached_includes_files($filename_prefix, $includes); |
716
|
|
|
} |
717
|
|
|
unset($includes_map, $filename_prefix, $includes); |
718
|
|
|
file_put_json( |
719
|
|
|
PUBLIC_CACHE."/$this->pcache_basename.json", |
720
|
|
|
[$dependencies, $structure] |
721
|
|
|
); |
722
|
|
|
unset($structure); |
723
|
|
|
Event::instance()->fire('System/Page/rebuild_cache'); |
724
|
|
|
return $this; |
725
|
|
|
} |
726
|
|
|
/** |
727
|
|
|
* Creates cached version of given HTML, JS and CSS files. |
728
|
|
|
* Resulting file name consists of <b>$filename_prefix</b> and <b>$this->pcache_basename</b> |
729
|
|
|
* |
730
|
|
|
* @param string $filename_prefix |
731
|
|
|
* @param array $includes Array of paths to files, may have keys: <b>css</b> and/or <b>js</b> and/or <b>html</b> |
732
|
|
|
* |
733
|
|
|
* @return array |
734
|
|
|
*/ |
735
|
|
|
protected function create_cached_includes_files ($filename_prefix, $includes) { |
736
|
|
|
$cache_hash = []; |
737
|
|
|
/** @noinspection AlterInForeachInspection */ |
738
|
|
|
foreach ($includes as $extension => $files) { |
739
|
|
|
$content = $this->create_cached_includes_files_process_files( |
740
|
|
|
$extension, |
741
|
|
|
$filename_prefix, |
742
|
|
|
$files |
743
|
|
|
); |
744
|
|
|
file_put_contents(PUBLIC_CACHE."/$filename_prefix$this->pcache_basename.$extension", gzencode($content, 9), LOCK_EX | FILE_BINARY); |
745
|
|
|
$cache_hash[$extension] = substr(md5($content), 0, 5); |
746
|
|
|
} |
747
|
|
|
return $cache_hash; |
748
|
|
|
} |
749
|
|
|
protected function create_cached_includes_files_process_files ($extension, $filename_prefix, $files) { |
750
|
|
|
$content = ''; |
751
|
|
|
switch ($extension) { |
752
|
|
|
/** |
753
|
|
|
* Insert external elements into resulting css file. |
754
|
|
|
* It is needed, because those files will not be copied into new destination of resulting css file. |
755
|
|
|
*/ |
756
|
|
|
case 'css': |
757
|
|
|
$callback = function ($content, $file) { |
758
|
|
|
return |
759
|
|
|
$content. |
760
|
|
|
Includes_processing::css( |
761
|
|
|
file_get_contents($file), |
762
|
|
|
$file |
763
|
|
|
); |
764
|
|
|
}; |
765
|
|
|
break; |
766
|
|
|
/** |
767
|
|
|
* Combine css and js files for Web Component into resulting files in order to optimize loading process |
768
|
|
|
*/ |
769
|
|
|
case 'html': |
770
|
|
|
/** |
771
|
|
|
* For CSP-compatible HTML files we need to know destination to put there additional JS/CSS files |
772
|
|
|
*/ |
773
|
|
|
$destination = Config::instance()->core['vulcanization'] ? false : PUBLIC_CACHE; |
774
|
|
|
$callback = function ($content, $file) use ($filename_prefix, $destination) { |
775
|
|
|
return |
776
|
|
|
$content. |
777
|
|
|
Includes_processing::html( |
778
|
|
|
file_get_contents($file), |
779
|
|
|
$file, |
780
|
|
|
"$filename_prefix$this->pcache_basename-".basename($file).'+'.substr(md5($file), 0, 5), |
781
|
|
|
$destination |
782
|
|
|
); |
783
|
|
|
}; |
784
|
|
|
break; |
785
|
|
|
case 'js': |
786
|
|
|
$callback = function ($content, $file) { |
787
|
|
|
return |
788
|
|
|
$content. |
789
|
|
|
Includes_processing::js(file_get_contents($file)); |
790
|
|
|
}; |
791
|
|
|
if ($filename_prefix == '') { |
792
|
|
|
$content = 'window.cs={Language:'._json_encode(Language::instance()).'};'; |
793
|
|
|
$content .= 'window.requirejs={paths:'._json_encode($this->get_requirejs_paths()).'};'; |
794
|
|
|
} |
795
|
|
|
} |
796
|
|
|
/** @noinspection PhpUndefinedVariableInspection */ |
797
|
|
|
return array_reduce(array_filter($files, 'file_exists'), $callback, $content); |
798
|
|
|
} |
799
|
|
|
/** |
800
|
|
|
* 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 |
801
|
|
|
* |
802
|
|
|
* @return array[] [$dependencies, $includes_map] |
803
|
|
|
*/ |
804
|
|
|
protected function includes_dependencies_and_map () { |
805
|
|
|
/** |
806
|
|
|
* Get all includes |
807
|
|
|
*/ |
808
|
|
|
$all_includes = $this->get_includes_list(true); |
809
|
|
|
$includes_map = []; |
810
|
|
|
/** |
811
|
|
|
* Array [package => [list of packages it depends on]] |
812
|
|
|
*/ |
813
|
|
|
$dependencies = []; |
814
|
|
|
$functionalities = []; |
815
|
|
|
/** |
816
|
|
|
* According to components's maps some files should be included only on specific pages. |
817
|
|
|
* Here we read this rules, and remove from whole includes list such items, that should be included only on specific pages. |
818
|
|
|
* Also collect dependencies. |
819
|
|
|
*/ |
820
|
|
|
$Config = Config::instance(); |
821
|
|
|
foreach ($Config->components['modules'] as $module_name => $module_data) { |
822
|
|
|
if ($module_data['active'] == Config\Module_Properties::UNINSTALLED) { |
823
|
|
|
continue; |
824
|
|
|
} |
825
|
|
|
if (file_exists(MODULES."/$module_name/meta.json")) { |
826
|
|
|
$this->process_meta( |
827
|
|
|
file_get_json(MODULES."/$module_name/meta.json"), |
828
|
|
|
$dependencies, |
829
|
|
|
$functionalities |
830
|
|
|
); |
831
|
|
|
} |
832
|
|
|
if (file_exists(MODULES."/$module_name/includes/map.json")) { |
833
|
|
|
$this->process_map( |
834
|
|
|
file_get_json_nocomments(MODULES."/$module_name/includes/map.json"), |
835
|
|
|
MODULES."/$module_name/includes", |
836
|
|
|
$includes_map, |
837
|
|
|
$all_includes |
838
|
|
|
); |
839
|
|
|
} |
840
|
|
|
} |
841
|
|
|
unset($module_name, $module_data); |
842
|
|
|
foreach ($Config->components['plugins'] as $plugin_name) { |
843
|
|
|
if (file_exists(PLUGINS."/$plugin_name/meta.json")) { |
844
|
|
|
$this->process_meta( |
845
|
|
|
file_get_json(PLUGINS."/$plugin_name/meta.json"), |
846
|
|
|
$dependencies, |
847
|
|
|
$functionalities |
848
|
|
|
); |
849
|
|
|
} |
850
|
|
|
if (file_exists(PLUGINS."/$plugin_name/includes/map.json")) { |
851
|
|
|
$this->process_map( |
852
|
|
|
file_get_json_nocomments(PLUGINS."/$plugin_name/includes/map.json"), |
853
|
|
|
PLUGINS."/$plugin_name/includes", |
854
|
|
|
$includes_map, |
855
|
|
|
$all_includes |
856
|
|
|
); |
857
|
|
|
} |
858
|
|
|
} |
859
|
|
|
unset($plugin_name); |
860
|
|
|
/** |
861
|
|
|
* For consistency |
862
|
|
|
*/ |
863
|
|
|
$includes_map[''] = $all_includes; |
864
|
|
|
Event::instance()->fire( |
865
|
|
|
'System/Page/includes_dependencies_and_map', |
866
|
|
|
[ |
867
|
|
|
'dependencies' => &$dependencies, |
868
|
|
|
'includes_map' => &$includes_map |
869
|
|
|
] |
870
|
|
|
); |
871
|
|
|
$dependencies = $this->normalize_dependencies($dependencies, $functionalities); |
872
|
|
|
$includes_map = $this->clean_includes_arrays_without_files($dependencies, $includes_map); |
873
|
|
|
$dependencies = array_map('array_values', $dependencies); |
874
|
|
|
$dependencies = array_filter($dependencies); |
875
|
|
|
return [$dependencies, $includes_map]; |
876
|
|
|
} |
877
|
|
|
/** |
878
|
|
|
* Process meta information and corresponding entries to dependencies and functionalities |
879
|
|
|
* |
880
|
|
|
* @param array $meta |
881
|
|
|
* @param array $dependencies |
882
|
|
|
* @param array $functionalities |
883
|
|
|
*/ |
884
|
|
|
protected function process_meta ($meta, &$dependencies, &$functionalities) { |
885
|
|
|
$package = $meta['package']; |
886
|
|
|
if (isset($meta['require'])) { |
887
|
|
|
foreach ((array)$meta['require'] as $r) { |
888
|
|
|
/** |
889
|
|
|
* Get only name of package or functionality |
890
|
|
|
*/ |
891
|
|
|
$r = preg_split('/[=<>]/', $r, 2)[0]; |
892
|
|
|
$dependencies[$package][] = $r; |
893
|
|
|
} |
894
|
|
|
} |
895
|
|
|
if (isset($meta['optional'])) { |
896
|
|
|
foreach ((array)$meta['optional'] as $o) { |
897
|
|
|
/** |
898
|
|
|
* Get only name of package or functionality |
899
|
|
|
*/ |
900
|
|
|
$o = preg_split('/[=<>]/', $o, 2)[0]; |
901
|
|
|
$dependencies[$package][] = $o; |
902
|
|
|
} |
903
|
|
|
unset($o); |
904
|
|
|
} |
905
|
|
|
if (isset($meta['provide'])) { |
906
|
|
|
foreach ((array)$meta['provide'] as $p) { |
907
|
|
|
/** |
908
|
|
|
* If provides sub-functionality for other component (for instance, `Blog/post_patch`) - inverse "providing" to "dependency" |
909
|
|
|
* Otherwise it is just functionality alias to package name |
910
|
|
|
*/ |
911
|
|
|
if (strpos($p, '/') !== false) { |
912
|
|
|
/** |
913
|
|
|
* Get name of package or functionality |
914
|
|
|
*/ |
915
|
|
|
$p = explode('/', $p)[0]; |
916
|
|
|
$dependencies[$p][] = $package; |
917
|
|
|
} else { |
918
|
|
|
$functionalities[$p] = $package; |
919
|
|
|
} |
920
|
|
|
} |
921
|
|
|
unset($p); |
922
|
|
|
} |
923
|
|
|
} |
924
|
|
|
/** |
925
|
|
|
* Process map structure, fill includes map and remove files from list of all includes (remaining files will be included on all pages) |
926
|
|
|
* |
927
|
|
|
* @param array $map |
928
|
|
|
* @param string $includes_dir |
929
|
|
|
* @param array $includes_map |
930
|
|
|
* @param array $all_includes |
931
|
|
|
*/ |
932
|
|
|
protected function process_map ($map, $includes_dir, &$includes_map, &$all_includes) { |
933
|
|
|
foreach ($map as $path => $files) { |
934
|
|
|
foreach ((array)$files as $file) { |
935
|
|
|
$extension = file_extension($file); |
936
|
|
|
if (in_array($extension, ['css', 'js', 'html'])) { |
937
|
|
|
$file = "$includes_dir/$extension/$file"; |
938
|
|
|
$includes_map[$path][$extension][] = $file; |
939
|
|
|
$all_includes[$extension] = array_diff($all_includes[$extension], [$file]); |
940
|
|
|
} else { |
941
|
|
|
$file = rtrim($file, '*'); |
942
|
|
|
/** |
943
|
|
|
* Wildcard support, it is possible to specify just path prefix and all files with this prefix will be included |
944
|
|
|
*/ |
945
|
|
|
$found_files = array_filter( |
946
|
|
|
get_files_list($includes_dir, '/.*\.(css|js|html)$/i', 'f', '', true, 'name', '!include') ?: [], |
947
|
|
|
function ($f) use ($file) { |
948
|
|
|
// We need only files with specified mask and only those located in directory that corresponds to file's extension |
949
|
|
|
return preg_match("#^(css|js|html)/$file.*\\1$#i", $f); |
950
|
|
|
} |
951
|
|
|
); |
952
|
|
|
// Drop first level directory |
953
|
|
|
$found_files = _preg_replace('#^[^/]+/(.*)#', '$1', $found_files); |
954
|
|
|
$this->process_map([$path => $found_files], $includes_dir, $includes_map, $all_includes); |
955
|
|
|
} |
956
|
|
|
} |
957
|
|
|
} |
958
|
|
|
} |
959
|
|
|
/** |
960
|
|
|
* Replace functionalities by real packages names, take into account recursive dependencies |
961
|
|
|
* |
962
|
|
|
* @param array $dependencies |
963
|
|
|
* @param array $functionalities |
964
|
|
|
* |
965
|
|
|
* @return array |
966
|
|
|
*/ |
967
|
|
|
protected function normalize_dependencies ($dependencies, $functionalities) { |
968
|
|
|
/** |
969
|
|
|
* First of all remove packages without any dependencies |
970
|
|
|
*/ |
971
|
|
|
$dependencies = array_filter($dependencies); |
972
|
|
|
/** |
973
|
|
|
* First round, process aliases among keys |
974
|
|
|
*/ |
975
|
|
|
foreach (array_keys($dependencies) as $d) { |
976
|
|
|
if (isset($functionalities[$d])) { |
977
|
|
|
$package = $functionalities[$d]; |
978
|
|
|
/** |
979
|
|
|
* Add dependencies to existing package dependencies |
980
|
|
|
*/ |
981
|
|
|
foreach ($dependencies[$d] as $dependency) { |
982
|
|
|
$dependencies[$package][] = $dependency; |
983
|
|
|
} |
984
|
|
|
/** |
985
|
|
|
* Drop alias |
986
|
|
|
*/ |
987
|
|
|
unset($dependencies[$d]); |
988
|
|
|
} |
989
|
|
|
} |
990
|
|
|
unset($d, $dependency); |
991
|
|
|
/** |
992
|
|
|
* Second round, process aliases among dependencies |
993
|
|
|
*/ |
994
|
|
|
foreach ($dependencies as &$depends_on) { |
995
|
|
|
foreach ($depends_on as &$dependency) { |
996
|
|
|
if (isset($functionalities[$dependency])) { |
997
|
|
|
$dependency = $functionalities[$dependency]; |
998
|
|
|
} |
999
|
|
|
} |
1000
|
|
|
} |
1001
|
|
|
unset($depends_on, $dependency); |
1002
|
|
|
/** |
1003
|
|
|
* Third round, process recursive dependencies |
1004
|
|
|
*/ |
1005
|
|
|
foreach ($dependencies as &$depends_on) { |
1006
|
|
|
foreach ($depends_on as &$dependency) { |
1007
|
|
|
if ($dependency != 'System' && isset($dependencies[$dependency])) { |
1008
|
|
|
foreach (array_diff($dependencies[$dependency], $depends_on) as $new_dependency) { |
1009
|
|
|
$depends_on[] = $new_dependency; |
1010
|
|
|
} |
1011
|
|
|
} |
1012
|
|
|
} |
1013
|
|
|
} |
1014
|
|
|
return array_map('array_unique', $dependencies); |
1015
|
|
|
} |
1016
|
|
|
/** |
1017
|
|
|
* Includes array is composed from dependencies and sometimes dependencies doesn't have any files, so we'll clean that |
1018
|
|
|
* |
1019
|
|
|
* @param array $dependencies |
1020
|
|
|
* @param array $includes_map |
1021
|
|
|
* |
1022
|
|
|
* @return array |
1023
|
|
|
*/ |
1024
|
|
|
protected function clean_includes_arrays_without_files ($dependencies, $includes_map) { |
1025
|
|
|
foreach ($dependencies as &$depends_on) { |
1026
|
|
|
foreach ($depends_on as $index => &$dependency) { |
1027
|
|
|
if (!isset($includes_map[$dependency])) { |
1028
|
|
|
unset($depends_on[$index]); |
1029
|
|
|
} |
1030
|
|
|
} |
1031
|
|
|
unset($dependency); |
1032
|
|
|
} |
1033
|
|
|
return $includes_map; |
1034
|
|
|
} |
1035
|
|
|
} |
1036
|
|
|
|
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.If the return type contains the type array, this check recommends the use of a more specific type like
String[]
orarray<String>
.