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
|
|
|
cs\Page\Includes, |
12
|
|
|
cs\Page\Meta; |
13
|
|
|
|
14
|
|
|
class Page { |
15
|
|
|
use |
16
|
|
|
Singleton, |
17
|
|
|
Includes; |
18
|
|
|
public $Content; |
19
|
|
|
public $interface = true; |
20
|
|
|
public $pre_Html = ''; |
21
|
|
|
public $Html = ''; |
22
|
|
|
public $Description = ''; |
23
|
|
|
/** |
24
|
|
|
* @var string|string[] |
25
|
|
|
*/ |
26
|
|
|
public $Title = []; |
27
|
|
|
public $Head = ''; |
28
|
|
|
public $pre_Body = ''; |
29
|
|
|
public $Left = ''; |
30
|
|
|
public $Top = ''; |
31
|
|
|
public $Right = ''; |
32
|
|
|
public $Bottom = ''; |
33
|
|
|
public $post_Body = ''; |
34
|
|
|
public $post_Html = ''; |
35
|
|
|
/** |
36
|
|
|
* Number of tabs by default for indentation the substitution of values into template |
37
|
|
|
* |
38
|
|
|
* @var array |
39
|
|
|
*/ |
40
|
|
|
public $level = [ |
41
|
|
|
'Head' => 0, |
42
|
|
|
'pre_Body' => 1, |
43
|
|
|
'Left' => 3, |
44
|
|
|
'Top' => 3, |
45
|
|
|
'Content' => 4, |
46
|
|
|
'Bottom' => 3, |
47
|
|
|
'Right' => 3, |
48
|
|
|
'post_Body' => 1 |
49
|
|
|
]; |
50
|
|
|
/** |
51
|
|
|
* @var array[] |
52
|
|
|
*/ |
53
|
|
|
protected $link = []; |
54
|
|
|
/** |
55
|
|
|
* @var string[] |
56
|
|
|
*/ |
57
|
|
|
protected $search_replace = []; |
58
|
|
|
/** |
59
|
|
|
* @var false|string |
60
|
|
|
*/ |
61
|
|
|
protected $canonical_url = false; |
62
|
|
|
protected $theme; |
63
|
|
|
protected $error_showed = false; |
64
|
|
|
protected $finish_called_once = false; |
65
|
|
|
/** |
66
|
|
|
* @param string $property |
67
|
|
|
* |
68
|
|
|
* @return false|null|string |
69
|
|
|
*/ |
70
|
|
|
function __get ($property) { |
71
|
|
|
// For internal use by \cs\Meta class |
72
|
|
|
if ($property === 'canonical_url') { |
73
|
|
|
return $this->canonical_url; |
74
|
|
|
} |
75
|
|
|
return false; |
76
|
|
|
} |
77
|
|
|
protected function construct () { |
78
|
|
|
$Config = Config::instance(true); |
79
|
|
|
/** |
80
|
|
|
* We need Config for initialization |
81
|
|
|
*/ |
82
|
|
|
if (!$Config) { |
83
|
|
|
Event::instance()->once( |
84
|
|
|
'System/Config/init/after', |
85
|
|
|
function () { |
86
|
|
|
$this->init(); |
87
|
|
|
} |
88
|
|
|
); |
89
|
|
|
} else { |
90
|
|
|
$this->init(); |
91
|
|
|
} |
92
|
|
|
Event::instance()->on( |
93
|
|
|
'System/Config/changed', |
94
|
|
|
function () { |
95
|
|
|
$this->init(); |
96
|
|
|
} |
97
|
|
|
); |
98
|
|
|
} |
99
|
|
|
/** |
100
|
|
|
* Initialization: setting of title and theme according to system configuration |
101
|
|
|
* |
102
|
|
|
* @return Page |
103
|
|
|
*/ |
104
|
|
|
protected function init () { |
105
|
|
|
$Config = Config::instance(); |
106
|
|
|
$this->Title[0] = htmlentities(get_core_ml_text('name'), ENT_COMPAT, 'utf-8'); |
107
|
|
|
$this->set_theme($Config->core['theme']); |
108
|
|
|
return $this; |
109
|
|
|
} |
110
|
|
|
/** |
111
|
|
|
* Theme changing |
112
|
|
|
* |
113
|
|
|
* @param string $theme |
114
|
|
|
* |
115
|
|
|
* @return Page |
116
|
|
|
*/ |
117
|
|
|
function set_theme ($theme) { |
118
|
|
|
$this->theme = $theme; |
119
|
|
|
return $this; |
120
|
|
|
} |
121
|
|
|
/** |
122
|
|
|
* Adding of content on the page |
123
|
|
|
* |
124
|
|
|
* @param string $add |
125
|
|
|
* @param bool|int $level |
126
|
|
|
* |
127
|
|
|
* @return Page |
128
|
|
|
*/ |
129
|
|
|
function content ($add, $level = false) { |
130
|
|
|
if ($level !== false) { |
131
|
|
|
$this->Content .= h::level($add, $level); |
132
|
|
|
} else { |
133
|
|
|
$this->Content .= $add; |
134
|
|
|
} |
135
|
|
|
return $this; |
136
|
|
|
} |
137
|
|
|
/** |
138
|
|
|
* Sets body with content, that is transformed into JSON format |
139
|
|
|
* |
140
|
|
|
* @param mixed $add |
141
|
|
|
* |
142
|
|
|
* @return Page |
143
|
|
|
*/ |
144
|
|
|
function json ($add) { |
145
|
|
|
Response::instance()->header('content-type', 'application/json; charset=utf-8'); |
146
|
|
|
interface_off(); |
147
|
|
|
$this->Content = _json_encode($add); |
148
|
|
|
return $this; |
149
|
|
|
} |
150
|
|
|
/** |
151
|
|
|
* Loading of theme template |
152
|
|
|
* |
153
|
|
|
* @return bool |
154
|
|
|
*/ |
155
|
|
|
protected function get_template () { |
156
|
|
|
/** |
157
|
|
|
* Theme is fixed for administration, and may vary for other pages |
158
|
|
|
*/ |
159
|
|
|
if (admin_path()) { |
160
|
|
|
$this->theme = 'CleverStyle'; |
161
|
|
|
} |
162
|
|
|
$theme_dir = THEMES."/$this->theme"; |
163
|
|
|
_include("$theme_dir/prepare.php", false, false); |
164
|
|
|
ob_start(); |
165
|
|
|
$return = true; |
166
|
|
|
/** |
167
|
|
|
* If website is closed and user is not an administrator - send `503 Service Unavailable` header and show closed site page |
168
|
|
|
*/ |
169
|
|
|
if ( |
170
|
|
|
!Config::instance()->core['site_mode'] && |
171
|
|
|
!User::instance(true)->admin() && |
172
|
|
|
status_code(503) && |
|
|
|
|
173
|
|
|
!_include("$theme_dir/closed.php", false, false) && |
174
|
|
|
!_include("$theme_dir/closed.html", false, false) |
175
|
|
|
) { |
176
|
|
|
echo |
177
|
|
|
"<!doctype html>\n". |
178
|
|
|
h::title(get_core_ml_text('closed_title')). |
179
|
|
|
get_core_ml_text('closed_text'); |
180
|
|
|
$return = false; |
181
|
|
|
} else { |
182
|
|
|
_include("$theme_dir/index.php", false, false) || _include("$theme_dir/index.html"); |
183
|
|
|
} |
184
|
|
|
$this->Html = ob_get_clean(); |
185
|
|
|
return $return; |
186
|
|
|
} |
187
|
|
|
/** |
188
|
|
|
* Processing of template, substituting of content, preparing for the output |
189
|
|
|
* |
190
|
|
|
* @return Page |
191
|
|
|
*/ |
192
|
|
|
protected function prepare () { |
193
|
|
|
$Config = Config::instance(true); |
194
|
|
|
/** |
195
|
|
|
* Loading of template |
196
|
|
|
*/ |
197
|
|
|
if (!$this->get_template()) { |
198
|
|
|
return $this; |
199
|
|
|
} |
200
|
|
|
/** |
201
|
|
|
* Forming page title |
202
|
|
|
*/ |
203
|
|
|
$this->Title = array_filter($this->Title, 'trim'); |
204
|
|
|
$this->Title = $Config->core['title_reverse'] ? array_reverse($this->Title) : $this->Title; |
205
|
|
|
$this->Title = implode($Config->core['title_delimiter'] ?: '|', $this->Title); |
206
|
|
|
/** |
207
|
|
|
* Forming <head> content |
208
|
|
|
*/ |
209
|
|
|
$this->Head = |
210
|
|
|
h::title($this->Title). |
211
|
|
|
h::meta( |
212
|
|
|
[ |
213
|
|
|
'charset' => 'utf-8' |
214
|
|
|
] |
215
|
|
|
). |
216
|
|
|
h::meta( |
217
|
|
|
$this->Description ? [ |
218
|
|
|
'name' => 'description', |
219
|
|
|
'content' => $this->Description |
220
|
|
|
] : false |
221
|
|
|
). |
222
|
|
|
h::meta( |
223
|
|
|
[ |
224
|
|
|
'name' => 'generator', |
225
|
|
|
'content' => 'CleverStyle CMS by Mokrynskyi Nazar' |
226
|
|
|
] |
227
|
|
|
). |
228
|
|
|
h::base( |
229
|
|
|
$Config ? [ |
230
|
|
|
'href' => $Config->base_url().'/' |
231
|
|
|
] : false |
232
|
|
|
). |
233
|
|
|
$this->Head. |
234
|
|
|
h::link( |
235
|
|
|
[ |
236
|
|
|
'rel' => 'shortcut icon', |
237
|
|
|
'href' => $this->get_favicon_path() |
238
|
|
|
] |
239
|
|
|
). |
240
|
|
|
h::link(array_values($this->link) ?: false); |
241
|
|
|
/** |
242
|
|
|
* Addition of CSS, JavaScript and Web Components includes |
243
|
|
|
*/ |
244
|
|
|
$this->add_includes_on_page(); |
245
|
|
|
/** |
246
|
|
|
* Generation of Open Graph protocol information |
247
|
|
|
*/ |
248
|
|
|
Meta::instance()->render(); |
249
|
|
|
/** |
250
|
|
|
* Substitution of information into template |
251
|
|
|
*/ |
252
|
|
|
$this->Html = str_replace( |
253
|
|
|
[ |
254
|
|
|
'<!--pre_Html-->', |
255
|
|
|
'<!--head-->', |
256
|
|
|
'<!--pre_Body-->', |
257
|
|
|
'<!--left_blocks-->', |
258
|
|
|
'<!--top_blocks-->', |
259
|
|
|
'<!--content-->', |
260
|
|
|
'<!--bottom_blocks-->', |
261
|
|
|
'<!--right_blocks-->', |
262
|
|
|
'<!--post_Body-->', |
263
|
|
|
'<!--post_Html-->' |
264
|
|
|
], |
265
|
|
|
_rtrim( |
266
|
|
|
[ |
267
|
|
|
$this->pre_Html, |
268
|
|
|
$this->get_property_with_indentation('Head'), |
269
|
|
|
$this->get_property_with_indentation('pre_Body'), |
270
|
|
|
$this->get_property_with_indentation('Left'), |
271
|
|
|
$this->get_property_with_indentation('Top'), |
272
|
|
|
$this->get_property_with_indentation('Content'), |
273
|
|
|
$this->get_property_with_indentation('Bottom'), |
274
|
|
|
$this->get_property_with_indentation('Right'), |
275
|
|
|
$this->get_property_with_indentation('post_Body'), |
276
|
|
|
$this->post_Html |
277
|
|
|
], |
278
|
|
|
"\t" |
279
|
|
|
), |
280
|
|
|
$this->Html |
281
|
|
|
); |
282
|
|
|
return $this; |
283
|
|
|
} |
284
|
|
|
/** |
285
|
|
|
* @return string |
286
|
|
|
*/ |
287
|
|
|
protected function get_favicon_path () { |
288
|
|
|
$file = file_exists_with_extension(THEMES."/$this->theme/img/favicon", ['png', 'ico']); |
289
|
|
|
if ($file) { |
290
|
|
|
return str_replace(THEMES, 'themes', $file); |
291
|
|
|
} |
292
|
|
|
return 'favicon.ico'; |
293
|
|
|
} |
294
|
|
|
/** |
295
|
|
|
* @param string $property |
296
|
|
|
* |
297
|
|
|
* @return string |
298
|
|
|
*/ |
299
|
|
|
protected function get_property_with_indentation ($property) { |
300
|
|
|
return h::level($this->$property, $this->level[$property]); |
301
|
|
|
} |
302
|
|
|
/** |
303
|
|
|
* Replacing anything in source code of finally generated page |
304
|
|
|
* |
305
|
|
|
* Parameters may be both simply strings for str_replace() and regular expressions for preg_replace() |
306
|
|
|
* |
307
|
|
|
* @param string|string[] $search |
308
|
|
|
* @param string|string[] $replace |
309
|
|
|
* |
310
|
|
|
* @return Page |
311
|
|
|
*/ |
312
|
|
|
function replace ($search, $replace = '') { |
313
|
|
|
if (is_array($search)) { |
314
|
|
|
$this->search_replace = $search + $this->search_replace; |
315
|
|
|
} else { |
316
|
|
|
/** @noinspection OffsetOperationsInspection */ |
317
|
|
|
$this->search_replace[$search] = $replace; |
318
|
|
|
} |
319
|
|
|
return $this; |
320
|
|
|
} |
321
|
|
|
/** |
322
|
|
|
* Processing of replacing in content |
323
|
|
|
* |
324
|
|
|
* @param string $content |
325
|
|
|
* |
326
|
|
|
* @return string |
327
|
|
|
*/ |
328
|
|
|
protected function process_replacing ($content) { |
329
|
|
|
foreach ($this->search_replace as $search => $replace) { |
330
|
|
|
$content = _preg_replace($search, $replace, $content) ?: str_replace($search, $replace, $content); |
331
|
|
|
} |
332
|
|
|
$this->search_replace = []; |
333
|
|
|
return $content; |
334
|
|
|
} |
335
|
|
|
/** |
336
|
|
|
* Adding links |
337
|
|
|
* |
338
|
|
|
* @param array $data According to h class syntax |
339
|
|
|
* |
340
|
|
|
* @return Page |
341
|
|
|
*/ |
342
|
|
|
function link ($data) { |
343
|
|
|
if ($data !== false) { |
344
|
|
|
$this->link[] = $data; |
345
|
|
|
} |
346
|
|
|
return $this; |
347
|
|
|
} |
348
|
|
|
/** |
349
|
|
|
* Simple wrapper of $Page->link() for inserting Atom feed on page |
350
|
|
|
* |
351
|
|
|
* @param string $href |
352
|
|
|
* @param string $title |
353
|
|
|
* |
354
|
|
|
* @return Page |
355
|
|
|
*/ |
356
|
|
|
function atom ($href, $title = 'Atom Feed') { |
357
|
|
|
return $this->link( |
358
|
|
|
[ |
359
|
|
|
'href' => $href, |
360
|
|
|
'title' => $title, |
361
|
|
|
'rel' => 'alternate', |
362
|
|
|
'type' => 'application/atom+xml' |
363
|
|
|
] |
364
|
|
|
); |
365
|
|
|
} |
366
|
|
|
/** |
367
|
|
|
* Simple wrapper of $Page->link() for inserting RSS feed on page |
368
|
|
|
* |
369
|
|
|
* @param string $href |
370
|
|
|
* @param string $title |
371
|
|
|
* |
372
|
|
|
* @return Page |
373
|
|
|
*/ |
374
|
|
|
function rss ($href, $title = 'RSS Feed') { |
375
|
|
|
return $this->link( |
376
|
|
|
[ |
377
|
|
|
'href' => $href, |
378
|
|
|
'title' => $title, |
379
|
|
|
'rel' => 'alternate', |
380
|
|
|
'type' => 'application/rss+xml' |
381
|
|
|
] |
382
|
|
|
); |
383
|
|
|
} |
384
|
|
|
/** |
385
|
|
|
* Specify canonical url of current page |
386
|
|
|
* |
387
|
|
|
* @param string $url |
388
|
|
|
* |
389
|
|
|
* @return Page |
390
|
|
|
*/ |
391
|
|
|
function canonical_url ($url) { |
392
|
|
|
$this->canonical_url = $url; |
393
|
|
|
$this->link['canonical_url'] = [ |
394
|
|
|
'href' => $this->canonical_url, |
395
|
|
|
'rel' => 'canonical' |
396
|
|
|
]; |
397
|
|
|
return $this; |
398
|
|
|
} |
399
|
|
|
/** |
400
|
|
|
* Adding text to the title page |
401
|
|
|
* |
402
|
|
|
* @param string $title |
403
|
|
|
* @param bool $replace Replace whole title by this |
404
|
|
|
* |
405
|
|
|
* @return Page |
406
|
|
|
*/ |
407
|
|
|
function title ($title, $replace = false) { |
408
|
|
|
$title = htmlentities($title, ENT_COMPAT, 'utf-8'); |
409
|
|
|
if ($replace) { |
410
|
|
|
$this->Title = [$title]; |
411
|
|
|
} else { |
412
|
|
|
$this->Title[] = $title; |
413
|
|
|
} |
414
|
|
|
return $this; |
415
|
|
|
} |
416
|
|
|
/** |
417
|
|
|
* Display success message |
418
|
|
|
* |
419
|
|
|
* @param string $success_text |
420
|
|
|
* |
421
|
|
|
* @return Page |
422
|
|
|
*/ |
423
|
|
|
function success ($success_text) { |
424
|
|
|
return $this->top_message($success_text, 'success'); |
425
|
|
|
} |
426
|
|
|
/** |
427
|
|
|
* Display notice message |
428
|
|
|
* |
429
|
|
|
* @param string $notice_text |
430
|
|
|
* |
431
|
|
|
* @return Page |
432
|
|
|
*/ |
433
|
|
|
function notice ($notice_text) { |
434
|
|
|
return $this->top_message($notice_text, 'warning'); |
435
|
|
|
} |
436
|
|
|
/** |
437
|
|
|
* Display warning message |
438
|
|
|
* |
439
|
|
|
* @param string $warning_text |
440
|
|
|
* |
441
|
|
|
* @return Page |
442
|
|
|
*/ |
443
|
|
|
function warning ($warning_text) { |
444
|
|
|
return $this->top_message($warning_text, 'error'); |
445
|
|
|
} |
446
|
|
|
/** |
447
|
|
|
* Generic method for 3 methods above |
448
|
|
|
* |
449
|
|
|
* @param string $message |
450
|
|
|
* @param string $class_ending |
451
|
|
|
* |
452
|
|
|
* @return Page |
453
|
|
|
*/ |
454
|
|
|
protected function top_message ($message, $class_ending) { |
455
|
|
|
$this->Top .= h::div( |
456
|
|
|
$message, |
457
|
|
|
[ |
458
|
|
|
'class' => "cs-text-center cs-block-$class_ending cs-text-$class_ending" |
459
|
|
|
] |
460
|
|
|
); |
461
|
|
|
return $this; |
462
|
|
|
} |
463
|
|
|
/** |
464
|
|
|
* Error pages processing |
465
|
|
|
* |
466
|
|
|
* @param null|string|string[] $custom_text Custom error text instead of text like "404 Not Found" or array with two elements: [error, error_description] |
467
|
|
|
* @param bool $json Force JSON return format |
468
|
|
|
* @param int $error_code HTTP status code |
469
|
|
|
* |
470
|
|
|
* @throws ExitException |
471
|
|
|
*/ |
472
|
|
|
function error ($custom_text = null, $json = false, $error_code = 500) { |
473
|
|
|
if ($this->error_showed) { |
474
|
|
|
return; |
475
|
|
|
} |
476
|
|
|
$this->error_showed = true; |
477
|
|
|
$Response = Response::instance(); |
478
|
|
|
/** |
479
|
|
|
* Hack for 403 after sign out in administration |
480
|
|
|
*/ |
481
|
|
|
if ($error_code == 403 && !api_path() && _getcookie('sign_out')) { |
|
|
|
|
482
|
|
|
$Response->redirect('/'); |
483
|
|
|
$this->Content = ''; |
484
|
|
|
throw new ExitException; |
485
|
|
|
} |
486
|
|
|
interface_off(); |
487
|
|
|
$status_text = status_code($error_code); |
|
|
|
|
488
|
|
|
$error_description = $status_text; |
489
|
|
|
if (is_array($custom_text)) { |
490
|
|
|
list($error_code, $error_description) = $custom_text; |
491
|
|
|
} elseif ($custom_text) { |
492
|
|
|
$error_description = $custom_text; |
493
|
|
|
} |
494
|
|
|
if ($json || api_path()) { |
495
|
|
|
if ($json) { |
496
|
|
|
$Response->header('content-type', 'application/json; charset=utf-8'); |
497
|
|
|
interface_off(); |
498
|
|
|
} |
499
|
|
|
$this->json( |
500
|
|
|
[ |
501
|
|
|
'error' => $error_code, |
502
|
|
|
'error_description' => $error_description |
503
|
|
|
] |
504
|
|
|
); |
505
|
|
|
} else { |
506
|
|
|
ob_start(); |
507
|
|
|
if ( |
508
|
|
|
!_include(THEMES."/$this->theme/error.html", false, false) && |
509
|
|
|
!_include(THEMES."/$this->theme/error.php", false, false) |
510
|
|
|
) { |
511
|
|
|
echo |
512
|
|
|
"<!doctype html>\n". |
513
|
|
|
h::title(status_code($error_code)). |
|
|
|
|
514
|
|
|
$error_description; |
515
|
|
|
} |
516
|
|
|
$this->Content = ob_get_clean(); |
517
|
|
|
} |
518
|
|
|
$this->__finish(); |
519
|
|
|
throw new ExitException; |
520
|
|
|
} |
521
|
|
|
/** |
522
|
|
|
* Provides next events: |
523
|
|
|
* System/Page/display/before |
524
|
|
|
* |
525
|
|
|
* System/Page/display/after |
526
|
|
|
* |
527
|
|
|
* Page generation |
528
|
|
|
*/ |
529
|
|
|
function __finish () { |
530
|
|
|
/** |
531
|
|
|
* Protection from double calling |
532
|
|
|
*/ |
533
|
|
|
if ($this->finish_called_once) { |
534
|
|
|
return; |
535
|
|
|
} |
536
|
|
|
$this->finish_called_once = true; |
537
|
|
|
$Response = Response::instance(); |
538
|
|
|
if (is_resource($Response->body_stream)) { |
539
|
|
|
return; |
540
|
|
|
} |
541
|
|
|
/** |
542
|
|
|
* For AJAX and API requests only content without page template |
543
|
|
|
*/ |
544
|
|
|
$api = api_path(); |
545
|
|
|
if ($api || !$this->interface) { |
546
|
|
|
/** |
547
|
|
|
* Processing of replacing in content |
548
|
|
|
*/ |
549
|
|
|
/** @noinspection NestedTernaryOperatorInspection */ |
550
|
|
|
$Response->body = $this->process_replacing($this->Content ?: ($api ? 'null' : '')); |
551
|
|
|
} else { |
552
|
|
|
Event::instance()->fire('System/Page/display/before'); |
553
|
|
|
/** |
554
|
|
|
* Processing of template, substituting of content, preparing for the output |
555
|
|
|
*/ |
556
|
|
|
$this->prepare(); |
557
|
|
|
/** |
558
|
|
|
* Processing of replacing in content |
559
|
|
|
*/ |
560
|
|
|
$this->Html = $this->process_replacing($this->Html); |
561
|
|
|
Event::instance()->fire('System/Page/display/after'); |
562
|
|
|
$Response->body = rtrim($this->Html); |
563
|
|
|
} |
564
|
|
|
} |
565
|
|
|
} |
566
|
|
|
|
This function has been deprecated. The supplier of the file has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.