Completed
Push — master ( b7b84b...78616b )
by Nazar
04:19
created

Page::init_config()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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