Completed
Push — master ( 105618...88380e )
by Nazar
04:15
created

Router   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 161
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 4
Bugs 0 Features 1
Metric Value
c 4
b 0
f 1
dl 0
loc 161
rs 10
wmc 30
lcom 1
cbo 8

6 Methods

Rating   Name   Duplication   Size   Complexity  
A init_router() 0 4 1
B execute_router() 0 22 4
B get_working_directory() 0 14 7
B check_and_normalize_route() 0 28 5
B check_and_normalize_route_internal() 0 20 8
B handler_not_found() 0 17 5
1
<?php
2
/**
3
 * @package   CleverStyle CMS
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2015-2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs\App;
9
use
10
	cs\Event,
11
	cs\ExitException,
12
	cs\Page,
13
	cs\Response,
14
	cs\App\Router\CLI,
15
	cs\App\Router\Controller,
16
	cs\App\Router\Files;
17
18
trait Router {
19
	use
20
		CLI,
21
		Controller,
22
		Files;
23
	/**
24
	 * Path that will be used by controller to render page
25
	 *
26
	 * @var string[]
27
	 */
28
	protected $controller_path;
29
	/**
30
	 * @var string
31
	 */
32
	protected $working_directory;
33
	protected function init_router () {
34
		$this->controller_path   = ['index'];
35
		$this->working_directory = '';
36
	}
37
	/**
38
	 * Execute router
39
	 *
40
	 * Depending on module, files-based or controller-based router might be used
41
	 *
42
	 * @param \cs\Request $Request
43
	 *
44
	 * @throws ExitException
45
	 */
46
	protected function execute_router ($Request) {
47
		$this->working_directory = $this->get_working_directory($Request);
48
		/**
49
		 * If module consists of index.html only
50
		 */
51
		if (file_exists("$this->working_directory/index.html")) {
52
			ob_start();
53
			_include("$this->working_directory/index.html", false, false);
54
			Page::instance()->content(ob_get_clean());
55
			return;
56
		}
57
		$this->check_and_normalize_route($Request);
58
		if (!Event::instance()->fire('System/App/execute_router/before')) {
59
			return;
60
		}
61
		if (file_exists("$this->working_directory/Controller.php")) {
62
			$this->controller_router($Request);
63
		} else {
64
			$this->files_router($Request);
65
		}
66
		Event::instance()->fire('System/App/execute_router/after');
67
	}
68
	/**
69
	 * @param \cs\Request $Request
70
	 *
71
	 * @return string
72
	 *
73
	 * @throws ExitException
74
	 */
75
	protected function get_working_directory ($Request) {
76
		$working_directory = MODULES."/$Request->current_module";
77
		if ($Request->cli_path) {
78
			$working_directory .= '/cli';
79
		} elseif ($Request->admin_path) {
80
			$working_directory .= '/admin';
81
		} elseif ($Request->api_path) {
82
			$working_directory .= '/api';
83
		}
84
		if (!is_dir($working_directory) && (!$Request->cli_path || $Request->method != 'CLI')) {
85
			throw new ExitException(404);
86
		}
87
		return $working_directory;
88
	}
89
	/**
90
	 * Normalize `cs\Request::$route_path` and fill `cs\App::$controller_path`
91
	 *
92
	 * @param \cs\Request $Request
93
	 *
94
	 * @throws ExitException
95
	 */
96
	protected function check_and_normalize_route ($Request) {
97
		if (!file_exists("$this->working_directory/index.json")) {
98
			return;
99
		}
100
		$structure = file_get_json("$this->working_directory/index.json");
101
		if (!$structure) {
102
			return;
103
		}
104
		for ($nesting_level = 0; $structure; ++$nesting_level) {
105
			/**
106
			 * Next level of routing path
107
			 */
108
			$path = @$Request->route_path[$nesting_level];
109
			/**
110
			 * If path not specified - take first from structure
111
			 */
112
			$this->check_and_normalize_route_internal($path, $structure, $Request->cli_path || $Request->api_path);
113
			$Request->route_path[$nesting_level] = $path;
114
			/**
115
			 * Fill paths array intended for controller's usage
116
			 */
117
			$this->controller_path[] = $path;
118
			/**
119
			 * If nested structure is not available - we'll not go into next iteration of this cycle
120
			 */
121
			$structure = @$structure[$path];
122
		}
123
	}
124
	/**
125
	 * @param string $path
126
	 * @param array  $structure
127
	 * @param bool   $cli_or_api_path
128
	 *
129
	 * @throws ExitException
130
	 */
131
	protected function check_and_normalize_route_internal (&$path, $structure, $cli_or_api_path) {
132
		/**
133
		 * If path not specified - take first from structure
134
		 */
135
		if (!$path) {
136
			$path = isset($structure[0]) ? $structure[0] : array_keys($structure)[0];
137
			/**
138
			 * We need exact paths for CLI and API request (or `_` ending if available) and less strict mode for other cases that allows go deeper automatically
139
			 */
140
			if ($path !== '_' && $cli_or_api_path) {
141
				throw new ExitException(404);
142
			}
143
		} elseif (!isset($structure[$path]) && !in_array($path, $structure)) {
144
			throw new ExitException(404);
145
		}
146
		/** @noinspection PhpUndefinedMethodInspection */
147
		if (!$this->check_permission($path)) {
148
			throw new ExitException(403);
149
		}
150
	}
151
	/**
152
	 * If HTTP method handler not found we generate either `501 Not Implemented` if other methods are supported or `404 Not Found` if handlers for others
153
	 * methods also doesn't exist
154
	 *
155
	 * @param string[]    $available_methods
156
	 * @param string      $request_method
157
	 * @param \cs\Request $Request
158
	 *
159
	 * @throws ExitException
160
	 */
161
	protected function handler_not_found ($available_methods, $request_method, $Request) {
162
		if ($available_methods) {
163
			if ($Request->cli_path) {
164
				$this->print_cli_structure($Request->path);
165
				if ($request_method !== 'cli') {
166
					throw new ExitException(501);
167
				}
168
			} else {
169
				Response::instance()->header('Allow', implode(', ', $available_methods));
170
				if ($request_method !== 'options') {
171
					throw new ExitException(501);
172
				}
173
			}
174
		} else {
175
			throw new ExitException(404);
176
		}
177
	}
178
}
179