Completed
Push — master ( 1c47be...c1bd53 )
by Nazar
06:52
created

Page::get_favicon_path()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 7
rs 9.4285
cc 2
eloc 5
nc 2
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
/**
15
 * @method static Page instance($check = false)
16
 */
17
class Page {
18
	use
19
		Singleton,
20
		Includes;
21
	public $Content;
22
	public $interface   = true;
23
	public $pre_Html    = '';
24
	public $Html        = '';
25
	public $Description = '';
26
	/**
27
	 * @var string|string[]
28
	 */
29
	public $Title     = [];
30
	public $Head      = '';
31
	public $pre_Body  = '';
32
	public $Left      = '';
33
	public $Top       = '';
34
	public $Right     = '';
35
	public $Bottom    = '';
36
	public $post_Body = '';
37
	public $post_Html = '';
38
	/**
39
	 * Number of tabs by default for indentation the substitution of values into template
40
	 *
41
	 * @var array
42
	 */
43
	public $level = [
44
		'Head'      => 0,
45
		'pre_Body'  => 1,
46
		'Left'      => 3,
47
		'Top'       => 3,
48
		'Content'   => 4,
49
		'Bottom'    => 3,
50
		'Right'     => 3,
51
		'post_Body' => 1
52
	];
53
	/**
54
	 * @var array[]
55
	 */
56
	protected $link = [];
57
	/**
58
	 * @var string[]
59
	 */
60
	protected $search_replace = [];
61
	/**
62
	 * @var false|string
63
	 */
64
	protected $canonical_url      = false;
65
	protected $theme;
66
	protected $error_showed       = false;
67
	protected $finish_called_once = false;
68
	/**
69
	 * @param string $property
70
	 *
71
	 * @return false|null|string
1 ignored issue
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
72
	 */
73
	function __get ($property) {
74
		// For internal use by \cs\Meta class
75
		if ($property === 'canonical_url') {
76
			return $this->canonical_url;
77
		}
78
		return false;
79
	}
80
	/**
81
	 * Initialization: setting of title and theme according to specified parameters
82
	 *
83
	 * @param string $title
84
	 * @param string $theme
85
	 *
86
	 * @return Page
87
	 */
88
	function init ($title, $theme) {
89
		$this->Title[0] = htmlentities($title, ENT_COMPAT, 'utf-8');
90
		$this->set_theme($theme);
91
		return $this;
92
	}
93
	/**
94
	 * Theme changing
95
	 *
96
	 * @param string $theme
97
	 *
98
	 * @return Page
99
	 */
100
	function set_theme ($theme) {
101
		$this->theme = $theme;
102
		return $this;
103
	}
104
	/**
105
	 * Adding of content on the page
106
	 *
107
	 * @param string   $add
108
	 * @param bool|int $level
109
	 *
110
	 * @return Page
111
	 */
112
	function content ($add, $level = false) {
113
		if ($level !== false) {
114
			$this->Content .= h::level($add, $level);
115
		} else {
116
			$this->Content .= $add;
117
		}
118
		return $this;
119
	}
120
	/**
121
	 * Sets body with content, that is transformed into JSON format
122
	 *
123
	 * @param mixed $add
124
	 *
125
	 * @return Page
126
	 */
127
	function json ($add) {
128
		_header('Content-Type: application/json; charset=utf-8', true);
129
		interface_off();
130
		$this->Content = _json_encode($add);
131
		return $this;
132
	}
133
	/**
134
	 * Loading of theme template
135
	 *
136
	 * @return Page
137
	 */
138
	protected function get_template () {
139
		/**
140
		 * Theme is fixed for administration, and may vary for other pages
141
		 */
142
		if (admin_path()) {
143
			$this->theme = 'CleverStyle';
144
		}
145
		$theme_dir = THEMES."/$this->theme";
146
		_include("$theme_dir/prepare.php", false, false);
147
		ob_start();
148
		/**
149
		 * If website is closed and user is not an administrator - send `503 Service Unavailable` header and show closed site page
150
		 */
151
		if (
152
			!Config::instance()->core['site_mode'] &&
153
			!User::instance(true)->admin() &&
154
			status_code(503) &&
155
			!_include("$theme_dir/closed.php", false, false) &&
156
			!_include("$theme_dir/closed.html", false, false)
157
		) {
158
			echo
159
				"<!doctype html>\n".
1 ignored issue
show
Security Cross-Site Scripting introduced by
'<!doctype html> ' . \h:..._ml_text('closed_text') can contain request data and is used in output context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
160
				h::title(get_core_ml_text('closed_title')).
161
				get_core_ml_text('closed_text');
162
		} else {
163
			_include("$theme_dir/index.php", false, false) || _include("$theme_dir/index.html");
164
		}
165
		$this->Html = ob_get_clean();
166
		return $this;
167
	}
168
	/**
169
	 * Processing of template, substituting of content, preparing for the output
170
	 *
171
	 * @return Page
172
	 */
173
	protected function prepare () {
174
		$Config = Config::instance(true);
175
		/**
176
		 * Loading of template
177
		 */
178
		$this->get_template();
179
		/**
180
		 * Forming page title
181
		 */
182
		$this->Title = array_filter($this->Title, 'trim');
183
		$this->Title = $Config->core['title_reverse'] ? array_reverse($this->Title) : $this->Title;
184
		$this->Title = implode($Config->core['title_delimiter'] ?: '|', $this->Title);
185
		/**
186
		 * Forming <head> content
187
		 */
188
		$this->Head =
189
			h::title($this->Title).
190
			h::meta(
191
				[
192
					'charset' => 'utf-8'
193
				],
194
				$this->Description ? [
195
					'name'    => 'description',
196
					'content' => $this->Description
197
				] : false,
198
				[
199
					'name'    => 'generator',
200
					'content' => 'CleverStyle CMS by Mokrynskyi Nazar'
201
				]
202
			).
203
			h::base(
204
				$Config ? [
205
					'href' => $Config->base_url().'/'
206
				] : false
207
			).
208
			$this->Head.
209
			h::link(
210
				[
211
					'rel'  => 'shortcut icon',
212
					'href' => $this->get_favicon_path()
213
				]
214
			).
215
			h::link(array_values($this->link) ?: false);
216
		/**
217
		 * Addition of CSS, JavaScript and Web Components includes
218
		 */
219
		$this->add_includes_on_page();
220
		/**
221
		 * Generation of Open Graph protocol information
222
		 */
223
		Meta::instance()->render();
224
		/**
225
		 * Substitution of information into template
226
		 */
227
		$this->Html = str_replace(
228
			[
229
				'<!--pre_Html-->',
230
				'<!--head-->',
231
				'<!--pre_Body-->',
232
				'<!--left_blocks-->',
233
				'<!--top_blocks-->',
234
				'<!--content-->',
235
				'<!--bottom_blocks-->',
236
				'<!--right_blocks-->',
237
				'<!--post_Body-->',
238
				'<!--post_Html-->'
239
			],
240
			_rtrim(
241
				[
242
					$this->pre_Html,
243
					$this->get_property_with_indentation('Head'),
244
					$this->get_property_with_indentation('pre_Body'),
245
					$this->get_property_with_indentation('Left'),
246
					$this->get_property_with_indentation('Top'),
247
					$this->get_property_with_indentation('Content'),
248
					$this->get_property_with_indentation('Bottom'),
249
					$this->get_property_with_indentation('Right'),
250
					$this->get_property_with_indentation('post_Body'),
251
					$this->post_Html
252
				],
253
				"\t"
254
			),
255
			$this->Html
256
		);
257
		return $this;
258
	}
259
	/**
260
	 * @return string
261
	 */
262
	protected function get_favicon_path () {
263
		$file = file_exists_with_extension(THEMES."/$this->theme/img/favicon", ['png', 'ico']);
264
		if ($file) {
265
			return str_replace(THEMES, 'themes', $file);
266
		}
267
		return 'favicon.ico';
268
	}
269
	/**
270
	 * @param string $property
271
	 *
272
	 * @return string
273
	 */
274
	protected function get_property_with_indentation ($property) {
275
		return h::level($this->$property, $this->level[$property]);
276
	}
277
	/**
278
	 * Replacing anything in source code of finally generated page
279
	 *
280
	 * Parameters may be both simply strings for str_replace() and regular expressions for preg_replace()
281
	 *
282
	 * @param string|string[] $search
283
	 * @param string|string[] $replace
284
	 *
285
	 * @return Page
286
	 */
287
	function replace ($search, $replace = '') {
288
		if (is_array($search)) {
289
			$this->search_replace = $search + $this->search_replace;
290
		} else {
291
			$this->search_replace[$search] = $replace;
292
		}
293
		return $this;
294
	}
295
	/**
296
	 * Processing of replacing in content
297
	 *
298
	 * @param string $content
299
	 *
300
	 * @return string
301
	 */
302
	protected function process_replacing ($content) {
303
		foreach ($this->search_replace as $search => $replace) {
304
			$content = _preg_replace($search, $replace, $content) ?: str_replace($search, $replace, $content);
305
		}
306
		$this->search_replace = [];
307
		return $content;
308
	}
309
	/**
310
	 * Adding links
311
	 *
312
	 * @param array $data According to h class syntax
313
	 *
314
	 * @return Page
315
	 */
316
	function link ($data) {
317
		if ($data !== false) {
318
			$this->link[] = $data;
319
		}
320
		return $this;
321
	}
322
	/**
323
	 * Simple wrapper of $Page->link() for inserting Atom feed on page
324
	 *
325
	 * @param string $href
326
	 * @param string $title
327
	 *
328
	 * @return Page
329
	 */
330
	function atom ($href, $title = 'Atom Feed') {
331
		return $this->link(
332
			[
333
				'href'  => $href,
334
				'title' => $title,
335
				'rel'   => 'alternate',
336
				'type'  => 'application/atom+xml'
337
			]
338
		);
339
	}
340
	/**
341
	 * Simple wrapper of $Page->link() for inserting RSS feed on page
342
	 *
343
	 * @param string $href
344
	 * @param string $title
345
	 *
346
	 * @return Page
347
	 */
348
	function rss ($href, $title = 'RSS Feed') {
349
		return $this->link(
350
			[
351
				'href'  => $href,
352
				'title' => $title,
353
				'rel'   => 'alternate',
354
				'type'  => 'application/rss+xml'
355
			]
356
		);
357
	}
358
	/**
359
	 * Specify canonical url of current page
360
	 *
361
	 * @param string $url
362
	 *
363
	 * @return Page
364
	 */
365
	function canonical_url ($url) {
366
		$this->canonical_url         = $url;
367
		$this->link['canonical_url'] = [
368
			'href' => $this->canonical_url,
369
			'rel'  => 'canonical'
370
		];
371
		return $this;
372
	}
373
	/**
374
	 * Adding text to the title page
375
	 *
376
	 * @param string $title
377
	 * @param bool   $replace Replace whole title by this
378
	 *
379
	 * @return Page
380
	 */
381
	function title ($title, $replace = false) {
382
		$title = htmlentities($title, ENT_COMPAT, 'utf-8');
383
		if ($replace) {
384
			$this->Title = [$title];
385
		} else {
386
			$this->Title[] = $title;
387
		}
388
		return $this;
389
	}
390
	/**
391
	 * Display success message
392
	 *
393
	 * @param string $success_text
394
	 *
395
	 * @return Page
396
	 */
397
	function success ($success_text) {
398
		return $this->top_message($success_text, 'success');
399
	}
400
	/**
401
	 * Display notice message
402
	 *
403
	 * @param string $notice_text
404
	 *
405
	 * @return Page
406
	 */
407
	function notice ($notice_text) {
408
		return $this->top_message($notice_text, 'warning');
409
	}
410
	/**
411
	 * Display warning message
412
	 *
413
	 * @param string $warning_text
414
	 *
415
	 * @return Page
416
	 */
417
	function warning ($warning_text) {
418
		return $this->top_message($warning_text, 'error');
419
	}
420
	/**
421
	 * Generic method for 3 methods above
422
	 *
423
	 * @param string $message
424
	 * @param string $class_ending
425
	 *
426
	 * @return Page
427
	 */
428
	protected function top_message ($message, $class_ending) {
429
		$this->Top .= h::div(
430
			$message,
431
			[
432
				'class' => "cs-text-center cs-block-$class_ending cs-text-$class_ending"
433
			]
434
		);
435
		return $this;
436
	}
437
	/**
438
	 * Error pages processing
439
	 *
440
	 * @param null|string|string[] $custom_text Custom error text instead of text like "404 Not Found" or array with two elements: [error, error_description]
441
	 * @param bool                 $json        Force JSON return format
442
	 * @param int                  $error_code  HTTP status code
443
	 *
444
	 * @throws ExitException
445
	 */
446
	function error ($custom_text = null, $json = false, $error_code = 500) {
447
		if ($this->error_showed) {
448
			return;
449
		}
450
		$this->error_showed = true;
451
		/**
452
		 * Hack for 403 after sign out in administration
453
		 */
454
		if ($error_code == 403 && !api_path() && _getcookie('sign_out')) {
455
			_header('Location: /', true, 302);
456
			$this->Content = '';
457
			throw new ExitException;
458
		}
459
		interface_off();
460
		$status_text       = status_code($error_code);
461
		$error_description = $status_text;
462
		if (is_array($custom_text)) {
463
			list($error_code, $error_description) = $custom_text;
464
		} elseif ($custom_text) {
465
			$error_description = $custom_text;
466
		}
467
		if ($json || api_path()) {
468
			if ($json) {
469
				_header('Content-Type: application/json; charset=utf-8', true);
470
				interface_off();
471
			}
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_code($error_code)).
487
					$error_description;
488
			}
489
			$this->Content = ob_get_clean();
490
		}
491
		$this->__finish();
492
		throw new ExitException;
493
	}
494
	/**
495
	 * Provides next events:
496
	 *  System/Page/display/before
497
	 *
498
	 *  System/Page/display/after
499
	 *
500
	 * Page generation
501
	 */
502
	function __finish () {
503
		/**
504
		 * Protection from double calling
505
		 */
506
		if ($this->finish_called_once) {
507
			return;
508
		}
509
		$this->finish_called_once = true;
510
		/**
511
		 * For AJAX and API requests only content without page template
512
		 */
513
		$api = api_path();
514
		if ($api || !$this->interface) {
515
			/**
516
			 * Processing of replacing in content
517
			 */
518
			echo $this->process_replacing($this->Content ?: ($api ? 'null' : ''));
1 ignored issue
show
Security Cross-Site Scripting introduced by
$this->process_replacing...: ($api ? 'null' : '')) can contain request data and is used in output context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
519
		} else {
520
			Event::instance()->fire('System/Page/display/before');
521
			/**
522
			 * Processing of template, substituting of content, preparing for the output
523
			 */
524
			$this->prepare();
525
			/**
526
			 * Processing of replacing in content
527
			 */
528
			$this->Html = $this->process_replacing($this->Html);
529
			Event::instance()->fire('System/Page/display/after');
530
			echo rtrim($this->Html);
1 ignored issue
show
Security Cross-Site Scripting introduced by
rtrim($this->Html) can contain request data and is used in output context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
531
		}
532
	}
533
}
534