Completed
Push — master ( 52a157...dbf29b )
by Nazar
04:07
created

Page::success()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

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