Completed
Push — master ( 388f42...3586c8 )
by Nazar
05:18
created

Page::json()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 6
ccs 0
cts 0
cp 0
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package   CleverStyle Framework
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
use function
14
	cli\err;
15
16
/**
17
 * Provides next events:
18
 *  System/Page/includes_dependencies_and_map
19
 *  [
20
 *    'dependencies' => &$dependencies,
21
 *    'includes_map' => &$includes_map
22
 *  ]
23
 *
24
 *  System/Page/rebuild_cache
25
 *  [
26
 *    'key' => &$key //Reference to the key, that will be appended to all css and js files, can be changed to reflect JavaScript and CSS changes
27
 *  ]
28
 *
29
 *  System/Page/requirejs
30
 *  [
31
 *    'paths'                 => &$paths,                // The same as `paths` in requirejs.config()
32
 *    'directories_to_browse' => &$directories_to_browse // Location of AMD modules (typically bower_components and node_modules directories, absolute paths)
33
 *  ]
34
 *
35
 * @method static $this instance($check = false)
36
 */
37
class Page {
38
	use
39
		Singleton,
40
		Includes;
41
	const INIT_STATE_METHOD = 'init';
42
	/**
43
	 * Complete page contents
44
	 *
45
	 * @var string
46
	 */
47
	public $Content;
48
	/**
49
	 * 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)
50
	 *
51
	 * @var bool
52
	 */
53
	public $interface;
54
	/**
55
	 * @var string
56
	 */
57
	public $pre_Html;
58
	/**
59
	 * @var string
60
	 */
61
	public $Html;
62
	/**
63
	 * @var string
64
	 */
65
	public $Description;
66
	/**
67
	 * @var string|string[]
68
	 */
69
	public $Title;
70
	/**
71
	 * @var string
72
	 */
73
	public $Head;
74
	/**
75
	 * @var string
76
	 */
77
	public $pre_Body;
78
	/**
79
	 * @var string
80
	 */
81
	public $Left;
82
	/**
83
	 * @var string
84
	 */
85
	public $Top;
86
	/**
87
	 * @var string
88
	 */
89
	public $Right;
90
	/**
91
	 * @var string
92
	 */
93
	public $Bottom;
94
	/**
95
	 * @var string
96
	 */
97
	public $post_Body;
98
	/**
99
	 * @var string
100
	 */
101
	public $post_Html;
102
	/**
103
	 * Number of tabs by default for indentation the substitution of values into template
104
	 *
105
	 * @var array
106
	 */
107
	public $level;
108
	/**
109
	 * @var array[]
110
	 */
111
	protected $link;
112
	/**
113
	 * @var string[]
114
	 */
115
	protected $search_replace;
116
	/**
117
	 * @var false|string
118
	 */
119
	protected $canonical_url;
120
	protected $theme;
121
	protected $finish_called_once;
122
	/**
123
	 * @param string $property
124
	 *
125
	 * @return false|null|string
126
	 */
127
	function __get ($property) {
128
		// Hack: for internal use by \cs\Meta class
129
		if ($property === 'canonical_url') {
130
			return $this->canonical_url;
131
		}
132
		return false;
133
	}
134 36
	protected function init () {
135 36
		$this->Content            = '';
136 36
		$this->interface          = true;
137 36
		$this->pre_Html           = '';
138 36
		$this->Html               = '';
139 36
		$this->Description        = '';
140 36
		$this->Title              = [];
141 36
		$this->Head               = '';
142 36
		$this->pre_Body           = '';
143 36
		$this->Left               = '';
144 36
		$this->Top                = '';
145 36
		$this->Right              = '';
146 36
		$this->Bottom             = '';
147 36
		$this->post_Body          = '';
148 36
		$this->post_Html          = '';
149 36
		$this->level              = [
150
			'Head'      => 0,
151
			'pre_Body'  => 1,
152
			'Left'      => 3,
153
			'Top'       => 3,
154
			'Content'   => 4,
155
			'Bottom'    => 3,
156
			'Right'     => 3,
157
			'post_Body' => 1
158
		];
159 36
		$this->link               = [];
160 36
		$this->search_replace     = [];
161 36
		$this->canonical_url      = false;
162 36
		$this->theme              = null;
163 36
		$this->finish_called_once = false;
164 36
		$this->init_includes();
165 36
		$Config = Config::instance(true);
166
		/**
167
		 * We need Config for initialization
168
		 */
169 36
		if (!$Config) {
170 2
			Event::instance()->once(
171 2
				'System/Config/init/after',
172
				function () {
173
					$this->theme = Config::instance()->core['theme'];
174 2
				}
175
			);
176
		} else {
177 34
			$this->theme = Config::instance()->core['theme'];
178
		}
179 36
		Event::instance()->on(
180 36
			'System/Config/changed',
181 36
			function () {
182
				$this->theme = Config::instance()->core['theme'];
183 36
			}
184
		);
185 36
	}
186
	/**
187
	 * Adding of content on the page
188
	 *
189
	 * @param string   $add
190
	 * @param bool|int $level
191
	 *
192
	 * @return Page
193
	 */
194
	function content ($add, $level = false) {
195
		if ($level !== false) {
196
			$this->Content .= h::level($add, $level);
197
		} else {
198
			$this->Content .= $add;
199
		}
200
		return $this;
201
	}
202
	/**
203
	 * Sets body with content, that is transformed into JSON format
204
	 *
205
	 * @param mixed $add
206
	 *
207
	 * @return Page
208
	 */
209
	function json ($add) {
210
		Response::instance()->header('content-type', 'application/json; charset=utf-8');
211
		$this->interface = false;
212
		$this->Content   = _json_encode($add);
213
		return $this;
214
	}
215
	/**
216
	 * Loading of theme template
217
	 */
218
	protected function get_template () {
219
		/**
220
		 * Theme is fixed for administration, and may vary for other pages
221
		 */
222
		if (Request::instance()->admin_path) {
223
			$this->theme = 'CleverStyle';
224
		}
225
		ob_start();
226
		$theme_dir = THEMES."/$this->theme";
227
		_include("$theme_dir/index.php", false, false) || _include("$theme_dir/index.html");
228
		$this->Html = ob_get_clean();
229
	}
230
	/**
231
	 * Processing of template, substituting of content, preparing for the output
232
	 *
233
	 * @return Page
234
	 */
235
	protected function prepare () {
236
		$Config = Config::instance(true);
237
		/**
238
		 * Loading of template
239
		 */
240
		$this->get_template();
241
		/**
242
		 * Forming page title
243
		 */
244
		$this->Title = array_filter($this->Title, 'trim');
245
		array_unshift($this->Title, get_core_ml_text('name'));
246
		$this->Title = $Config->core['title_reverse'] ? array_reverse($this->Title) : $this->Title;
247
		$this->Title = implode($Config->core['title_delimiter'] ?: '|', $this->Title);
248
		/**
249
		 * Addition of CSS, JavaScript and Web Components includes
250
		 */
251
		$this->add_includes_on_page();
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 Framework 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
		 * Generation of Open Graph protocol information
289
		 */
290
		Meta::instance()->render();
291
		/**
292
		 * Substitution of information into template
293
		 */
294
		$this->Html = str_replace(
295
			[
296
				'<!--pre_Html-->',
297
				'<!--head-->',
298
				'<!--pre_Body-->',
299
				'<!--left_blocks-->',
300
				'<!--top_blocks-->',
301
				'<!--content-->',
302
				'<!--bottom_blocks-->',
303
				'<!--right_blocks-->',
304
				'<!--post_Body-->',
305
				'<!--post_Html-->'
306
			],
307
			_rtrim(
308
				[
309
					$this->pre_Html,
310
					$this->get_property_with_indentation('Head'),
311
					$this->get_property_with_indentation('pre_Body'),
312
					$this->get_property_with_indentation('Left'),
313
					$this->get_property_with_indentation('Top'),
314
					$this->get_property_with_indentation('Content'),
315
					$this->get_property_with_indentation('Bottom'),
316
					$this->get_property_with_indentation('Right'),
317
					$this->get_property_with_indentation('post_Body'),
318
					$this->post_Html
319
				],
320
				"\t"
321
			),
322
			$this->Html
323
		);
324
		return $this;
325
	}
326
	/**
327
	 * @return string
328
	 */
329
	protected function get_favicon_path () {
330
		$file = file_exists_with_extension(THEMES."/$this->theme/img/favicon", ['png', 'ico']);
331
		if ($file) {
332
			return str_replace(THEMES, 'themes', $file);
333
		}
334
		return str_replace(DIR.'/', '', file_exists_with_extension(DIR."/favicon", ['png', 'ico']));
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal /favicon does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
335
	}
336
	/**
337
	 * @param string $property
338
	 *
339
	 * @return string
340
	 */
341
	protected function get_property_with_indentation ($property) {
342
		return h::level($this->$property, $this->level[$property]);
343
	}
344
	/**
345
	 * Replacing anything in source code of finally generated page
346
	 *
347
	 * Parameters may be both simply strings for str_replace() and regular expressions for preg_replace()
348
	 *
349
	 * @param string|string[] $search
350
	 * @param string|string[] $replace
351
	 *
352
	 * @return Page
353
	 */
354
	function replace ($search, $replace = '') {
355
		if (is_array($search)) {
356
			$this->search_replace = $search + $this->search_replace;
357
		} else {
358
			/** @noinspection OffsetOperationsInspection */
359
			$this->search_replace[$search] = $replace;
360
		}
361
		return $this;
362
	}
363
	/**
364
	 * Processing of replacing in content
365
	 *
366
	 * @param string $content
367
	 *
368
	 * @return string
369
	 */
370
	protected function process_replacing ($content) {
371
		foreach ($this->search_replace as $search => $replace) {
372
			$content = _preg_replace($search, $replace, $content) ?: str_replace($search, $replace, $content);
373
		}
374
		$this->search_replace = [];
375
		return $content;
376
	}
377
	/**
378
	 * Adding links
379
	 *
380
	 * @param array $data According to h class syntax
381
	 *
382
	 * @return Page
383
	 */
384
	function link ($data) {
385
		if ($data !== false) {
386
			$this->link[] = $data;
387
		}
388
		return $this;
389
	}
390
	/**
391
	 * Simple wrapper of $Page->link() for inserting Atom feed on page
392
	 *
393
	 * @param string $href
394
	 * @param string $title
395
	 *
396
	 * @return Page
397
	 */
398
	function atom ($href, $title = 'Atom Feed') {
399
		return $this->link(
400
			[
401
				'href'  => $href,
402
				'title' => $title,
403
				'rel'   => 'alternate',
404
				'type'  => 'application/atom+xml'
405
			]
406
		);
407
	}
408
	/**
409
	 * Simple wrapper of $Page->link() for inserting RSS feed on page
410
	 *
411
	 * @param string $href
412
	 * @param string $title
413
	 *
414
	 * @return Page
415
	 */
416
	function rss ($href, $title = 'RSS Feed') {
417
		return $this->link(
418
			[
419
				'href'  => $href,
420
				'title' => $title,
421
				'rel'   => 'alternate',
422
				'type'  => 'application/rss+xml'
423
			]
424
		);
425
	}
426
	/**
427
	 * Specify canonical url of current page
428
	 *
429
	 * @param string $url
430
	 *
431
	 * @return Page
432
	 */
433
	function canonical_url ($url) {
434
		$this->canonical_url         = $url;
435
		$this->link['canonical_url'] = [
436
			'href' => $this->canonical_url,
437
			'rel'  => 'canonical'
438
		];
439
		return $this;
440
	}
441
	/**
442
	 * Adding text to the title page
443
	 *
444
	 * @param string $title
445
	 * @param bool   $replace Replace whole title by this
446
	 *
447
	 * @return Page
448
	 */
449
	function title ($title, $replace = false) {
450
		$title = htmlentities($title, ENT_COMPAT, 'utf-8');
451
		if ($replace) {
452
			$this->Title = [$title];
453
		} else {
454
			$this->Title[] = $title;
455
		}
456
		return $this;
457
	}
458
	/**
459
	 * Display success message
460
	 *
461
	 * @param string $success_text
462
	 *
463
	 * @return Page
464
	 */
465
	function success ($success_text) {
466
		return $this->top_message($success_text, 'success');
467
	}
468
	/**
469
	 * Display notice message
470
	 *
471
	 * @param string $notice_text
472
	 *
473
	 * @return Page
474
	 */
475
	function notice ($notice_text) {
476
		return $this->top_message($notice_text, 'warning');
477
	}
478
	/**
479
	 * Display warning message
480
	 *
481
	 * @param string $warning_text
482
	 *
483
	 * @return Page
484
	 */
485
	function warning ($warning_text) {
486
		return $this->top_message($warning_text, 'error');
487
	}
488
	/**
489
	 * Generic method for 3 methods above
490
	 *
491
	 * @param string $message
492
	 * @param string $class_ending
493
	 *
494
	 * @return Page
495
	 */
496
	protected function top_message ($message, $class_ending) {
497
		$this->Top .= h::div(
498
			$message,
499
			[
500
				'class' => "cs-text-center cs-block-$class_ending cs-text-$class_ending"
501
			]
502
		);
503
		return $this;
504
	}
505
	/**
506
	 * Error pages processing
507
	 *
508
	 * @param null|string|string[] $custom_text Custom error text instead of text like "404 Not Found" or array with two elements: [error, error_description]
509
	 * @param bool                 $json        Force JSON return format
510
	 */
511
	function error ($custom_text = null, $json = false) {
512
		$Request  = Request::instance();
513
		$Response = Response::instance();
514
		$code     = $Response->code;
515
		/**
516
		 * Hack for 403 after sign out in administration
517
		 */
518
		if ($code == 403 && !$Request->api_path && $Request->cookie('sign_out')) {
519
			$Response->redirect('/');
520
			return;
521
		}
522
		list($title, $description) = $this->error_title_description($code, $custom_text);
523
		if ($json || $Request->api_path) {
524
			$this->json(
525
				[
526
					'error'             => $code,
527
					'error_description' => $description
528
				]
529
			);
530
		} elseif ($Request->cli_path) {
531
			$content = $title != $description ? "$title\n$description" : $description;
532
			err("%r$content%n");
533
		} else {
534
			$this->Content = $this->error_page($title, $description);
535
		}
536
		$Response->body = $this->Content;
537
	}
538
	/**
539
	 * @param int                  $code
540
	 * @param null|string|string[] $custom_text
541
	 *
542
	 * @return string[]
543
	 */
544
	protected function error_title_description ($code, $custom_text) {
545
		$title       = status_code_string($code);
546
		$description = $custom_text ?: $title;
547
		if (is_array($custom_text)) {
548
			list($title, $description) = $custom_text;
549
		}
550
		return [$title, $description];
551
	}
552
	/**
553
	 * @param string $title
554
	 * @param string $description
555
	 *
556
	 * @return string
557
	 */
558
	protected function error_page ($title, $description) {
559
		ob_start();
560
		if (
561
			!_include(THEMES."/$this->theme/error.html", false, false) &&
562
			!_include(THEMES."/$this->theme/error.php", false, false)
563
		) {
564
			echo
565
				"<!doctype html>\n".
566
				h::title($title).
567
				$description;
568
		}
569
		return ob_get_clean();
570
	}
571
	/**
572
	 * Provides next events:
573
	 *  System/Page/render/before
574
	 *
575
	 *  System/Page/render/after
576
	 *
577
	 * Page generation
578
	 */
579
	function render () {
580
		/**
581
		 * Protection from double calling
582
		 */
583
		if ($this->finish_called_once) {
584
			return;
585
		}
586
		$this->finish_called_once = true;
587
		$Response                 = Response::instance();
588
		if (is_resource($Response->body_stream)) {
589
			return;
590
		}
591
		/**
592
		 * For CLI, API and generally JSON responses only content without page template
593
		 */
594
		$Request = Request::instance();
595
		if ($Request->cli_path || $Request->api_path || !$this->interface) {
596
			/**
597
			 * Processing of replacing in content
598
			 */
599
			/** @noinspection NestedTernaryOperatorInspection */
600
			$Response->body = $this->process_replacing(strlen($this->Content) ? $this->Content : ($Request->api_path ? 'null' : ''));
601
		} else {
602
			Event::instance()->fire('System/Page/render/before');
603
			/**
604
			 * Processing of template, substituting of content, preparing for the output
605
			 */
606
			$this->prepare();
607
			/**
608
			 * Processing of replacing in content
609
			 */
610
			$this->Html = $this->process_replacing($this->Html);
611
			Event::instance()->fire('System/Page/render/after');
612
			$Response->body = rtrim($this->Html);
613
		}
614
	}
615
}
616