Completed
Push — master ( 764bc6...7e26d6 )
by Nazar
07:43
created

Router::execute_router()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 4
eloc 14
nc 4
nop 1
dl 0
loc 20
ccs 14
cts 14
cp 1
crap 4
rs 9.2
c 1
b 0
f 1
1
<?php
2
/**
3
 * @package   CleverStyle Framework
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 14
	protected function init_router () {
34 14
		$this->controller_path   = ['index'];
35 14
		$this->working_directory = '';
36 14
	}
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 8
	protected function execute_router ($Request) {
47 8
		$this->working_directory = $this->get_working_directory($Request);
48 8
		$this->check_and_normalize_route($Request);
49 8
		if (!Event::instance()->fire('System/App/execute_router/before')) {
50 2
			return;
51
		}
52
		/**
53
		 * If module consists of index.html only
54
		 */
55 8
		if (file_exists("$this->working_directory/index.html")) {
56 2
			ob_start();
57 2
			include "$this->working_directory/index.html";
58 2
			Page::instance()->content(ob_get_clean());
59 8
		} elseif (file_exists("$this->working_directory/Controller.php")) {
60 8
			$this->controller_router($Request);
61
		} else {
62 2
			$this->files_router($Request);
63
		}
64 8
		Event::instance()->fire('System/App/execute_router/after');
65 8
	}
66
	/**
67
	 * @param \cs\Request $Request
68
	 *
69
	 * @return string
70
	 *
71
	 * @throws ExitException
72
	 */
73 8
	protected function get_working_directory ($Request) {
74 8
		$working_directory = MODULES."/$Request->current_module";
75 8
		if ($Request->cli_path) {
76
			$working_directory .= '/cli';
77 8
		} elseif ($Request->admin_path) {
78 2
			$working_directory .= '/admin';
79 8
		} elseif ($Request->api_path) {
80 4
			$working_directory .= '/api';
81
		}
82
		// CLI interface will print useful info instead of 404
83 8
		if (!is_dir($working_directory) && (!$Request->cli_path || $Request->method != 'CLI')) {
84 2
			throw new ExitException(404);
85
		}
86 8
		return $working_directory;
87
	}
88
	/**
89
	 * Normalize `cs\Request::$route_path` and fill `cs\App::$controller_path`
90
	 *
91
	 * @param \cs\Request $Request
92
	 *
93
	 * @throws ExitException
94
	 */
95 8
	protected function check_and_normalize_route ($Request) {
96 8
		if (!file_exists("$this->working_directory/index.json")) {
97 2
			return;
98
		}
99 8
		$structure = file_get_json("$this->working_directory/index.json");
100 8
		if (!$structure) {
101
			return;
102
		}
103 8
		for ($nesting_level = 0; $structure; ++$nesting_level) {
104
			/**
105
			 * Next level of routing path
106
			 */
107 8
			$path = @$Request->route_path[$nesting_level];
108
			/**
109
			 * If path not specified - take first from structure
110
			 */
111 8
			$this->check_and_normalize_route_internal($Request, $path, $structure, $Request->cli_path || $Request->api_path);
112 8
			$Request->route_path[$nesting_level] = $path;
113
			/**
114
			 * Fill paths array intended for controller's usage
115
			 */
116 8
			$this->controller_path[] = $path;
117
			/**
118
			 * If nested structure is not available - we'll not go into next iteration of this cycle
119
			 */
120 8
			$structure = @$structure[$path];
121
		}
122 8
	}
123
	/**
124
	 * @param \cs\Request $Request
125
	 * @param string      $path
126
	 * @param array       $structure
127
	 * @param bool        $cli_or_api_path
128
	 *
129
	 * @throws ExitException
130
	 */
131 8
	protected function check_and_normalize_route_internal ($Request, &$path, $structure, $cli_or_api_path) {
132
		/**
133
		 * If path not specified - take first from structure
134
		 */
135 8
		if (!$path) {
136 4
			$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 4
			if ($path !== '_' && $cli_or_api_path) {
141 4
				throw new ExitException(404);
142
			}
143 4
		} elseif (!isset($structure[$path]) && !in_array($path, $structure)) {
144
			throw new ExitException(404);
145
		}
146 8
		if (!$this->check_permission($Request, $path)) {
147
			throw new ExitException(403);
148
		}
149 8
	}
150
	/**
151
	 * 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
152
	 * methods also doesn't exist
153
	 *
154
	 * @param string[]    $available_methods
155
	 * @param string      $request_method
156
	 * @param \cs\Request $Request
157
	 *
158
	 * @throws ExitException
159
	 */
160
	protected function handler_not_found ($available_methods, $request_method, $Request) {
161
		if ($available_methods) {
162
			if ($Request->cli_path) {
163
				$this->print_cli_structure($Request->path);
164
				if ($request_method !== 'cli') {
165
					throw new ExitException(501);
166
				}
167
			} else {
168
				Response::instance()->header('Allow', implode(', ', $available_methods));
169
				if ($request_method !== 'options') {
170
					throw new ExitException(501);
171
				}
172
			}
173
		} else {
174
			throw new ExitException(404);
175
		}
176
	}
177
}
178