Passed
Push — master ( 8ffa76...acdc68 )
by Roeland
10:43 queued 12s
created

RouteConfig::buildRootPrefix()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 6
nop 2
dl 0
loc 15
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Bernhard Posselt <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Patrick Paysant <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Robin McCorkell <[email protected]>
14
 * @author Roeland Jago Douma <[email protected]>
15
 * @author Thomas Müller <[email protected]>
16
 *
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program. If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
33
namespace OC\AppFramework\Routing;
34
35
use OC\AppFramework\DependencyInjection\DIContainer;
36
use OCP\Route\IRouter;
37
38
/**
39
 * Class RouteConfig
40
 * @package OC\AppFramework\routing
41
 */
42
class RouteConfig {
43
	/** @var DIContainer */
44
	private $container;
45
46
	/** @var IRouter */
47
	private $router;
48
49
	/** @var array */
50
	private $routes;
51
52
	/** @var string */
53
	private $appName;
54
55
	/** @var string[] */
56
	private $controllerNameCache = [];
57
58
	protected $rootUrlApps = [
59
		'cloud_federation_api',
60
		'core',
61
		'files_sharing',
62
		'files',
63
		'settings',
64
		'spreed',
65
	];
66
67
	/**
68
	 * @param \OC\AppFramework\DependencyInjection\DIContainer $container
69
	 * @param \OCP\Route\IRouter $router
70
	 * @param array $routes
71
	 * @internal param $appName
72
	 */
73
	public function __construct(DIContainer $container, IRouter $router, $routes) {
74
		$this->routes = $routes;
75
		$this->container = $container;
76
		$this->router = $router;
77
		$this->appName = $container['AppName'];
78
	}
79
80
	/**
81
	 * The routes and resource will be registered to the \OCP\Route\IRouter
82
	 */
83
	public function register() {
84
85
		// parse simple
86
		$this->processIndexRoutes($this->routes);
87
88
		// parse resources
89
		$this->processIndexResources($this->routes);
90
91
		/*
92
		 * OCS routes go into a different collection
93
		 */
94
		$oldCollection = $this->router->getCurrentCollection();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Route\IRouter::getCurrentCollection() has been deprecated: 9.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

94
		$oldCollection = /** @scrutinizer ignore-deprecated */ $this->router->getCurrentCollection();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
95
		$this->router->useCollection($oldCollection . '.ocs');
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Route\IRouter::useCollection() has been deprecated: 9.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

95
		/** @scrutinizer ignore-deprecated */ $this->router->useCollection($oldCollection . '.ocs');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
96
97
		// parse ocs simple routes
98
		$this->processOCS($this->routes);
99
100
		// parse ocs simple routes
101
		$this->processOCSResources($this->routes);
102
103
		$this->router->useCollection($oldCollection);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Route\IRouter::useCollection() has been deprecated: 9.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

103
		/** @scrutinizer ignore-deprecated */ $this->router->useCollection($oldCollection);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
104
	}
105
106
	private function processOCS(array $routes): void {
107
		$ocsRoutes = $routes['ocs'] ?? [];
108
		foreach ($ocsRoutes as $ocsRoute) {
109
			$this->processRoute($ocsRoute, 'ocs.');
110
		}
111
	}
112
113
	/**
114
	 * Creates one route base on the give configuration
115
	 * @param array $routes
116
	 * @throws \UnexpectedValueException
117
	 */
118
	private function processIndexRoutes(array $routes): void {
119
		$simpleRoutes = $routes['routes'] ?? [];
120
		foreach ($simpleRoutes as $simpleRoute) {
121
			$this->processRoute($simpleRoute);
122
		}
123
	}
124
125
	protected function processRoute(array $route, string $routeNamePrefix = ''): void {
126
		$name = $route['name'];
127
		$postfix = $route['postfix'] ?? '';
128
		$root = $this->buildRootPrefix($route, $routeNamePrefix);
129
130
		$url = $root . '/' . ltrim($route['url'], '/');
131
		$verb = strtoupper($route['verb'] ?? 'GET');
132
133
		$split = explode('#', $name, 2);
134
		if (count($split) !== 2) {
135
			throw new \UnexpectedValueException('Invalid route name');
136
		}
137
		list($controller, $action) = $split;
138
139
		$controllerName = $this->buildControllerName($controller);
140
		$actionName = $this->buildActionName($action);
141
142
		$routeName = $routeNamePrefix . $this->appName . '.' . $controller . '.' . $action . $postfix;
143
144
		// register the route
145
		$handler = new RouteActionHandler($this->container, $controllerName, $actionName);
146
147
		$router = $this->router->create($routeName, $url)
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Route\IRouter::create() has been deprecated: 9.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

147
		$router = /** @scrutinizer ignore-deprecated */ $this->router->create($routeName, $url)

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
148
			->method($verb)
149
			->action($handler);
150
151
		// optionally register requirements for route. This is used to
152
		// tell the route parser how url parameters should be matched
153
		if (array_key_exists('requirements', $route)) {
154
			$router->requirements($route['requirements']);
155
		}
156
157
		// optionally register defaults for route. This is used to
158
		// tell the route parser how url parameters should be default valued
159
		if (array_key_exists('defaults', $route)) {
160
			$router->defaults($route['defaults']);
161
		}
162
	}
163
164
	/**
165
	 * For a given name and url restful OCS routes are created:
166
	 *  - index
167
	 *  - show
168
	 *  - create
169
	 *  - update
170
	 *  - destroy
171
	 *
172
	 * @param array $routes
173
	 */
174
	private function processOCSResources(array $routes): void {
175
		$this->processResources($routes['ocs-resources'] ?? [], 'ocs.');
176
	}
177
178
	/**
179
	 * For a given name and url restful routes are created:
180
	 *  - index
181
	 *  - show
182
	 *  - create
183
	 *  - update
184
	 *  - destroy
185
	 *
186
	 * @param array $routes
187
	 */
188
	private function processIndexResources(array $routes): void {
189
		$this->processResources($routes['resources'] ?? []);
190
	}
191
192
	/**
193
	 * For a given name and url restful routes are created:
194
	 *  - index
195
	 *  - show
196
	 *  - create
197
	 *  - update
198
	 *  - destroy
199
	 *
200
	 * @param array $resources
201
	 * @param string $routeNamePrefix
202
	 */
203
	protected function processResources(array $resources, string $routeNamePrefix = ''): void {
204
		// declaration of all restful actions
205
		$actions = [
206
			['name' => 'index', 'verb' => 'GET', 'on-collection' => true],
207
			['name' => 'show', 'verb' => 'GET'],
208
			['name' => 'create', 'verb' => 'POST', 'on-collection' => true],
209
			['name' => 'update', 'verb' => 'PUT'],
210
			['name' => 'destroy', 'verb' => 'DELETE'],
211
		];
212
213
		foreach ($resources as $resource => $config) {
214
			$root = $this->buildRootPrefix($config, $routeNamePrefix);
215
216
			// the url parameter used as id to the resource
217
			foreach ($actions as $action) {
218
				$url = $root . '/' . ltrim($config['url'], '/');
219
				$method = $action['name'];
220
221
				$verb = strtoupper($action['verb'] ?? 'GET');
222
				$collectionAction = $action['on-collection'] ?? false;
223
				if (!$collectionAction) {
224
					$url .= '/{id}';
225
				}
226
				if (isset($action['url-postfix'])) {
227
					$url .= '/' . $action['url-postfix'];
228
				}
229
230
				$controller = $resource;
231
232
				$controllerName = $this->buildControllerName($controller);
233
				$actionName = $this->buildActionName($method);
234
235
				$routeName = $routeNamePrefix . $this->appName . '.' . strtolower($resource) . '.' . strtolower($method);
236
237
				$this->router->create($routeName, $url)->method($verb)->action(
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Route\IRouter::create() has been deprecated: 9.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

237
				/** @scrutinizer ignore-deprecated */ $this->router->create($routeName, $url)->method($verb)->action(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
238
					new RouteActionHandler($this->container, $controllerName, $actionName)
239
				);
240
			}
241
		}
242
	}
243
244
	private function buildRootPrefix(array $route, string $routeNamePrefix): string {
245
		$defaultRoot = $this->appName === 'core' ? '' : '/apps/' . $this->appName;
246
		$root = $route['root'] ?? $defaultRoot;
247
248
		if ($routeNamePrefix !== '') {
249
			// In OCS all apps are whitelisted
250
			return $root;
251
		}
252
253
		if (!\in_array($this->appName, $this->rootUrlApps, true)) {
254
			// Only allow root URLS for some apps
255
			return  $defaultRoot;
256
		}
257
258
		return $root;
259
	}
260
261
	/**
262
	 * Based on a given route name the controller name is generated
263
	 * @param string $controller
264
	 * @return string
265
	 */
266
	private function buildControllerName(string $controller): string {
267
		if (!isset($this->controllerNameCache[$controller])) {
268
			$this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller';
269
		}
270
		return $this->controllerNameCache[$controller];
271
	}
272
273
	/**
274
	 * Based on the action part of the route name the controller method name is generated
275
	 * @param string $action
276
	 * @return string
277
	 */
278
	private function buildActionName(string $action): string {
279
		return $this->underScoreToCamelCase($action);
280
	}
281
282
	/**
283
	 * Underscored strings are converted to camel case strings
284
	 * @param string $str
285
	 * @return string
286
	 */
287
	private function underScoreToCamelCase(string $str): string {
288
		$pattern = '/_[a-z]?/';
289
		return preg_replace_callback(
290
			$pattern,
291
			function ($matches) {
292
				return strtoupper(ltrim($matches[0], '_'));
293
			},
294
			$str);
295
	}
296
}
297