Completed
Push — master ( 7c4761...b7b84b )
by Nazar
04:46
created

Page::render()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 38
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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