Completed
Push — master ( 122153...1ebbbc )
by Nazar
04:18
created

Page::__get()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
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) &&
0 ignored issues
show
Deprecated Code introduced by
The function status_code() has been deprecated with message: Use `cs\Response::$code` instead

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.

Loading history...
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')) {
0 ignored issues
show
Deprecated Code introduced by
The function _getcookie() has been deprecated with message: Use `cs\Request::$cookie` instead

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.

Loading history...
482
			$Response->redirect('/');
483
			$this->Content = '';
484
			throw new ExitException;
485
		}
486
		interface_off();
487
		$status_text       = status_code($error_code);
0 ignored issues
show
Deprecated Code introduced by
The function status_code() has been deprecated with message: Use `cs\Response::$code` instead

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.

Loading history...
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)).
0 ignored issues
show
Deprecated Code introduced by
The function status_code() has been deprecated with message: Use `cs\Response::$code` instead

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.

Loading history...
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