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
|
|
|
'spreed', |
64
|
|
|
]; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* @param \OC\AppFramework\DependencyInjection\DIContainer $container |
68
|
|
|
* @param \OCP\Route\IRouter $router |
69
|
|
|
* @param array $routes |
70
|
|
|
* @internal param $appName |
71
|
|
|
*/ |
72
|
|
|
public function __construct(DIContainer $container, IRouter $router, $routes) { |
73
|
|
|
$this->routes = $routes; |
74
|
|
|
$this->container = $container; |
75
|
|
|
$this->router = $router; |
76
|
|
|
$this->appName = $container['AppName']; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* The routes and resource will be registered to the \OCP\Route\IRouter |
81
|
|
|
*/ |
82
|
|
|
public function register() { |
83
|
|
|
|
84
|
|
|
// parse simple |
85
|
|
|
$this->processSimpleRoutes($this->routes); |
86
|
|
|
|
87
|
|
|
// parse resources |
88
|
|
|
$this->processResources($this->routes); |
89
|
|
|
|
90
|
|
|
/* |
91
|
|
|
* OCS routes go into a different collection |
92
|
|
|
*/ |
93
|
|
|
$oldCollection = $this->router->getCurrentCollection(); |
|
|
|
|
94
|
|
|
$this->router->useCollection($oldCollection . '.ocs'); |
|
|
|
|
95
|
|
|
|
96
|
|
|
// parse ocs simple routes |
97
|
|
|
$this->processOCS($this->routes); |
98
|
|
|
|
99
|
|
|
// parse ocs simple routes |
100
|
|
|
$this->processOCSResources($this->routes); |
101
|
|
|
|
102
|
|
|
$this->router->useCollection($oldCollection); |
|
|
|
|
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
private function processOCS(array $routes): void { |
106
|
|
|
$ocsRoutes = $routes['ocs'] ?? []; |
107
|
|
|
foreach ($ocsRoutes as $ocsRoute) { |
108
|
|
|
$this->processRoute($ocsRoute, 'ocs.'); |
109
|
|
|
} |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Creates one route base on the give configuration |
114
|
|
|
* @param array $routes |
115
|
|
|
* @throws \UnexpectedValueException |
116
|
|
|
*/ |
117
|
|
|
private function processSimpleRoutes(array $routes): void { |
118
|
|
|
$simpleRoutes = $routes['routes'] ?? []; |
119
|
|
|
foreach ($simpleRoutes as $simpleRoute) { |
120
|
|
|
$this->processRoute($simpleRoute); |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
protected function processRoute(array $route, string $routeNamePrefix = ''): void { |
125
|
|
|
$name = $route['name']; |
126
|
|
|
$postfix = $route['postfix'] ?? ''; |
127
|
|
|
$defaultRoot = $this->appName === 'core' ? '' : '/apps/' . $this->appName; |
128
|
|
|
$root = $route['root'] ?? $defaultRoot; |
129
|
|
|
if ($routeNamePrefix === '' && !\in_array($this->appName, $this->rootUrlApps, true)) { |
130
|
|
|
// Only allow root URLS for some apps |
131
|
|
|
$root = $defaultRoot; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
$url = $root . $route['url']; |
135
|
|
|
$verb = strtoupper($route['verb'] ?? 'GET'); |
136
|
|
|
|
137
|
|
|
$split = explode('#', $name, 2); |
138
|
|
|
if (count($split) !== 2) { |
139
|
|
|
throw new \UnexpectedValueException('Invalid route name'); |
140
|
|
|
} |
141
|
|
|
list($controller, $action) = $split; |
142
|
|
|
|
143
|
|
|
$controllerName = $this->buildControllerName($controller); |
144
|
|
|
$actionName = $this->buildActionName($action); |
145
|
|
|
|
146
|
|
|
$routeName = $routeNamePrefix . $this->appName . '.' . $controller . '.' . $action . $postfix; |
147
|
|
|
|
148
|
|
|
// register the route |
149
|
|
|
$handler = new RouteActionHandler($this->container, $controllerName, $actionName); |
150
|
|
|
|
151
|
|
|
$router = $this->router->create($routeName, $url) |
|
|
|
|
152
|
|
|
->method($verb) |
153
|
|
|
->action($handler); |
154
|
|
|
|
155
|
|
|
// optionally register requirements for route. This is used to |
156
|
|
|
// tell the route parser how url parameters should be matched |
157
|
|
|
if (array_key_exists('requirements', $route)) { |
158
|
|
|
$router->requirements($route['requirements']); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
// optionally register defaults for route. This is used to |
162
|
|
|
// tell the route parser how url parameters should be default valued |
163
|
|
|
if (array_key_exists('defaults', $route)) { |
164
|
|
|
$router->defaults($route['defaults']); |
165
|
|
|
} |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* For a given name and url restful OCS routes are created: |
170
|
|
|
* - index |
171
|
|
|
* - show |
172
|
|
|
* - create |
173
|
|
|
* - update |
174
|
|
|
* - destroy |
175
|
|
|
* |
176
|
|
|
* @param array $routes |
177
|
|
|
*/ |
178
|
|
|
private function processOCSResources(array $routes): void { |
179
|
|
|
// declaration of all restful actions |
180
|
|
|
$actions = [ |
181
|
|
|
['name' => 'index', 'verb' => 'GET', 'on-collection' => true], |
182
|
|
|
['name' => 'show', 'verb' => 'GET'], |
183
|
|
|
['name' => 'create', 'verb' => 'POST', 'on-collection' => true], |
184
|
|
|
['name' => 'update', 'verb' => 'PUT'], |
185
|
|
|
['name' => 'destroy', 'verb' => 'DELETE'], |
186
|
|
|
]; |
187
|
|
|
|
188
|
|
|
$resources = $routes['ocs-resources'] ?? []; |
189
|
|
|
foreach ($resources as $resource => $config) { |
190
|
|
|
$root = $config['root'] ?? '/apps/' . $this->appName; |
191
|
|
|
|
192
|
|
|
// the url parameter used as id to the resource |
193
|
|
|
foreach ($actions as $action) { |
194
|
|
|
$url = $root . $config['url']; |
195
|
|
|
$method = $action['name']; |
196
|
|
|
$verb = strtoupper($action['verb'] ?? 'GET'); |
197
|
|
|
$collectionAction = $action['on-collection'] ?? false; |
198
|
|
|
if (!$collectionAction) { |
199
|
|
|
$url .= '/{id}'; |
200
|
|
|
} |
201
|
|
|
if (isset($action['url-postfix'])) { |
202
|
|
|
$url .= '/' . $action['url-postfix']; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
$controller = $resource; |
206
|
|
|
|
207
|
|
|
$controllerName = $this->buildControllerName($controller); |
208
|
|
|
$actionName = $this->buildActionName($method); |
209
|
|
|
|
210
|
|
|
$routeName = 'ocs.' . $this->appName . '.' . strtolower($resource) . '.' . strtolower($method); |
211
|
|
|
|
212
|
|
|
$this->router->create($routeName, $url)->method($verb)->action( |
|
|
|
|
213
|
|
|
new RouteActionHandler($this->container, $controllerName, $actionName) |
214
|
|
|
); |
215
|
|
|
} |
216
|
|
|
} |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* For a given name and url restful routes are created: |
221
|
|
|
* - index |
222
|
|
|
* - show |
223
|
|
|
* - create |
224
|
|
|
* - update |
225
|
|
|
* - destroy |
226
|
|
|
* |
227
|
|
|
* @param array $routes |
228
|
|
|
*/ |
229
|
|
|
private function processResources(array $routes): void { |
230
|
|
|
// declaration of all restful actions |
231
|
|
|
$actions = [ |
232
|
|
|
['name' => 'index', 'verb' => 'GET', 'on-collection' => true], |
233
|
|
|
['name' => 'show', 'verb' => 'GET'], |
234
|
|
|
['name' => 'create', 'verb' => 'POST', 'on-collection' => true], |
235
|
|
|
['name' => 'update', 'verb' => 'PUT'], |
236
|
|
|
['name' => 'destroy', 'verb' => 'DELETE'], |
237
|
|
|
]; |
238
|
|
|
|
239
|
|
|
$resources = $routes['resources'] ?? []; |
240
|
|
|
foreach ($resources as $resource => $config) { |
241
|
|
|
|
242
|
|
|
// the url parameter used as id to the resource |
243
|
|
|
foreach ($actions as $action) { |
244
|
|
|
$url = $config['url']; |
245
|
|
|
$method = $action['name']; |
246
|
|
|
$verb = strtoupper($action['verb'] ?? 'GET'); |
247
|
|
|
$collectionAction = $action['on-collection'] ?? false; |
248
|
|
|
if (!$collectionAction) { |
249
|
|
|
$url .= '/{id}'; |
250
|
|
|
} |
251
|
|
|
if (isset($action['url-postfix'])) { |
252
|
|
|
$url .= '/' . $action['url-postfix']; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
$controller = $resource; |
256
|
|
|
|
257
|
|
|
$controllerName = $this->buildControllerName($controller); |
258
|
|
|
$actionName = $this->buildActionName($method); |
259
|
|
|
|
260
|
|
|
$routeName = $this->appName . '.' . strtolower($resource) . '.' . strtolower($method); |
261
|
|
|
|
262
|
|
|
$this->router->create($routeName, $url)->method($verb)->action( |
|
|
|
|
263
|
|
|
new RouteActionHandler($this->container, $controllerName, $actionName) |
264
|
|
|
); |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Based on a given route name the controller name is generated |
271
|
|
|
* @param string $controller |
272
|
|
|
* @return string |
273
|
|
|
*/ |
274
|
|
|
private function buildControllerName(string $controller): string { |
275
|
|
|
if (!isset($this->controllerNameCache[$controller])) { |
276
|
|
|
$this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller'; |
277
|
|
|
} |
278
|
|
|
return $this->controllerNameCache[$controller]; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* Based on the action part of the route name the controller method name is generated |
283
|
|
|
* @param string $action |
284
|
|
|
* @return string |
285
|
|
|
*/ |
286
|
|
|
private function buildActionName(string $action): string { |
287
|
|
|
return $this->underScoreToCamelCase($action); |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Underscored strings are converted to camel case strings |
292
|
|
|
* @param string $str |
293
|
|
|
* @return string |
294
|
|
|
*/ |
295
|
|
|
private function underScoreToCamelCase(string $str): string { |
296
|
|
|
$pattern = '/_[a-z]?/'; |
297
|
|
|
return preg_replace_callback( |
298
|
|
|
$pattern, |
299
|
|
|
function ($matches) { |
300
|
|
|
return strtoupper(ltrim($matches[0], '_')); |
301
|
|
|
}, |
302
|
|
|
$str); |
303
|
|
|
} |
304
|
|
|
} |
305
|
|
|
|
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.