1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @package CleverStyle CMS |
4
|
|
|
* @author Nazar Mokrynskyi <[email protected]> |
5
|
|
|
* @copyright Copyright (c) 2011-2016, Nazar Mokrynskyi |
6
|
|
|
* @license MIT License, see license.txt |
7
|
|
|
*/ |
8
|
|
|
namespace cs; |
9
|
|
|
use |
10
|
|
|
h; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Provides next events: |
14
|
|
|
* System/Index/block_render |
15
|
|
|
* [ |
16
|
|
|
* 'index' => $index, //Block index |
17
|
|
|
* 'blocks_array' => &$blocks_array //Reference to array in form ['top' => '', 'left' => '', 'right' => '', 'bottom' => ''] |
18
|
|
|
* ] |
19
|
|
|
* |
20
|
|
|
* System/Index/construct |
21
|
|
|
* |
22
|
|
|
* System/Index/preload |
23
|
|
|
* |
24
|
|
|
* System/Index/postload |
25
|
|
|
* |
26
|
|
|
* @method static Index instance($check = false) |
27
|
|
|
* |
28
|
|
|
* @property string[] $controller_path Path that will be used by controller to render page |
29
|
|
|
*/ |
30
|
|
|
class Index { |
31
|
|
|
use |
32
|
|
|
Singleton, |
33
|
|
|
Index\Router; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Appends to the end of title |
37
|
|
|
* |
38
|
|
|
* @var string |
39
|
|
|
*/ |
40
|
|
|
protected $append_to_title = ''; |
41
|
|
|
/** |
42
|
|
|
* Name of current module |
43
|
|
|
* |
44
|
|
|
* @var string |
45
|
|
|
*/ |
46
|
|
|
protected $module; |
47
|
|
|
protected $request_method; |
48
|
|
|
protected $working_directory = ''; |
49
|
|
|
protected $called_once = false; |
50
|
|
|
/** |
51
|
|
|
* Reference to Route::instance()->path |
52
|
|
|
* |
53
|
|
|
* @var string[] |
54
|
|
|
*/ |
55
|
|
|
protected $path = []; |
56
|
|
|
/** |
57
|
|
|
* Reference to Route::instance()->ids |
58
|
|
|
* |
59
|
|
|
* @var int[] |
60
|
|
|
*/ |
61
|
|
|
protected $ids = []; |
62
|
|
|
/** |
63
|
|
|
* Path that will be used by controller to render page |
64
|
|
|
* |
65
|
|
|
* @var string[] |
66
|
|
|
*/ |
67
|
|
|
protected $controller_path = ['index']; |
68
|
|
|
/** |
69
|
|
|
* Detecting module folder including of admin/api request type, including prepare file, including of plugins |
70
|
|
|
* |
71
|
|
|
* @throws ExitException |
72
|
|
|
*/ |
73
|
|
|
function construct () { |
74
|
|
|
$Config = Config::instance(); |
75
|
|
|
$Route = Route::instance(); |
76
|
|
|
$this->path = &$Route->path; |
77
|
|
|
$this->ids = &$Route->ids; |
78
|
|
|
if ($this->closed_site($Config)) { |
79
|
|
|
return; |
80
|
|
|
} |
81
|
|
|
$this->module = current_module(); |
82
|
|
|
$this->working_directory = MODULES."/$this->module"; |
83
|
|
|
if (admin_path()) { |
84
|
|
|
$this->working_directory .= '/admin'; |
85
|
|
|
} elseif (api_path()) { |
86
|
|
|
$this->working_directory .= '/api'; |
87
|
|
|
} |
88
|
|
|
if (!is_dir($this->working_directory)) { |
89
|
|
|
throw new ExitException(404); |
90
|
|
|
} |
91
|
|
|
if (!$this->check_permission('index')) { |
92
|
|
|
throw new ExitException(403); |
93
|
|
|
} |
94
|
|
|
Event::instance()->fire('System/Index/construct'); |
95
|
|
|
/** |
96
|
|
|
* Plugins processing |
97
|
|
|
*/ |
98
|
|
|
foreach ($Config->components['plugins'] as $plugin) { |
99
|
|
|
_include(PLUGINS."/$plugin/index.php", false, false); |
100
|
|
|
} |
101
|
|
|
_include("$this->working_directory/prepare.php", false, false); |
102
|
|
|
/** |
103
|
|
|
* @var _SERVER $_SERVER |
104
|
|
|
*/ |
105
|
|
|
$this->request_method = strtolower($_SERVER->request_method); |
106
|
|
|
if (!preg_match('/^[a-z_]+$/', $this->request_method)) { |
107
|
|
|
throw new ExitException(400); |
108
|
|
|
} |
109
|
|
|
} |
110
|
|
|
/** |
111
|
|
|
* Check if site is closed (taking user into account) |
112
|
|
|
* |
113
|
|
|
* @param Config $Config |
114
|
|
|
* |
115
|
|
|
* @return bool Whether user is not admin and this is not request for sign in (we allow to sign in on disabled site) |
116
|
|
|
*/ |
117
|
|
|
protected function closed_site ($Config) { |
118
|
|
|
if ( |
119
|
|
|
$Config->core['site_mode'] || |
120
|
|
|
User::instance()->admin() |
121
|
|
|
) { |
122
|
|
|
return false; |
123
|
|
|
} |
124
|
|
|
return |
125
|
|
|
!api_path() || |
126
|
|
|
$this->module != 'System' || |
127
|
|
|
Route::instance()->route !== ['user', 'sign_in']; |
128
|
|
|
} |
129
|
|
|
/** |
130
|
|
|
* Check whether user allowed to access to specified label |
131
|
|
|
* |
132
|
|
|
* @param string $label |
133
|
|
|
* |
134
|
|
|
* @return bool |
135
|
|
|
*/ |
136
|
|
|
protected function check_permission ($label) { |
137
|
|
|
$permission_group = $this->module; |
138
|
|
|
if (admin_path()) { |
139
|
|
|
$permission_group = "admin/$permission_group"; |
140
|
|
|
} elseif (api_path()) { |
141
|
|
|
$permission_group = "api/$permission_group"; |
142
|
|
|
} |
143
|
|
|
return User::instance()->get_permission($permission_group, $label); |
144
|
|
|
} |
145
|
|
|
/** |
146
|
|
|
* Page generation, blocks processing, adding of form with save/apply/cancel/reset and/or custom users buttons |
147
|
|
|
* |
148
|
|
|
* @throws ExitException |
149
|
|
|
*/ |
150
|
|
|
protected function render_page () { |
151
|
|
|
$this->render_title(); |
152
|
|
|
$this->render_content(); |
153
|
|
|
if (!api_path()) { |
154
|
|
|
$this->render_blocks(); |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
/** |
158
|
|
|
* Render page title |
159
|
|
|
*/ |
160
|
|
|
protected function render_title () { |
161
|
|
|
$Page = Page::instance(); |
162
|
|
|
/** |
163
|
|
|
* Add generic Home or Module name title |
164
|
|
|
*/ |
165
|
|
|
if (!api_path()) { |
166
|
|
|
$L = Language::instance(); |
167
|
|
|
if (admin_path()) { |
168
|
|
|
$Page->title($L->administration); |
169
|
|
|
} |
170
|
|
|
$Page->title( |
171
|
|
|
$L->{home_page() ? 'home' : $this->module} |
172
|
|
|
); |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
/** |
176
|
|
|
* Render page content (without blocks, just module content) |
177
|
|
|
* |
178
|
|
|
* @throws ExitException |
179
|
|
|
*/ |
180
|
|
|
protected function render_content () { |
181
|
|
|
$Page = Page::instance(); |
182
|
|
|
/** |
183
|
|
|
* If module consists of index.html only |
184
|
|
|
*/ |
185
|
|
|
if (file_exists("$this->working_directory/index.html")) { |
186
|
|
|
ob_start(); |
187
|
|
|
_include("$this->working_directory/index.html", false, false); |
188
|
|
|
$Page->content(ob_get_clean()); |
189
|
|
|
return; |
190
|
|
|
} |
191
|
|
|
$this->execute_router(); |
192
|
|
|
} |
193
|
|
|
/** |
194
|
|
|
* Simple wrapper for form buttons |
195
|
|
|
* |
196
|
|
|
* @param string $name |
197
|
|
|
* @param bool $disabled |
198
|
|
|
* |
199
|
|
|
* @return string |
200
|
|
|
*/ |
201
|
|
|
protected function form_button ($name, $disabled = false) { |
202
|
|
|
$L = Language::instance(); |
203
|
|
|
return h::{'button[is=cs-button]'}( |
204
|
|
|
$L->$name, |
205
|
|
|
[ |
206
|
|
|
'name' => $name, |
207
|
|
|
'type' => 'submit', |
208
|
|
|
'tooltip' => $L->{$name.'_info'}, |
209
|
|
|
$disabled ? 'disabled' : false |
210
|
|
|
] |
211
|
|
|
); |
212
|
|
|
} |
213
|
|
|
/** |
214
|
|
|
* Blocks rendering |
215
|
|
|
*/ |
216
|
|
|
protected function render_blocks () { |
217
|
|
|
$blocks = Config::instance()->components['blocks']; |
218
|
|
|
/** |
219
|
|
|
* It is frequent that there is no blocks - so, no need to to anything here |
220
|
|
|
*/ |
221
|
|
|
if (!$blocks) { |
222
|
|
|
return; |
223
|
|
|
} |
224
|
|
|
$Page = Page::instance(); |
225
|
|
|
$blocks_array = [ |
226
|
|
|
'top' => '', |
227
|
|
|
'left' => '', |
228
|
|
|
'right' => '', |
229
|
|
|
'bottom' => '' |
230
|
|
|
]; |
231
|
|
|
foreach ($blocks as $block) { |
232
|
|
|
/** |
233
|
|
|
* If there is no need to show block or it was rendered by even handler - skip further processing |
234
|
|
|
*/ |
235
|
|
|
if ( |
236
|
|
|
!$this->should_block_be_rendered($block) || |
237
|
|
|
!Event::instance()->fire( |
238
|
|
|
'System/Index/block_render', |
239
|
|
|
[ |
240
|
|
|
'index' => $block['index'], |
241
|
|
|
'blocks_array' => &$blocks_array |
242
|
|
|
] |
243
|
|
|
) |
244
|
|
|
) { |
245
|
|
|
/** |
246
|
|
|
* Block was rendered by event handler |
247
|
|
|
*/ |
248
|
|
|
continue; |
249
|
|
|
} |
250
|
|
|
$block['title'] = $this->ml_process($block['title']); |
251
|
|
|
switch ($block['type']) { |
252
|
|
|
default: |
253
|
|
|
$block['content'] = ob_wrapper( |
254
|
|
|
function () use ($block) { |
255
|
|
|
include BLOCKS."/block.$block[type].php"; |
256
|
|
|
} |
257
|
|
|
); |
258
|
|
|
break; |
259
|
|
|
case 'html': |
260
|
|
|
case 'raw_html': |
261
|
|
|
$block['content'] = $this->ml_process($block['content']); |
262
|
|
|
break; |
263
|
|
|
} |
264
|
|
|
/** |
265
|
|
|
* Template file will have access to `$block` variable, so it can use that |
266
|
|
|
*/ |
267
|
|
|
$content = str_replace( |
268
|
|
|
[ |
269
|
|
|
'<!--id-->', |
270
|
|
|
'<!--title-->', |
271
|
|
|
'<!--content-->' |
272
|
|
|
], |
273
|
|
|
[ |
274
|
|
|
$block['index'], |
275
|
|
|
$block['title'], |
276
|
|
|
$block['content'] |
277
|
|
|
], |
278
|
|
|
ob_wrapper( |
279
|
|
|
function () use ($block) { |
280
|
|
|
$template = file_exists(TEMPLATES."/blocks/block.$block[template]") ? $block['template'] : 'default.html'; |
281
|
|
|
include TEMPLATES."/blocks/block.$template"; |
282
|
|
|
} |
283
|
|
|
) |
284
|
|
|
); |
285
|
|
|
if ($block['position'] == 'floating') { |
286
|
|
|
$Page->replace( |
287
|
|
|
"<!--block#$block[index]-->", |
288
|
|
|
$content |
289
|
|
|
); |
290
|
|
|
} else { |
291
|
|
|
$blocks_array[$block['position']] .= $content; |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
$Page->Top .= $blocks_array['top']; |
295
|
|
|
$Page->Left .= $blocks_array['left']; |
296
|
|
|
$Page->Right .= $blocks_array['right']; |
297
|
|
|
$Page->Bottom .= $blocks_array['bottom']; |
298
|
|
|
} |
299
|
|
|
/** |
300
|
|
|
* Check whether to render block or not based on its properties (active state, when start to show, when it expires and permissions) |
301
|
|
|
* |
302
|
|
|
* @param array $block |
303
|
|
|
* |
304
|
|
|
* @return bool |
305
|
|
|
*/ |
306
|
|
|
protected function should_block_be_rendered ($block) { |
307
|
|
|
return |
308
|
|
|
$block['active'] && |
309
|
|
|
$block['start'] <= time() && |
310
|
|
|
( |
311
|
|
|
!$block['expire'] || |
312
|
|
|
$block['expire'] >= time() |
313
|
|
|
) && |
314
|
|
|
User::instance()->get_permission('Block', $block['index']); |
315
|
|
|
} |
316
|
|
|
/** |
317
|
|
|
* @param string $text |
318
|
|
|
* |
319
|
|
|
* @return string |
|
|
|
|
320
|
|
|
*/ |
321
|
|
|
protected function ml_process ($text) { |
322
|
|
|
return Text::instance()->process(Config::instance()->module('System')->db('texts'), $text, true); |
323
|
|
|
} |
324
|
|
|
/** |
325
|
|
|
* Getter for `controller_path` property (no other properties supported currently) |
326
|
|
|
* |
327
|
|
|
* @param string $property |
328
|
|
|
* |
329
|
|
|
* @return false|string[] |
330
|
|
|
*/ |
331
|
|
|
function __get ($property) { |
332
|
|
|
switch ($property) { |
333
|
|
|
case 'controller_path'; |
334
|
|
|
return $this->controller_path; |
335
|
|
|
} |
336
|
|
|
return false; |
337
|
|
|
} |
338
|
|
|
/** |
339
|
|
|
* Executes plugins processing, blocks and module page generation |
340
|
|
|
* |
341
|
|
|
* @throws ExitException |
342
|
|
|
*/ |
343
|
|
|
function __finish () { |
344
|
|
|
/** |
345
|
|
|
* Protection from double calling |
346
|
|
|
*/ |
347
|
|
|
if ($this->called_once) { |
348
|
|
|
return; |
349
|
|
|
} |
350
|
|
|
$this->called_once = true; |
351
|
|
|
$Config = Config::instance(); |
352
|
|
|
$Page = Page::instance(); |
353
|
|
|
/** |
354
|
|
|
* If site is closed |
355
|
|
|
*/ |
356
|
|
|
if (!$Config->core['site_mode']) { |
357
|
|
|
if ($this->closed_site($Config)) { |
358
|
|
|
status_code(503); |
359
|
|
|
return; |
360
|
|
|
} |
361
|
|
|
/** |
362
|
|
|
* Warning about closed site |
363
|
|
|
*/ |
364
|
|
|
$Page->warning(get_core_ml_text('closed_title')); |
365
|
|
|
} |
366
|
|
|
Event::instance()->fire('System/Index/preload'); |
367
|
|
|
$this->render_page(); |
368
|
|
|
Event::instance()->fire('System/Index/postload'); |
369
|
|
|
} |
370
|
|
|
} |
371
|
|
|
|
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>
.