Completed
Push — master ( a872a4...078041 )
by Nazar
06:38
created

Page   B

Complexity

Total Complexity 54

Size/Duplication

Total Lines 570
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 91.08%

Importance

Changes 0
Metric Value
dl 0
loc 570
ccs 143
cts 157
cp 0.9108
rs 7.0642
c 0
b 0
f 0
wmc 54
lcom 1
cbo 7

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __get() 0 7 2
A get_template() 0 12 3
A get_favicon_path() 0 7 2
A get_property_with_indentation() 0 3 1
A replace() 0 9 2
A process_replacing() 0 7 3
A link() 0 6 2
A atom() 0 10 1
A rss() 0 10 1
A canonical_url() 0 8 1
A title() 0 9 2
A success() 0 3 1
A notice() 0 3 1
A warning() 0 3 1
A top_message() 0 9 1
C error() 0 27 8
A error_title_description() 0 8 3
A error_page() 0 13 3
A init() 0 51 2
A content() 0 8 2
B prepare() 0 91 6
A json() 0 6 1
B render() 0 29 5

How to fix   Complexity   

Complex Class

Complex classes like Page often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Page, and based on these observations, apply Extract Interface, too.

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