Completed
Push — master ( a4ebee...ff9168 )
by Nazar
07:28
created

Page::content()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 8
rs 9.4285
cc 2
eloc 6
nc 2
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
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(
0 ignored issues
show
Bug introduced by
It seems like once() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
84
				'System/Config/init/after',
85
				function () {
86
					$this->init();
87
				}
88
			);
89
		} else {
90
			$this->init();
91
		}
92
		Event::instance()->on(
0 ignored issues
show
Bug introduced by
It seems like on() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
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
		_header('Content-Type: application/json; charset=utf-8', true);
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() &&
0 ignored issues
show
Bug introduced by
It seems like admin() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
172
			status_code(503) &&
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
0 ignored issues
show
Documentation introduced by
Should the return type not be Page|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
191
	 */
192
	protected function prepare () {
193
		$Config = Config::instance(true);
194
		/**
195
		 * Loading of template
196
		 */
197
		if (!$this->get_template()) {
198
			return;
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().'/'
0 ignored issues
show
Bug introduced by
It seems like base_url() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
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();
0 ignored issues
show
Bug introduced by
It seems like render() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
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
			$this->search_replace[$search] = $replace;
317
		}
318
		return $this;
319
	}
320
	/**
321
	 * Processing of replacing in content
322
	 *
323
	 * @param string $content
324
	 *
325
	 * @return string
326
	 */
327
	protected function process_replacing ($content) {
328
		foreach ($this->search_replace as $search => $replace) {
329
			$content = _preg_replace($search, $replace, $content) ?: str_replace($search, $replace, $content);
330
		}
331
		$this->search_replace = [];
332
		return $content;
333
	}
334
	/**
335
	 * Adding links
336
	 *
337
	 * @param array $data According to h class syntax
338
	 *
339
	 * @return Page
340
	 */
341
	function link ($data) {
342
		if ($data !== false) {
343
			$this->link[] = $data;
344
		}
345
		return $this;
346
	}
347
	/**
348
	 * Simple wrapper of $Page->link() for inserting Atom feed on page
349
	 *
350
	 * @param string $href
351
	 * @param string $title
352
	 *
353
	 * @return Page
354
	 */
355
	function atom ($href, $title = 'Atom Feed') {
356
		return $this->link(
357
			[
358
				'href'  => $href,
359
				'title' => $title,
360
				'rel'   => 'alternate',
361
				'type'  => 'application/atom+xml'
362
			]
363
		);
364
	}
365
	/**
366
	 * Simple wrapper of $Page->link() for inserting RSS feed on page
367
	 *
368
	 * @param string $href
369
	 * @param string $title
370
	 *
371
	 * @return Page
372
	 */
373
	function rss ($href, $title = 'RSS Feed') {
374
		return $this->link(
375
			[
376
				'href'  => $href,
377
				'title' => $title,
378
				'rel'   => 'alternate',
379
				'type'  => 'application/rss+xml'
380
			]
381
		);
382
	}
383
	/**
384
	 * Specify canonical url of current page
385
	 *
386
	 * @param string $url
387
	 *
388
	 * @return Page
389
	 */
390
	function canonical_url ($url) {
391
		$this->canonical_url         = $url;
392
		$this->link['canonical_url'] = [
393
			'href' => $this->canonical_url,
394
			'rel'  => 'canonical'
395
		];
396
		return $this;
397
	}
398
	/**
399
	 * Adding text to the title page
400
	 *
401
	 * @param string $title
402
	 * @param bool   $replace Replace whole title by this
403
	 *
404
	 * @return Page
405
	 */
406
	function title ($title, $replace = false) {
407
		$title = htmlentities($title, ENT_COMPAT, 'utf-8');
408
		if ($replace) {
409
			$this->Title = [$title];
410
		} else {
411
			$this->Title[] = $title;
412
		}
413
		return $this;
414
	}
415
	/**
416
	 * Display success message
417
	 *
418
	 * @param string $success_text
419
	 *
420
	 * @return Page
421
	 */
422
	function success ($success_text) {
423
		return $this->top_message($success_text, 'success');
424
	}
425
	/**
426
	 * Display notice message
427
	 *
428
	 * @param string $notice_text
429
	 *
430
	 * @return Page
431
	 */
432
	function notice ($notice_text) {
433
		return $this->top_message($notice_text, 'warning');
434
	}
435
	/**
436
	 * Display warning message
437
	 *
438
	 * @param string $warning_text
439
	 *
440
	 * @return Page
441
	 */
442
	function warning ($warning_text) {
443
		return $this->top_message($warning_text, 'error');
444
	}
445
	/**
446
	 * Generic method for 3 methods above
447
	 *
448
	 * @param string $message
449
	 * @param string $class_ending
450
	 *
451
	 * @return Page
452
	 */
453
	protected function top_message ($message, $class_ending) {
454
		$this->Top .= h::div(
455
			$message,
456
			[
457
				'class' => "cs-text-center cs-block-$class_ending cs-text-$class_ending"
458
			]
459
		);
460
		return $this;
461
	}
462
	/**
463
	 * Error pages processing
464
	 *
465
	 * @param null|string|string[] $custom_text Custom error text instead of text like "404 Not Found" or array with two elements: [error, error_description]
466
	 * @param bool                 $json        Force JSON return format
467
	 * @param int                  $error_code  HTTP status code
468
	 *
469
	 * @throws ExitException
470
	 */
471
	function error ($custom_text = null, $json = false, $error_code = 500) {
472
		if ($this->error_showed) {
473
			return;
474
		}
475
		$this->error_showed = true;
476
		/**
477
		 * Hack for 403 after sign out in administration
478
		 */
479
		if ($error_code == 403 && !api_path() && _getcookie('sign_out')) {
480
			_header('Location: /', true, 302);
481
			$this->Content = '';
482
			throw new ExitException;
483
		}
484
		interface_off();
485
		$status_text       = status_code($error_code);
486
		$error_description = $status_text;
487
		if (is_array($custom_text)) {
488
			list($error_code, $error_description) = $custom_text;
489
		} elseif ($custom_text) {
490
			$error_description = $custom_text;
491
		}
492
		if ($json || api_path()) {
493
			if ($json) {
494
				_header('Content-Type: application/json; charset=utf-8', true);
495
				interface_off();
496
			}
497
			$this->json(
498
				[
499
					'error'             => $error_code,
500
					'error_description' => $error_description
501
				]
502
			);
503
		} else {
504
			ob_start();
505
			if (
506
				!_include(THEMES."/$this->theme/error.html", false, false) &&
507
				!_include(THEMES."/$this->theme/error.php", false, false)
508
			) {
509
				echo
510
					"<!doctype html>\n".
511
					h::title(status_code($error_code)).
512
					$error_description;
513
			}
514
			$this->Content = ob_get_clean();
515
		}
516
		$this->__finish();
517
		throw new ExitException;
518
	}
519
	/**
520
	 * Provides next events:
521
	 *  System/Page/display/before
522
	 *
523
	 *  System/Page/display/after
524
	 *
525
	 * Page generation
526
	 */
527
	function __finish () {
528
		/**
529
		 * Protection from double calling
530
		 */
531
		if ($this->finish_called_once) {
532
			return;
533
		}
534
		$this->finish_called_once = true;
535
		/**
536
		 * For AJAX and API requests only content without page template
537
		 */
538
		$api = api_path();
539
		if ($api || !$this->interface) {
540
			/**
541
			 * Processing of replacing in content
542
			 */
543
			echo $this->process_replacing($this->Content ?: ($api ? 'null' : ''));
544
		} else {
545
			Event::instance()->fire('System/Page/display/before');
0 ignored issues
show
Bug introduced by
It seems like fire() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
546
			/**
547
			 * Processing of template, substituting of content, preparing for the output
548
			 */
549
			$this->prepare();
550
			/**
551
			 * Processing of replacing in content
552
			 */
553
			$this->Html = $this->process_replacing($this->Html);
554
			Event::instance()->fire('System/Page/display/after');
0 ignored issues
show
Bug introduced by
It seems like fire() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
555
			echo rtrim($this->Html);
556
		}
557
	}
558
}
559