1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Modules; |
4
|
|
|
|
5
|
|
|
use Common\Hooker\Pimp; |
6
|
|
|
use Exception; |
7
|
|
|
use SN; |
8
|
|
|
use template; |
9
|
|
|
|
10
|
|
|
class sn_module { |
11
|
|
|
// Manifest params |
12
|
|
|
const M_REQUIRE = 'require'; |
13
|
|
|
const M_ROOT_RELATIVE = 'root_relative'; |
14
|
|
|
const M_LOAD_ORDER = 'load_order'; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* SN version in which module was committed. Can be treated as version in which module guaranteed to work |
18
|
|
|
* @var string $versionCommitted |
19
|
|
|
*/ |
20
|
|
|
public $versionCommitted = '#43a15.20#'; |
21
|
|
|
|
22
|
|
|
public $manifest = [ |
23
|
|
|
'package' => 'core', |
24
|
|
|
'name' => 'Modules\sn_module', |
25
|
|
|
'version' => '1c0', |
26
|
|
|
'copyright' => 'Project "SuperNova.WS" #43a15.20# copyright © 2009-2017 Gorlum', |
27
|
|
|
|
28
|
|
|
self::M_LOAD_ORDER => MODULE_LOAD_ORDER_DEFAULT, |
29
|
|
|
|
30
|
|
|
self::M_REQUIRE => [], |
31
|
|
|
self::M_ROOT_RELATIVE => '', |
32
|
|
|
|
33
|
|
|
'installed' => true, |
34
|
|
|
'active' => true, |
35
|
|
|
|
36
|
|
|
// 'constants' array - contents of this array would be instaled into engine |
37
|
|
|
'constants' => [ |
38
|
|
|
// 'UNIT_STRUCTURE_NEW' => 999999, |
|
|
|
|
39
|
|
|
], |
40
|
|
|
|
41
|
|
|
'vars' => [], // Just a placeholder. vars assigned via special method __assign_vars(). Need 'cause of new constants that can be defined within module. See below |
42
|
|
|
|
43
|
|
|
// 'functions' array - this functions would be installed as hooks |
44
|
|
|
// Key: overwritable function name to replace |
45
|
|
|
// Value: which method to use. Format: [*][<object_name>][.]<method> |
46
|
|
|
// '*' means that new function would replace old one |
47
|
|
|
// If object_name is ommited but "." is present - hook linked to global function |
48
|
|
|
// If only "method" present - overwritable linked to appropriate method of current object |
49
|
|
|
// Function/Method should be accessible on module init |
50
|
|
|
'functions' => [ |
51
|
|
|
// 'test_object_test_method' => 'test_object.test_method', |
|
|
|
|
52
|
|
|
// 'test_function' => '.my_test_function', |
53
|
|
|
// 'this_object_test_method' => 'test_method', |
54
|
|
|
], |
55
|
|
|
|
56
|
|
|
// 'menu' array - this menu items would be merged into main game menu |
57
|
|
|
// Array element almost identical to $sn_menu with additional param 'LOCATION'. |
58
|
|
|
// 'LOCATION' => '-news', // Special atrtribute for modules |
|
|
|
|
59
|
|
|
// [-|+][<menu_item_id>] |
|
|
|
|
60
|
|
|
// <menu_item_id> identifies menu item aginst new menu item would be placed. When ommited new item placed against whole menu |
61
|
|
|
// -/+ indicates that new item should be placed before/after identified menu item (or whole menu). If ommited and menu item exists - new item will replace previous one |
62
|
|
|
// Empty or non-existent LOCATION equivalent to '+' - place item at end of menu |
63
|
|
|
// Non-existent menu_item_id treated as ommited |
64
|
|
|
'menu' => [], |
65
|
|
|
|
66
|
|
|
// 'page' array - defines pages which will handle this module and appropriate handlers |
67
|
|
|
'page' => [], |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* 'mvc' subarray |
71
|
|
|
* [ |
72
|
|
|
* FIELD_MODEL => |
73
|
|
|
* FIELD_VIEW => |
74
|
|
|
* MVC_OPTIONS => |
75
|
|
|
* ] |
76
|
|
|
*/ |
77
|
|
|
'mvc' => [], |
78
|
|
|
]; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* New way to add functions instead of manifest['functions'] |
82
|
|
|
* |
83
|
|
|
* [ |
84
|
|
|
* (string)$functionName => [ |
85
|
|
|
* (callable)$callable, |
86
|
|
|
* (string)'methodName', // Local method name aka $this->methodName |
87
|
|
|
* (callable array)[$this|objectName, 'methodName'], // Callable array |
88
|
|
|
* ], |
89
|
|
|
* ] |
90
|
|
|
* |
91
|
|
|
* @var array $functions |
92
|
|
|
*/ |
93
|
|
|
protected $functions = []; |
94
|
|
|
|
95
|
|
|
protected $hooks = []; |
96
|
|
|
|
97
|
|
|
protected $config = array(); |
98
|
|
|
|
99
|
|
|
protected $module_full_class_path = __FILE__; |
100
|
|
|
|
101
|
|
|
protected $filename = ''; |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* @param string $functionName |
105
|
|
|
* @param callable $callable |
106
|
|
|
*/ |
107
|
|
|
public function addFunctionHook($functionName, $callable) { |
108
|
|
|
$this->functions[$functionName][] = $callable; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Динамическое назначение переменных |
113
|
|
|
* |
114
|
|
|
* Актуально, когда записываемые данные зависят от статуса игры |
115
|
|
|
* Например - назначаются константы внутри модуля |
116
|
|
|
* |
117
|
|
|
* @return array |
118
|
|
|
*/ |
119
|
|
|
protected function __assign_vars() { |
120
|
|
|
return array(/* |
|
|
|
|
121
|
|
|
'sn_data' => array( |
122
|
|
|
UNIT_STRUCTURE_NEW => array( // Will honor new constants |
123
|
|
|
'name' => 'robot_factory', |
124
|
|
|
'type' => UNIT_STRUCTURE, |
125
|
|
|
'location' => LOC_PLANET, |
126
|
|
|
'cost' => array( |
127
|
|
|
RES_METAL => 400, |
128
|
|
|
RES_CRYSTAL => 120, |
129
|
|
|
RES_DEUTERIUM => 200, |
130
|
|
|
RES_ENERGY => 0, |
131
|
|
|
'factor' => 2, |
132
|
|
|
), |
133
|
|
|
'metal' => 400, |
134
|
|
|
'crystal' => 120, |
135
|
|
|
'deuterium' => 200, |
136
|
|
|
'energy' => 0, |
137
|
|
|
'factor' => 2, |
138
|
|
|
), |
139
|
|
|
), |
140
|
|
|
*/ |
141
|
|
|
); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* sn_module constructor. |
146
|
|
|
* |
147
|
|
|
* @param string $filename |
148
|
|
|
*/ |
149
|
|
|
public function __construct($filename = __FILE__) { |
150
|
|
|
$this->filename = $filename; |
151
|
|
|
|
152
|
|
|
// Getting module PHP class name |
153
|
|
|
$class_module_name = get_called_class(); |
154
|
|
|
|
155
|
|
|
// Validating source settings. Some fields are mandatory in each manifest |
156
|
|
|
// Should be removed when manifest would be parsed to separate fields |
157
|
|
|
foreach (['name', 'package', 'installed', 'active', 'version'] as $mandatoryManifest) { |
158
|
|
|
if (!array_key_exists($mandatoryManifest, $this->manifest)) { |
159
|
|
|
throw new Exception('{ There is no mandatory field "' . $mandatoryManifest . '" in manifest of module } "' . $class_module_name . '"'); |
160
|
|
|
} |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
// Getting module root relative to SN |
164
|
|
|
$this->manifest[static::M_ROOT_RELATIVE] = str_replace( |
165
|
|
|
[SN_ROOT_PHYSICAL, basename($this->filename)], |
166
|
|
|
'', |
167
|
|
|
str_replace('\\', '/', $this->filename) |
168
|
|
|
); |
169
|
|
|
|
170
|
|
|
// TODO: Load configuration from DB. Manifest setting |
171
|
|
|
// Trying to load configuration from file |
172
|
|
|
$config_exists = false; |
173
|
|
|
// Конфигурация может лежать в config_path в манифеста или в корне модуля |
174
|
|
|
if (isset($this->manifest['config_path']) && file_exists($config_filename = $this->manifest['config_path'] . '/config.php')) { |
175
|
|
|
$config_exists = true; |
176
|
|
|
} elseif (file_exists($config_filename = dirname($filename) . '/config.php')) { |
177
|
|
|
$config_exists = true; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
if ($config_exists) { |
181
|
|
|
include($config_filename); |
182
|
|
|
$module_config_array = $class_module_name . '_config'; |
183
|
|
|
$this->config = $$module_config_array; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
// Registering classes with autoloader |
187
|
|
|
\Core\Autoloader::register($this->getRootRelative() . 'classes/'); |
188
|
|
|
|
189
|
|
|
// TODO - currently not possible because each module is not a service |
190
|
|
|
// When it's done - remove double registration from loadModulesFromDirectory() |
191
|
|
|
// Registering module in manager |
|
|
|
|
192
|
|
|
// SN::$gc->modules->registerModule($class_module_name, $this); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
protected function __patch_menu(&$sn_menu_extra, &$menu_patch) { |
196
|
|
|
if (isset($menu_patch) && is_array($menu_patch) && !empty($menu_patch)) { |
197
|
|
|
foreach ($menu_patch as $menu_item_name => $menu_item_data) { |
198
|
|
|
$sn_menu_extra[$menu_item_name] = $menu_item_data; |
199
|
|
|
} |
200
|
|
|
} |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* |
206
|
|
|
*/ |
207
|
|
|
public function initialize() { |
208
|
|
|
// Checking module status - is it installed and active |
209
|
|
|
$this->check_status(); |
210
|
|
|
if (!$this->manifest['active']) { |
211
|
|
|
return; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
// Setting constants - if any |
215
|
|
|
if (isset($this->manifest['constants']) && is_array($this->manifest['constants']) && !empty($this->manifest['constants'])) { |
216
|
|
|
foreach ($this->manifest['constants'] as $constant_name => $constant_value) { |
217
|
|
|
defined($constant_name) or define($constant_name, $constant_value); |
|
|
|
|
218
|
|
|
} |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
// Adding vars - if any |
222
|
|
|
// Due to possible introduce of new constants in previous step vars is assigned via special method to honor new constants |
223
|
|
|
// Assignation can work with simple variables and with multidimensional arrays - for ex. 'sn_data[groups][test]' |
224
|
|
|
// New values from module variables will overwrite previous values (for root variables) and array elements with corresponding indexes (for arrays) |
225
|
|
|
// Constants as array indexes are honored - it's make valid such declarations as 'sn_data[ques][QUE_STRUCTURES]' |
226
|
|
|
$this->manifest['vars'] = $this->__assign_vars(); |
227
|
|
|
if (!empty($this->manifest['vars'])) { |
228
|
|
|
$vars_assigned = array(); |
229
|
|
|
foreach ($this->manifest['vars'] as $var_name => $var_value) { |
230
|
|
|
$sub_vars = explode('[', str_replace(']', '', $var_name)); |
231
|
|
|
$var_name = $sub_vars[0]; |
232
|
|
|
|
233
|
|
|
if (!isset($vars_assigned[$var_name])) { |
234
|
|
|
$vars_assigned[$var_name] = true; |
235
|
|
|
global $$var_name; |
|
|
|
|
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
$pointer = &$$var_name; |
239
|
|
|
if (($n = count($sub_vars)) > 1) { |
240
|
|
|
for ($i = 1; $i < $n; $i++) { |
241
|
|
|
if (defined($sub_vars[$i])) { |
242
|
|
|
$sub_vars[$i] = constant($sub_vars[$i]); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
if (!isset($pointer[$sub_vars[$i]]) && $i != $n) { |
246
|
|
|
$pointer[$sub_vars[$i]] = array(); |
247
|
|
|
} |
248
|
|
|
$pointer = &$pointer[$sub_vars[$i]]; |
249
|
|
|
} |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
if (!isset($pointer) || !is_array($pointer)) { |
253
|
|
|
$pointer = $var_value; |
254
|
|
|
} elseif (is_array($$var_name)) { |
255
|
|
|
$pointer = array_merge_recursive_numeric($pointer, $var_value); |
256
|
|
|
} |
257
|
|
|
} |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
// Overriding function if any |
261
|
|
|
global $functions; |
|
|
|
|
262
|
|
|
sn_sys_handler_add($functions, $this->manifest['functions'], $this); |
263
|
|
|
|
264
|
|
|
foreach ($this->functions as $functionName => $callableList) { |
265
|
|
|
!is_array($callableList) ? $callableList = [$callableList] : false; |
266
|
|
|
foreach ($callableList as $callable) { |
267
|
|
|
sys_handler_add_one($functions, $functionName, $callable, static::class, ''); |
268
|
|
|
} |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
$this->registerHooks(); |
272
|
|
|
|
273
|
|
|
// Patching game menu - if any |
274
|
|
|
global $sn_menu_extra, $sn_menu_admin_extra; |
|
|
|
|
275
|
|
|
isset($this->manifest['menu']) and $this->__patch_menu($sn_menu_extra, $this->manifest['menu']); |
|
|
|
|
276
|
|
|
isset($this->manifest['menu_admin']) and $this->__patch_menu($sn_menu_admin_extra, $this->manifest['menu_admin']); |
|
|
|
|
277
|
|
|
|
278
|
|
|
global $sn_mvc; |
|
|
|
|
279
|
|
|
foreach ($sn_mvc as $handler_type => &$handler_data) { |
280
|
|
|
if ($handler_type == MVC_OPTIONS) { |
281
|
|
|
continue; |
282
|
|
|
} |
283
|
|
|
sn_sys_handler_add($handler_data, $this->manifest['mvc'][$handler_type], $this, $handler_type); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
if (!empty($this->manifest['mvc'][MVC_OPTIONS])) { |
287
|
|
|
foreach ($this->manifest['mvc'][MVC_OPTIONS] as $pageName => $pageOptions) { |
288
|
|
|
if (empty($pageOptions)) { |
289
|
|
|
continue; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
!is_array($sn_mvc['pages'][$pageName][MVC_OPTIONS]) ? $sn_mvc['pages'][$pageName][MVC_OPTIONS] = [] : false; |
293
|
|
|
$sn_mvc['pages'][$pageName][MVC_OPTIONS] = array_merge($sn_mvc['pages'][$pageName][MVC_OPTIONS], $pageOptions); |
294
|
|
|
} |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
if (isset($this->manifest['i18n']) && is_array($this->manifest['i18n']) && !empty($this->manifest['i18n'])) { |
298
|
|
|
foreach ($this->manifest['i18n'] as $i18n_page_name => &$i18n_file_list) { |
299
|
|
|
foreach ($i18n_file_list as &$i18n_file_data) { |
300
|
|
|
if (is_array($i18n_file_data) && !$i18n_file_data['path']) { |
301
|
|
|
$i18n_file_data['path'] = $this->getRootRelative(); |
302
|
|
|
} |
303
|
|
|
} |
304
|
|
|
if (!isset($sn_mvc['i18n'][$i18n_page_name])) { |
305
|
|
|
$sn_mvc['i18n'][$i18n_page_name] = array(); |
306
|
|
|
} |
307
|
|
|
$sn_mvc['i18n'][$i18n_page_name] += $i18n_file_list; |
308
|
|
|
} |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
if (!empty($this->manifest['javascript']) && is_array($this->manifest['javascript'])) { |
312
|
|
|
foreach ($this->manifest['javascript'] as $javascript_page_name => &$javascript_list) { |
313
|
|
|
!isset($sn_mvc['javascript'][$javascript_page_name]) ? $sn_mvc['javascript'][$javascript_page_name] = array() : false; |
314
|
|
|
foreach ($javascript_list as $script_name => &$script_content) { |
315
|
|
|
$sn_mvc['javascript'][$javascript_page_name][$script_name] = $script_content; |
316
|
|
|
} |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
if (!empty($this->manifest['css']) && is_array($this->manifest['css'])) { |
321
|
|
|
foreach ($this->manifest['css'] as $javascript_page_name => &$javascript_list) { |
322
|
|
|
!isset($sn_mvc['css'][$javascript_page_name]) ? $sn_mvc['css'][$javascript_page_name] = array() : false; |
323
|
|
|
foreach ($javascript_list as $script_name => &$script_content) { |
324
|
|
|
$sn_mvc['css'][$javascript_page_name][$script_name] = $script_content; |
325
|
|
|
} |
326
|
|
|
} |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
if (!empty($this->manifest['navbar_prefix_button']) && is_array($this->manifest['navbar_prefix_button'])) { |
330
|
|
|
foreach ($this->manifest['navbar_prefix_button'] as $button_image => $button_url_relative) { |
331
|
|
|
$sn_mvc['navbar_prefix_button'][$button_image] = $button_url_relative; |
332
|
|
|
} |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
if (!empty($this->manifest['navbar_main_button']) && is_array($this->manifest['navbar_main_button'])) { |
336
|
|
|
foreach ($this->manifest['navbar_main_button'] as $button_image => $button_url_relative) { |
337
|
|
|
$sn_mvc['navbar_main_button'][$button_image] = $button_url_relative; |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
public function check_status() { |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Checks if module is active |
347
|
|
|
* |
348
|
|
|
* @return bool |
349
|
|
|
*/ |
350
|
|
|
public function isActive() { |
351
|
|
|
return !empty($this->manifest['active']) && !empty($this->manifest['installed']); |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* Register pages in $manifest['mvc']['pages'] for further use |
356
|
|
|
* |
357
|
|
|
* @param string[] $pages - array of records ['pageName' => 'pageFile']. 'pageFile' is currently unused |
358
|
|
|
* |
359
|
|
|
* @deprecated |
360
|
|
|
*/ |
361
|
|
|
protected function __mvcRegisterPagesOld($pages) { |
362
|
|
|
!is_array($this->manifest['mvc']['pages']) ? $this->manifest['mvc']['pages'] = [] : false; |
363
|
|
|
if (is_array($pages) && !empty($pages)) { |
364
|
|
|
$this->manifest['mvc']['pages'] = array_merge($this->manifest['mvc']['pages'], $pages); |
365
|
|
|
} |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
protected function registerHooks() { |
369
|
|
|
foreach ($this->hooks as $hookName => $hookRecord) { |
370
|
|
|
// Priority can be first element of hook array |
371
|
|
|
$priority = Pimp::ORDER_AS_IS; |
372
|
|
|
if (is_array($hookRecord) && count($hookRecord) > 1 && is_numeric(reset($hookRecord))) { |
373
|
|
|
$priority = intval(reset($hookRecord)); |
374
|
|
|
array_shift($hookRecord); |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
// There is 2 elements in callable array |
378
|
|
|
if (is_array($hookRecord) && 2 == count($hookRecord)) { |
379
|
|
|
// Checking - if first should be replaced with $this |
380
|
|
|
if (THIS_STRING === reset($hookRecord)) { |
381
|
|
|
$hookRecord = [$this, end($hookRecord)]; |
382
|
|
|
} |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
SN::$gc->pimp->register($hookName, $hookRecord, $priority); |
386
|
|
|
} |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
public function getLoadOrder() { |
390
|
|
|
return !empty($this->manifest[self::M_LOAD_ORDER]) ? $this->manifest[self::M_LOAD_ORDER] : MODULE_LOAD_ORDER_DEFAULT; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
public function getRootRelative() { |
394
|
|
|
if (empty($this->manifest[static::M_ROOT_RELATIVE])) { |
395
|
|
|
$this->manifest[static::M_ROOT_RELATIVE] = str_replace([SN_ROOT_PHYSICAL, basename($this->filename)], '', str_replace('\\', '/', $this->filename)); |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
return $this->manifest[static::M_ROOT_RELATIVE]; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
protected function getTemplateRootRelative() { |
402
|
|
|
return $this->getRootRelative() . 'design/templates/'; |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
/** |
406
|
|
|
* |
407
|
|
|
* Should stay public due using in Festivals (?) |
408
|
|
|
* |
409
|
|
|
* @param string $templateName |
410
|
|
|
* @param template $template |
411
|
|
|
* |
412
|
|
|
* @return template |
413
|
|
|
*/ |
414
|
|
|
public function addModuleTemplate($templateName, $template) { |
415
|
|
|
return gettemplate($templateName, $template, $this->getTemplateRootRelative()); |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
} |
419
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.