Completed
Push — master ( 17bd20...4edca1 )
by Nazar
04:17
created

Page::rss()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

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