Completed
Push — master ( f057a2...06ebbb )
by Joas
42:01 queued 14s
created
lib/private/Route/Router.php 1 patch
Indentation   +526 added lines, -526 removed lines patch added patch discarded remove patch
@@ -31,535 +31,535 @@
 block discarded – undo
31 31
 use Symfony\Component\Routing\RouteCollection;
32 32
 
33 33
 class Router implements IRouter {
34
-	/** @var RouteCollection[] */
35
-	protected $collections = [];
36
-	/** @var null|RouteCollection */
37
-	protected $collection = null;
38
-	/** @var null|string */
39
-	protected $collectionName = null;
40
-	/** @var null|RouteCollection */
41
-	protected $root = null;
42
-	/** @var null|UrlGenerator */
43
-	protected $generator = null;
44
-	/** @var string[]|null */
45
-	protected $routingFiles;
46
-	/** @var bool */
47
-	protected $loaded = false;
48
-	/** @var array */
49
-	protected $loadedApps = [];
50
-	/** @var RequestContext */
51
-	protected $context;
52
-
53
-	public function __construct(
54
-		protected LoggerInterface $logger,
55
-		IRequest $request,
56
-		protected IConfig $config,
57
-		protected IEventLogger $eventLogger,
58
-		private ContainerInterface $container,
59
-		protected IAppManager $appManager,
60
-	) {
61
-		$baseUrl = \OC::$WEBROOT;
62
-		if (!($config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) {
63
-			$baseUrl .= '/index.php';
64
-		}
65
-		if (!\OC::$CLI && isset($_SERVER['REQUEST_METHOD'])) {
66
-			$method = $_SERVER['REQUEST_METHOD'];
67
-		} else {
68
-			$method = 'GET';
69
-		}
70
-		$host = $request->getServerHost();
71
-		$schema = $request->getServerProtocol();
72
-		$this->context = new RequestContext($baseUrl, $method, $host, $schema);
73
-		// TODO cache
74
-		$this->root = $this->getCollection('root');
75
-	}
76
-
77
-	public function setContext(RequestContext $context): void {
78
-		$this->context = $context;
79
-	}
80
-
81
-	public function getRouteCollection() {
82
-		return $this->root;
83
-	}
84
-
85
-	/**
86
-	 * Get the files to load the routes from
87
-	 *
88
-	 * @return string[]
89
-	 */
90
-	public function getRoutingFiles() {
91
-		if ($this->routingFiles === null) {
92
-			$this->routingFiles = [];
93
-			foreach ($this->appManager->getEnabledApps() as $app) {
94
-				try {
95
-					$appPath = $this->appManager->getAppPath($app);
96
-					$file = $appPath . '/appinfo/routes.php';
97
-					if (file_exists($file)) {
98
-						$this->routingFiles[$app] = $file;
99
-					}
100
-				} catch (AppPathNotFoundException) {
101
-					/* ignore */
102
-				}
103
-			}
104
-		}
105
-		return $this->routingFiles;
106
-	}
107
-
108
-	/**
109
-	 * Loads the routes
110
-	 *
111
-	 * @param null|string $app
112
-	 */
113
-	public function loadRoutes(?string $app = null, bool $skipLoadingCore = false): void {
114
-		if (is_string($app)) {
115
-			$app = $this->appManager->cleanAppId($app);
116
-		}
117
-
118
-		$requestedApp = $app;
119
-		if ($this->loaded) {
120
-			return;
121
-		}
122
-		$this->eventLogger->start('route:load:' . $requestedApp, 'Loading Routes for ' . $requestedApp);
123
-		if (is_null($app)) {
124
-			$this->loaded = true;
125
-			$routingFiles = $this->getRoutingFiles();
126
-
127
-			$this->eventLogger->start('route:load:attributes', 'Loading Routes from attributes');
128
-			foreach ($this->appManager->getEnabledApps() as $enabledApp) {
129
-				$this->loadAttributeRoutes($enabledApp);
130
-			}
131
-			$this->eventLogger->end('route:load:attributes');
132
-		} else {
133
-			if (isset($this->loadedApps[$app])) {
134
-				return;
135
-			}
136
-			try {
137
-				$appPath = $this->appManager->getAppPath($app);
138
-				$file = $appPath . '/appinfo/routes.php';
139
-				if (file_exists($file)) {
140
-					$routingFiles = [$app => $file];
141
-				} else {
142
-					$routingFiles = [];
143
-				}
144
-			} catch (AppPathNotFoundException) {
145
-				$routingFiles = [];
146
-			}
147
-
148
-			if ($this->appManager->isEnabledForUser($app)) {
149
-				$this->loadAttributeRoutes($app);
150
-			}
151
-		}
152
-
153
-		$this->eventLogger->start('route:load:files', 'Loading Routes from files');
154
-		foreach ($routingFiles as $app => $file) {
155
-			if (!isset($this->loadedApps[$app])) {
156
-				if (!$this->appManager->isAppLoaded($app)) {
157
-					// app MUST be loaded before app routes
158
-					// try again next time loadRoutes() is called
159
-					$this->loaded = false;
160
-					continue;
161
-				}
162
-				$this->loadedApps[$app] = true;
163
-				$this->useCollection($app);
164
-				$this->requireRouteFile($file, $app);
165
-				$collection = $this->getCollection($app);
166
-				$this->root->addCollection($collection);
167
-
168
-				// Also add the OCS collection
169
-				$collection = $this->getCollection($app . '.ocs');
170
-				$collection->addPrefix('/ocsapp');
171
-				$this->root->addCollection($collection);
172
-			}
173
-		}
174
-		$this->eventLogger->end('route:load:files');
175
-
176
-		if (!$skipLoadingCore && !isset($this->loadedApps['core'])) {
177
-			$this->loadedApps['core'] = true;
178
-			$this->useCollection('root');
179
-			$this->setupRoutes($this->getAttributeRoutes('core'), 'core');
180
-			$this->requireRouteFile(__DIR__ . '/../../../core/routes.php', 'core');
181
-
182
-			// Also add the OCS collection
183
-			$collection = $this->getCollection('root.ocs');
184
-			$collection->addPrefix('/ocsapp');
185
-			$this->root->addCollection($collection);
186
-		}
187
-		if ($this->loaded) {
188
-			$collection = $this->getCollection('ocs');
189
-			$collection->addPrefix('/ocs');
190
-			$this->root->addCollection($collection);
191
-		}
192
-		$this->eventLogger->end('route:load:' . $requestedApp);
193
-	}
194
-
195
-	/**
196
-	 * @param string $name
197
-	 * @return \Symfony\Component\Routing\RouteCollection
198
-	 */
199
-	protected function getCollection($name) {
200
-		if (!isset($this->collections[$name])) {
201
-			$this->collections[$name] = new RouteCollection();
202
-		}
203
-		return $this->collections[$name];
204
-	}
205
-
206
-	/**
207
-	 * Sets the collection to use for adding routes
208
-	 *
209
-	 * @param string $name Name of the collection to use.
210
-	 * @return void
211
-	 */
212
-	public function useCollection($name) {
213
-		$this->collection = $this->getCollection($name);
214
-		$this->collectionName = $name;
215
-	}
216
-
217
-	/**
218
-	 * returns the current collection name in use for adding routes
219
-	 *
220
-	 * @return string the collection name
221
-	 */
222
-	public function getCurrentCollection() {
223
-		return $this->collectionName;
224
-	}
225
-
226
-
227
-	/**
228
-	 * Create a \OC\Route\Route.
229
-	 *
230
-	 * @param string $name Name of the route to create.
231
-	 * @param string $pattern The pattern to match
232
-	 * @param array $defaults An array of default parameter values
233
-	 * @param array $requirements An array of requirements for parameters (regexes)
234
-	 * @return \OC\Route\Route
235
-	 */
236
-	public function create($name,
237
-		$pattern,
238
-		array $defaults = [],
239
-		array $requirements = []) {
240
-		$route = new Route($pattern, $defaults, $requirements);
241
-		$this->collection->add($name, $route);
242
-		return $route;
243
-	}
244
-
245
-	/**
246
-	 * Find the route matching $url
247
-	 *
248
-	 * @param string $url The url to find
249
-	 * @throws \Exception
250
-	 * @return array
251
-	 */
252
-	public function findMatchingRoute(string $url): array {
253
-		$this->eventLogger->start('route:match', 'Match route');
254
-		if (str_starts_with($url, '/apps/')) {
255
-			// empty string / 'apps' / $app / rest of the route
256
-			[, , $app,] = explode('/', $url, 4);
257
-
258
-			$app = $this->appManager->cleanAppId($app);
259
-			\OC::$REQUESTEDAPP = $app;
260
-			$this->loadRoutes($app);
261
-		} elseif (str_starts_with($url, '/ocsapp/apps/')) {
262
-			// empty string / 'ocsapp' / 'apps' / $app / rest of the route
263
-			[, , , $app,] = explode('/', $url, 5);
264
-
265
-			$app = $this->appManager->cleanAppId($app);
266
-			\OC::$REQUESTEDAPP = $app;
267
-			$this->loadRoutes($app);
268
-		} elseif (str_starts_with($url, '/settings/')) {
269
-			$this->loadRoutes('settings');
270
-		} elseif (str_starts_with($url, '/core/')) {
271
-			\OC::$REQUESTEDAPP = $url;
272
-			if ($this->config->getSystemValueBool('installed', false) && !Util::needUpgrade()) {
273
-				$this->appManager->loadApps();
274
-			}
275
-			$this->loadRoutes('core');
276
-		} else {
277
-			$this->loadRoutes();
278
-		}
279
-
280
-		$this->eventLogger->start('route:url:match', 'Symfony url matcher call');
281
-		$matcher = new UrlMatcher($this->root, $this->context);
282
-		try {
283
-			$parameters = $matcher->match($url);
284
-		} catch (ResourceNotFoundException $e) {
285
-			if (!str_ends_with($url, '/')) {
286
-				// We allow links to apps/files? for backwards compatibility reasons
287
-				// However, since Symfony does not allow empty route names, the route
288
-				// we need to match is '/', so we need to append the '/' here.
289
-				try {
290
-					$parameters = $matcher->match($url . '/');
291
-				} catch (ResourceNotFoundException $newException) {
292
-					// If we still didn't match a route, we throw the original exception
293
-					throw $e;
294
-				}
295
-			} else {
296
-				throw $e;
297
-			}
298
-		}
299
-		$this->eventLogger->end('route:url:match');
300
-
301
-		$this->eventLogger->end('route:match');
302
-		return $parameters;
303
-	}
304
-
305
-	/**
306
-	 * Find and execute the route matching $url
307
-	 *
308
-	 * @param string $url The url to find
309
-	 * @throws \Exception
310
-	 * @return void
311
-	 */
312
-	public function match($url) {
313
-		$parameters = $this->findMatchingRoute($url);
314
-
315
-		$this->eventLogger->start('route:run', 'Run route');
316
-		if (isset($parameters['caller'])) {
317
-			$caller = $parameters['caller'];
318
-			unset($parameters['caller']);
319
-			unset($parameters['action']);
320
-			$application = $this->getApplicationClass($caller[0]);
321
-			\OC\AppFramework\App::main($caller[1], $caller[2], $application->getContainer(), $parameters);
322
-		} elseif (isset($parameters['action'])) {
323
-			$this->logger->warning('Deprecated action route used', ['parameters' => $parameters]);
324
-			$this->callLegacyActionRoute($parameters);
325
-		} elseif (isset($parameters['file'])) {
326
-			$this->logger->debug('Deprecated file route used', ['parameters' => $parameters]);
327
-			$this->includeLegacyFileRoute($parameters);
328
-		} else {
329
-			throw new \Exception('no action available');
330
-		}
331
-		$this->eventLogger->end('route:run');
332
-	}
333
-
334
-	/**
335
-	 * @param array{file:mixed, ...} $parameters
336
-	 */
337
-	protected function includeLegacyFileRoute(array $parameters): void {
338
-		$param = $parameters;
339
-		unset($param['_route']);
340
-		$_GET = array_merge($_GET, $param);
341
-		unset($param);
342
-		require_once $parameters['file'];
343
-	}
344
-
345
-	/**
346
-	 * @param array{action:mixed, ...} $parameters
347
-	 */
348
-	protected function callLegacyActionRoute(array $parameters): void {
349
-		$action = $parameters['action'];
350
-		if (!is_callable($action)) {
351
-			throw new \Exception('not a callable action');
352
-		}
353
-		unset($parameters['action']);
354
-		unset($parameters['caller']);
355
-		$this->eventLogger->start('route:run:call', 'Run callable route');
356
-		call_user_func($action, $parameters);
357
-		$this->eventLogger->end('route:run:call');
358
-	}
359
-
360
-	/**
361
-	 * Get the url generator
362
-	 *
363
-	 * @return \Symfony\Component\Routing\Generator\UrlGenerator
364
-	 *
365
-	 */
366
-	public function getGenerator() {
367
-		if ($this->generator !== null) {
368
-			return $this->generator;
369
-		}
370
-
371
-		return $this->generator = new UrlGenerator($this->root, $this->context);
372
-	}
373
-
374
-	/**
375
-	 * Generate url based on $name and $parameters
376
-	 *
377
-	 * @param string $name Name of the route to use.
378
-	 * @param array $parameters Parameters for the route
379
-	 * @param bool $absolute
380
-	 * @return string
381
-	 */
382
-	public function generate($name,
383
-		$parameters = [],
384
-		$absolute = false) {
385
-		$referenceType = UrlGenerator::ABSOLUTE_URL;
386
-		if ($absolute === false) {
387
-			$referenceType = UrlGenerator::ABSOLUTE_PATH;
388
-		}
389
-		/*
34
+    /** @var RouteCollection[] */
35
+    protected $collections = [];
36
+    /** @var null|RouteCollection */
37
+    protected $collection = null;
38
+    /** @var null|string */
39
+    protected $collectionName = null;
40
+    /** @var null|RouteCollection */
41
+    protected $root = null;
42
+    /** @var null|UrlGenerator */
43
+    protected $generator = null;
44
+    /** @var string[]|null */
45
+    protected $routingFiles;
46
+    /** @var bool */
47
+    protected $loaded = false;
48
+    /** @var array */
49
+    protected $loadedApps = [];
50
+    /** @var RequestContext */
51
+    protected $context;
52
+
53
+    public function __construct(
54
+        protected LoggerInterface $logger,
55
+        IRequest $request,
56
+        protected IConfig $config,
57
+        protected IEventLogger $eventLogger,
58
+        private ContainerInterface $container,
59
+        protected IAppManager $appManager,
60
+    ) {
61
+        $baseUrl = \OC::$WEBROOT;
62
+        if (!($config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) {
63
+            $baseUrl .= '/index.php';
64
+        }
65
+        if (!\OC::$CLI && isset($_SERVER['REQUEST_METHOD'])) {
66
+            $method = $_SERVER['REQUEST_METHOD'];
67
+        } else {
68
+            $method = 'GET';
69
+        }
70
+        $host = $request->getServerHost();
71
+        $schema = $request->getServerProtocol();
72
+        $this->context = new RequestContext($baseUrl, $method, $host, $schema);
73
+        // TODO cache
74
+        $this->root = $this->getCollection('root');
75
+    }
76
+
77
+    public function setContext(RequestContext $context): void {
78
+        $this->context = $context;
79
+    }
80
+
81
+    public function getRouteCollection() {
82
+        return $this->root;
83
+    }
84
+
85
+    /**
86
+     * Get the files to load the routes from
87
+     *
88
+     * @return string[]
89
+     */
90
+    public function getRoutingFiles() {
91
+        if ($this->routingFiles === null) {
92
+            $this->routingFiles = [];
93
+            foreach ($this->appManager->getEnabledApps() as $app) {
94
+                try {
95
+                    $appPath = $this->appManager->getAppPath($app);
96
+                    $file = $appPath . '/appinfo/routes.php';
97
+                    if (file_exists($file)) {
98
+                        $this->routingFiles[$app] = $file;
99
+                    }
100
+                } catch (AppPathNotFoundException) {
101
+                    /* ignore */
102
+                }
103
+            }
104
+        }
105
+        return $this->routingFiles;
106
+    }
107
+
108
+    /**
109
+     * Loads the routes
110
+     *
111
+     * @param null|string $app
112
+     */
113
+    public function loadRoutes(?string $app = null, bool $skipLoadingCore = false): void {
114
+        if (is_string($app)) {
115
+            $app = $this->appManager->cleanAppId($app);
116
+        }
117
+
118
+        $requestedApp = $app;
119
+        if ($this->loaded) {
120
+            return;
121
+        }
122
+        $this->eventLogger->start('route:load:' . $requestedApp, 'Loading Routes for ' . $requestedApp);
123
+        if (is_null($app)) {
124
+            $this->loaded = true;
125
+            $routingFiles = $this->getRoutingFiles();
126
+
127
+            $this->eventLogger->start('route:load:attributes', 'Loading Routes from attributes');
128
+            foreach ($this->appManager->getEnabledApps() as $enabledApp) {
129
+                $this->loadAttributeRoutes($enabledApp);
130
+            }
131
+            $this->eventLogger->end('route:load:attributes');
132
+        } else {
133
+            if (isset($this->loadedApps[$app])) {
134
+                return;
135
+            }
136
+            try {
137
+                $appPath = $this->appManager->getAppPath($app);
138
+                $file = $appPath . '/appinfo/routes.php';
139
+                if (file_exists($file)) {
140
+                    $routingFiles = [$app => $file];
141
+                } else {
142
+                    $routingFiles = [];
143
+                }
144
+            } catch (AppPathNotFoundException) {
145
+                $routingFiles = [];
146
+            }
147
+
148
+            if ($this->appManager->isEnabledForUser($app)) {
149
+                $this->loadAttributeRoutes($app);
150
+            }
151
+        }
152
+
153
+        $this->eventLogger->start('route:load:files', 'Loading Routes from files');
154
+        foreach ($routingFiles as $app => $file) {
155
+            if (!isset($this->loadedApps[$app])) {
156
+                if (!$this->appManager->isAppLoaded($app)) {
157
+                    // app MUST be loaded before app routes
158
+                    // try again next time loadRoutes() is called
159
+                    $this->loaded = false;
160
+                    continue;
161
+                }
162
+                $this->loadedApps[$app] = true;
163
+                $this->useCollection($app);
164
+                $this->requireRouteFile($file, $app);
165
+                $collection = $this->getCollection($app);
166
+                $this->root->addCollection($collection);
167
+
168
+                // Also add the OCS collection
169
+                $collection = $this->getCollection($app . '.ocs');
170
+                $collection->addPrefix('/ocsapp');
171
+                $this->root->addCollection($collection);
172
+            }
173
+        }
174
+        $this->eventLogger->end('route:load:files');
175
+
176
+        if (!$skipLoadingCore && !isset($this->loadedApps['core'])) {
177
+            $this->loadedApps['core'] = true;
178
+            $this->useCollection('root');
179
+            $this->setupRoutes($this->getAttributeRoutes('core'), 'core');
180
+            $this->requireRouteFile(__DIR__ . '/../../../core/routes.php', 'core');
181
+
182
+            // Also add the OCS collection
183
+            $collection = $this->getCollection('root.ocs');
184
+            $collection->addPrefix('/ocsapp');
185
+            $this->root->addCollection($collection);
186
+        }
187
+        if ($this->loaded) {
188
+            $collection = $this->getCollection('ocs');
189
+            $collection->addPrefix('/ocs');
190
+            $this->root->addCollection($collection);
191
+        }
192
+        $this->eventLogger->end('route:load:' . $requestedApp);
193
+    }
194
+
195
+    /**
196
+     * @param string $name
197
+     * @return \Symfony\Component\Routing\RouteCollection
198
+     */
199
+    protected function getCollection($name) {
200
+        if (!isset($this->collections[$name])) {
201
+            $this->collections[$name] = new RouteCollection();
202
+        }
203
+        return $this->collections[$name];
204
+    }
205
+
206
+    /**
207
+     * Sets the collection to use for adding routes
208
+     *
209
+     * @param string $name Name of the collection to use.
210
+     * @return void
211
+     */
212
+    public function useCollection($name) {
213
+        $this->collection = $this->getCollection($name);
214
+        $this->collectionName = $name;
215
+    }
216
+
217
+    /**
218
+     * returns the current collection name in use for adding routes
219
+     *
220
+     * @return string the collection name
221
+     */
222
+    public function getCurrentCollection() {
223
+        return $this->collectionName;
224
+    }
225
+
226
+
227
+    /**
228
+     * Create a \OC\Route\Route.
229
+     *
230
+     * @param string $name Name of the route to create.
231
+     * @param string $pattern The pattern to match
232
+     * @param array $defaults An array of default parameter values
233
+     * @param array $requirements An array of requirements for parameters (regexes)
234
+     * @return \OC\Route\Route
235
+     */
236
+    public function create($name,
237
+        $pattern,
238
+        array $defaults = [],
239
+        array $requirements = []) {
240
+        $route = new Route($pattern, $defaults, $requirements);
241
+        $this->collection->add($name, $route);
242
+        return $route;
243
+    }
244
+
245
+    /**
246
+     * Find the route matching $url
247
+     *
248
+     * @param string $url The url to find
249
+     * @throws \Exception
250
+     * @return array
251
+     */
252
+    public function findMatchingRoute(string $url): array {
253
+        $this->eventLogger->start('route:match', 'Match route');
254
+        if (str_starts_with($url, '/apps/')) {
255
+            // empty string / 'apps' / $app / rest of the route
256
+            [, , $app,] = explode('/', $url, 4);
257
+
258
+            $app = $this->appManager->cleanAppId($app);
259
+            \OC::$REQUESTEDAPP = $app;
260
+            $this->loadRoutes($app);
261
+        } elseif (str_starts_with($url, '/ocsapp/apps/')) {
262
+            // empty string / 'ocsapp' / 'apps' / $app / rest of the route
263
+            [, , , $app,] = explode('/', $url, 5);
264
+
265
+            $app = $this->appManager->cleanAppId($app);
266
+            \OC::$REQUESTEDAPP = $app;
267
+            $this->loadRoutes($app);
268
+        } elseif (str_starts_with($url, '/settings/')) {
269
+            $this->loadRoutes('settings');
270
+        } elseif (str_starts_with($url, '/core/')) {
271
+            \OC::$REQUESTEDAPP = $url;
272
+            if ($this->config->getSystemValueBool('installed', false) && !Util::needUpgrade()) {
273
+                $this->appManager->loadApps();
274
+            }
275
+            $this->loadRoutes('core');
276
+        } else {
277
+            $this->loadRoutes();
278
+        }
279
+
280
+        $this->eventLogger->start('route:url:match', 'Symfony url matcher call');
281
+        $matcher = new UrlMatcher($this->root, $this->context);
282
+        try {
283
+            $parameters = $matcher->match($url);
284
+        } catch (ResourceNotFoundException $e) {
285
+            if (!str_ends_with($url, '/')) {
286
+                // We allow links to apps/files? for backwards compatibility reasons
287
+                // However, since Symfony does not allow empty route names, the route
288
+                // we need to match is '/', so we need to append the '/' here.
289
+                try {
290
+                    $parameters = $matcher->match($url . '/');
291
+                } catch (ResourceNotFoundException $newException) {
292
+                    // If we still didn't match a route, we throw the original exception
293
+                    throw $e;
294
+                }
295
+            } else {
296
+                throw $e;
297
+            }
298
+        }
299
+        $this->eventLogger->end('route:url:match');
300
+
301
+        $this->eventLogger->end('route:match');
302
+        return $parameters;
303
+    }
304
+
305
+    /**
306
+     * Find and execute the route matching $url
307
+     *
308
+     * @param string $url The url to find
309
+     * @throws \Exception
310
+     * @return void
311
+     */
312
+    public function match($url) {
313
+        $parameters = $this->findMatchingRoute($url);
314
+
315
+        $this->eventLogger->start('route:run', 'Run route');
316
+        if (isset($parameters['caller'])) {
317
+            $caller = $parameters['caller'];
318
+            unset($parameters['caller']);
319
+            unset($parameters['action']);
320
+            $application = $this->getApplicationClass($caller[0]);
321
+            \OC\AppFramework\App::main($caller[1], $caller[2], $application->getContainer(), $parameters);
322
+        } elseif (isset($parameters['action'])) {
323
+            $this->logger->warning('Deprecated action route used', ['parameters' => $parameters]);
324
+            $this->callLegacyActionRoute($parameters);
325
+        } elseif (isset($parameters['file'])) {
326
+            $this->logger->debug('Deprecated file route used', ['parameters' => $parameters]);
327
+            $this->includeLegacyFileRoute($parameters);
328
+        } else {
329
+            throw new \Exception('no action available');
330
+        }
331
+        $this->eventLogger->end('route:run');
332
+    }
333
+
334
+    /**
335
+     * @param array{file:mixed, ...} $parameters
336
+     */
337
+    protected function includeLegacyFileRoute(array $parameters): void {
338
+        $param = $parameters;
339
+        unset($param['_route']);
340
+        $_GET = array_merge($_GET, $param);
341
+        unset($param);
342
+        require_once $parameters['file'];
343
+    }
344
+
345
+    /**
346
+     * @param array{action:mixed, ...} $parameters
347
+     */
348
+    protected function callLegacyActionRoute(array $parameters): void {
349
+        $action = $parameters['action'];
350
+        if (!is_callable($action)) {
351
+            throw new \Exception('not a callable action');
352
+        }
353
+        unset($parameters['action']);
354
+        unset($parameters['caller']);
355
+        $this->eventLogger->start('route:run:call', 'Run callable route');
356
+        call_user_func($action, $parameters);
357
+        $this->eventLogger->end('route:run:call');
358
+    }
359
+
360
+    /**
361
+     * Get the url generator
362
+     *
363
+     * @return \Symfony\Component\Routing\Generator\UrlGenerator
364
+     *
365
+     */
366
+    public function getGenerator() {
367
+        if ($this->generator !== null) {
368
+            return $this->generator;
369
+        }
370
+
371
+        return $this->generator = new UrlGenerator($this->root, $this->context);
372
+    }
373
+
374
+    /**
375
+     * Generate url based on $name and $parameters
376
+     *
377
+     * @param string $name Name of the route to use.
378
+     * @param array $parameters Parameters for the route
379
+     * @param bool $absolute
380
+     * @return string
381
+     */
382
+    public function generate($name,
383
+        $parameters = [],
384
+        $absolute = false) {
385
+        $referenceType = UrlGenerator::ABSOLUTE_URL;
386
+        if ($absolute === false) {
387
+            $referenceType = UrlGenerator::ABSOLUTE_PATH;
388
+        }
389
+        /*
390 390
 		 * The route name has to be lowercase, for symfony to match it correctly.
391 391
 		 * This is required because smyfony allows mixed casing for controller names in the routes.
392 392
 		 * To avoid breaking all the existing route names, registering and matching will only use the lowercase names.
393 393
 		 * This is also safe on the PHP side because class and method names collide regardless of the casing.
394 394
 		 */
395
-		$name = strtolower($name);
396
-		$name = $this->fixLegacyRootName($name);
397
-		if (str_contains($name, '.')) {
398
-			[$appName, $other] = explode('.', $name, 3);
399
-			// OCS routes are prefixed with "ocs."
400
-			if ($appName === 'ocs') {
401
-				$appName = $other;
402
-			}
403
-			$this->loadRoutes($appName);
404
-			try {
405
-				return $this->getGenerator()->generate($name, $parameters, $referenceType);
406
-			} catch (RouteNotFoundException $e) {
407
-			}
408
-		}
409
-
410
-		// Fallback load all routes
411
-		$this->loadRoutes();
412
-		try {
413
-			return $this->getGenerator()->generate($name, $parameters, $referenceType);
414
-		} catch (RouteNotFoundException $e) {
415
-			$this->logger->info($e->getMessage(), ['exception' => $e]);
416
-			return '';
417
-		}
418
-	}
419
-
420
-	protected function fixLegacyRootName(string $routeName): string {
421
-		if ($routeName === 'files.viewcontroller.showfile') {
422
-			return 'files.view.showfile';
423
-		}
424
-		if ($routeName === 'files_sharing.sharecontroller.showshare') {
425
-			return 'files_sharing.share.showshare';
426
-		}
427
-		if ($routeName === 'files_sharing.sharecontroller.showauthenticate') {
428
-			return 'files_sharing.share.showauthenticate';
429
-		}
430
-		if ($routeName === 'files_sharing.sharecontroller.authenticate') {
431
-			return 'files_sharing.share.authenticate';
432
-		}
433
-		if ($routeName === 'files_sharing.sharecontroller.downloadshare') {
434
-			return 'files_sharing.share.downloadshare';
435
-		}
436
-		if ($routeName === 'files_sharing.publicpreview.directlink') {
437
-			return 'files_sharing.publicpreview.directlink';
438
-		}
439
-		if ($routeName === 'cloud_federation_api.requesthandlercontroller.addshare') {
440
-			return 'cloud_federation_api.requesthandler.addshare';
441
-		}
442
-		if ($routeName === 'cloud_federation_api.requesthandlercontroller.receivenotification') {
443
-			return 'cloud_federation_api.requesthandler.receivenotification';
444
-		}
445
-		if ($routeName === 'core.ProfilePage.index') {
446
-			return 'profile.ProfilePage.index';
447
-		}
448
-		return $routeName;
449
-	}
450
-
451
-	private function loadAttributeRoutes(string $app): void {
452
-		$routes = $this->getAttributeRoutes($app);
453
-		if (count($routes) === 0) {
454
-			return;
455
-		}
456
-
457
-		$this->useCollection($app);
458
-		$this->setupRoutes($routes, $app);
459
-		$collection = $this->getCollection($app);
460
-		$this->root->addCollection($collection);
461
-
462
-		// Also add the OCS collection
463
-		$collection = $this->getCollection($app . '.ocs');
464
-		$collection->addPrefix('/ocsapp');
465
-		$this->root->addCollection($collection);
466
-	}
467
-
468
-	/**
469
-	 * @throws ReflectionException
470
-	 */
471
-	private function getAttributeRoutes(string $app): array {
472
-		$routes = [];
473
-
474
-		if ($app === 'core') {
475
-			$appControllerPath = __DIR__ . '/../../../core/Controller';
476
-			$appNameSpace = 'OC\\Core';
477
-		} else {
478
-			try {
479
-				$appControllerPath = $this->appManager->getAppPath($app) . '/lib/Controller';
480
-			} catch (AppPathNotFoundException) {
481
-				return [];
482
-			}
483
-			$appNameSpace = App::buildAppNamespace($app);
484
-		}
485
-
486
-		if (!file_exists($appControllerPath)) {
487
-			return [];
488
-		}
489
-
490
-		$dir = new DirectoryIterator($appControllerPath);
491
-		foreach ($dir as $file) {
492
-			if (!str_ends_with($file->getPathname(), 'Controller.php')) {
493
-				continue;
494
-			}
495
-
496
-			$class = new ReflectionClass($appNameSpace . '\\Controller\\' . basename($file->getPathname(), '.php'));
497
-
498
-			foreach ($class->getMethods() as $method) {
499
-				foreach ($method->getAttributes(RouteAttribute::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
500
-					$route = $attribute->newInstance();
501
-
502
-					$serializedRoute = $route->toArray();
503
-					// Remove 'Controller' suffix
504
-					$serializedRoute['name'] = substr($class->getShortName(), 0, -10) . '#' . $method->getName();
505
-
506
-					$key = $route->getType();
507
-
508
-					$routes[$key] ??= [];
509
-					$routes[$key][] = $serializedRoute;
510
-				}
511
-			}
512
-		}
513
-
514
-		return $routes;
515
-	}
516
-
517
-	/**
518
-	 * To isolate the variable scope used inside the $file it is required in it's own method
519
-	 *
520
-	 * @param string $file the route file location to include
521
-	 * @param string $appName
522
-	 */
523
-	protected function requireRouteFile(string $file, string $appName): void {
524
-		$this->setupRoutes(include $file, $appName);
525
-	}
526
-
527
-
528
-	/**
529
-	 * If a routes.php file returns an array, try to set up the application and
530
-	 * register the routes for the app. The application class will be chosen by
531
-	 * camelcasing the appname, e.g.: my_app will be turned into
532
-	 * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default
533
-	 * App will be initialized. This makes it optional to ship an
534
-	 * appinfo/application.php by using the built in query resolver
535
-	 *
536
-	 * @param array $routes the application routes
537
-	 * @param string $appName the name of the app.
538
-	 */
539
-	private function setupRoutes($routes, $appName) {
540
-		if (is_array($routes)) {
541
-			$routeParser = new RouteParser();
542
-
543
-			$defaultRoutes = $routeParser->parseDefaultRoutes($routes, $appName);
544
-			$ocsRoutes = $routeParser->parseOCSRoutes($routes, $appName);
545
-
546
-			$this->root->addCollection($defaultRoutes);
547
-			$ocsRoutes->addPrefix('/ocsapp');
548
-			$this->root->addCollection($ocsRoutes);
549
-		}
550
-	}
551
-
552
-	private function getApplicationClass(string $appName) {
553
-		$appNameSpace = App::buildAppNamespace($appName);
554
-
555
-		$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
556
-
557
-		if (class_exists($applicationClassName)) {
558
-			$application = $this->container->get($applicationClassName);
559
-		} else {
560
-			$application = new App($appName);
561
-		}
562
-
563
-		return $application;
564
-	}
395
+        $name = strtolower($name);
396
+        $name = $this->fixLegacyRootName($name);
397
+        if (str_contains($name, '.')) {
398
+            [$appName, $other] = explode('.', $name, 3);
399
+            // OCS routes are prefixed with "ocs."
400
+            if ($appName === 'ocs') {
401
+                $appName = $other;
402
+            }
403
+            $this->loadRoutes($appName);
404
+            try {
405
+                return $this->getGenerator()->generate($name, $parameters, $referenceType);
406
+            } catch (RouteNotFoundException $e) {
407
+            }
408
+        }
409
+
410
+        // Fallback load all routes
411
+        $this->loadRoutes();
412
+        try {
413
+            return $this->getGenerator()->generate($name, $parameters, $referenceType);
414
+        } catch (RouteNotFoundException $e) {
415
+            $this->logger->info($e->getMessage(), ['exception' => $e]);
416
+            return '';
417
+        }
418
+    }
419
+
420
+    protected function fixLegacyRootName(string $routeName): string {
421
+        if ($routeName === 'files.viewcontroller.showfile') {
422
+            return 'files.view.showfile';
423
+        }
424
+        if ($routeName === 'files_sharing.sharecontroller.showshare') {
425
+            return 'files_sharing.share.showshare';
426
+        }
427
+        if ($routeName === 'files_sharing.sharecontroller.showauthenticate') {
428
+            return 'files_sharing.share.showauthenticate';
429
+        }
430
+        if ($routeName === 'files_sharing.sharecontroller.authenticate') {
431
+            return 'files_sharing.share.authenticate';
432
+        }
433
+        if ($routeName === 'files_sharing.sharecontroller.downloadshare') {
434
+            return 'files_sharing.share.downloadshare';
435
+        }
436
+        if ($routeName === 'files_sharing.publicpreview.directlink') {
437
+            return 'files_sharing.publicpreview.directlink';
438
+        }
439
+        if ($routeName === 'cloud_federation_api.requesthandlercontroller.addshare') {
440
+            return 'cloud_federation_api.requesthandler.addshare';
441
+        }
442
+        if ($routeName === 'cloud_federation_api.requesthandlercontroller.receivenotification') {
443
+            return 'cloud_federation_api.requesthandler.receivenotification';
444
+        }
445
+        if ($routeName === 'core.ProfilePage.index') {
446
+            return 'profile.ProfilePage.index';
447
+        }
448
+        return $routeName;
449
+    }
450
+
451
+    private function loadAttributeRoutes(string $app): void {
452
+        $routes = $this->getAttributeRoutes($app);
453
+        if (count($routes) === 0) {
454
+            return;
455
+        }
456
+
457
+        $this->useCollection($app);
458
+        $this->setupRoutes($routes, $app);
459
+        $collection = $this->getCollection($app);
460
+        $this->root->addCollection($collection);
461
+
462
+        // Also add the OCS collection
463
+        $collection = $this->getCollection($app . '.ocs');
464
+        $collection->addPrefix('/ocsapp');
465
+        $this->root->addCollection($collection);
466
+    }
467
+
468
+    /**
469
+     * @throws ReflectionException
470
+     */
471
+    private function getAttributeRoutes(string $app): array {
472
+        $routes = [];
473
+
474
+        if ($app === 'core') {
475
+            $appControllerPath = __DIR__ . '/../../../core/Controller';
476
+            $appNameSpace = 'OC\\Core';
477
+        } else {
478
+            try {
479
+                $appControllerPath = $this->appManager->getAppPath($app) . '/lib/Controller';
480
+            } catch (AppPathNotFoundException) {
481
+                return [];
482
+            }
483
+            $appNameSpace = App::buildAppNamespace($app);
484
+        }
485
+
486
+        if (!file_exists($appControllerPath)) {
487
+            return [];
488
+        }
489
+
490
+        $dir = new DirectoryIterator($appControllerPath);
491
+        foreach ($dir as $file) {
492
+            if (!str_ends_with($file->getPathname(), 'Controller.php')) {
493
+                continue;
494
+            }
495
+
496
+            $class = new ReflectionClass($appNameSpace . '\\Controller\\' . basename($file->getPathname(), '.php'));
497
+
498
+            foreach ($class->getMethods() as $method) {
499
+                foreach ($method->getAttributes(RouteAttribute::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
500
+                    $route = $attribute->newInstance();
501
+
502
+                    $serializedRoute = $route->toArray();
503
+                    // Remove 'Controller' suffix
504
+                    $serializedRoute['name'] = substr($class->getShortName(), 0, -10) . '#' . $method->getName();
505
+
506
+                    $key = $route->getType();
507
+
508
+                    $routes[$key] ??= [];
509
+                    $routes[$key][] = $serializedRoute;
510
+                }
511
+            }
512
+        }
513
+
514
+        return $routes;
515
+    }
516
+
517
+    /**
518
+     * To isolate the variable scope used inside the $file it is required in it's own method
519
+     *
520
+     * @param string $file the route file location to include
521
+     * @param string $appName
522
+     */
523
+    protected function requireRouteFile(string $file, string $appName): void {
524
+        $this->setupRoutes(include $file, $appName);
525
+    }
526
+
527
+
528
+    /**
529
+     * If a routes.php file returns an array, try to set up the application and
530
+     * register the routes for the app. The application class will be chosen by
531
+     * camelcasing the appname, e.g.: my_app will be turned into
532
+     * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default
533
+     * App will be initialized. This makes it optional to ship an
534
+     * appinfo/application.php by using the built in query resolver
535
+     *
536
+     * @param array $routes the application routes
537
+     * @param string $appName the name of the app.
538
+     */
539
+    private function setupRoutes($routes, $appName) {
540
+        if (is_array($routes)) {
541
+            $routeParser = new RouteParser();
542
+
543
+            $defaultRoutes = $routeParser->parseDefaultRoutes($routes, $appName);
544
+            $ocsRoutes = $routeParser->parseOCSRoutes($routes, $appName);
545
+
546
+            $this->root->addCollection($defaultRoutes);
547
+            $ocsRoutes->addPrefix('/ocsapp');
548
+            $this->root->addCollection($ocsRoutes);
549
+        }
550
+    }
551
+
552
+    private function getApplicationClass(string $appName) {
553
+        $appNameSpace = App::buildAppNamespace($appName);
554
+
555
+        $applicationClassName = $appNameSpace . '\\AppInfo\\Application';
556
+
557
+        if (class_exists($applicationClassName)) {
558
+            $application = $this->container->get($applicationClassName);
559
+        } else {
560
+            $application = new App($appName);
561
+        }
562
+
563
+        return $application;
564
+    }
565 565
 }
Please login to merge, or discard this patch.
core/Command/Router/MatchRoute.php 1 patch
Indentation   +67 added lines, -67 removed lines patch added patch discarded remove patch
@@ -23,78 +23,78 @@
 block discarded – undo
23 23
 
24 24
 class MatchRoute extends Base {
25 25
 
26
-	public function __construct(
27
-		private Router $router,
28
-	) {
29
-		parent::__construct();
30
-	}
26
+    public function __construct(
27
+        private Router $router,
28
+    ) {
29
+        parent::__construct();
30
+    }
31 31
 
32
-	protected function configure(): void {
33
-		parent::configure();
34
-		$this
35
-			->setName('router:match')
36
-			->setDescription('Match a URL to the target route')
37
-			->addArgument(
38
-				'path',
39
-				InputArgument::REQUIRED,
40
-				'Path of the request',
41
-			)
42
-			->addOption(
43
-				'method',
44
-				null,
45
-				InputOption::VALUE_REQUIRED,
46
-				'HTTP method',
47
-				'GET',
48
-			)
49
-		;
50
-	}
32
+    protected function configure(): void {
33
+        parent::configure();
34
+        $this
35
+            ->setName('router:match')
36
+            ->setDescription('Match a URL to the target route')
37
+            ->addArgument(
38
+                'path',
39
+                InputArgument::REQUIRED,
40
+                'Path of the request',
41
+            )
42
+            ->addOption(
43
+                'method',
44
+                null,
45
+                InputOption::VALUE_REQUIRED,
46
+                'HTTP method',
47
+                'GET',
48
+            )
49
+        ;
50
+    }
51 51
 
52
-	protected function execute(InputInterface $input, OutputInterface $output): int {
53
-		$context = new RequestContext(method: strtoupper($input->getOption('method')));
54
-		$this->router->setContext($context);
52
+    protected function execute(InputInterface $input, OutputInterface $output): int {
53
+        $context = new RequestContext(method: strtoupper($input->getOption('method')));
54
+        $this->router->setContext($context);
55 55
 
56
-		$path = $input->getArgument('path');
57
-		if (str_starts_with($path, '/index.php/')) {
58
-			$path = substr($path, 10);
59
-		}
60
-		if (str_starts_with($path, '/ocs/v1.php/') || str_starts_with($path, '/ocs/v2.php/')) {
61
-			$path = '/ocsapp' . substr($path, strlen('/ocs/v2.php'));
62
-		}
56
+        $path = $input->getArgument('path');
57
+        if (str_starts_with($path, '/index.php/')) {
58
+            $path = substr($path, 10);
59
+        }
60
+        if (str_starts_with($path, '/ocs/v1.php/') || str_starts_with($path, '/ocs/v2.php/')) {
61
+            $path = '/ocsapp' . substr($path, strlen('/ocs/v2.php'));
62
+        }
63 63
 
64
-		try {
65
-			$route = $this->router->findMatchingRoute($path);
66
-		} catch (MethodNotAllowedException) {
67
-			$output->writeln('<error>Method not allowed on this path</error>');
68
-			return self::FAILURE;
69
-		} catch (ResourceNotFoundException) {
70
-			$output->writeln('<error>Path not matched</error>');
71
-			if (preg_match('/\/apps\/([^\/]+)\//', $path, $matches)) {
72
-				$appManager = Server::get(IAppManager::class);
73
-				if (!$appManager->isEnabledForAnyone($matches[1])) {
74
-					$output->writeln('');
75
-					$output->writeln('<comment>App ' . $matches[1] . ' is not enabled</comment>');
76
-				}
77
-			}
78
-			return self::FAILURE;
79
-		}
64
+        try {
65
+            $route = $this->router->findMatchingRoute($path);
66
+        } catch (MethodNotAllowedException) {
67
+            $output->writeln('<error>Method not allowed on this path</error>');
68
+            return self::FAILURE;
69
+        } catch (ResourceNotFoundException) {
70
+            $output->writeln('<error>Path not matched</error>');
71
+            if (preg_match('/\/apps\/([^\/]+)\//', $path, $matches)) {
72
+                $appManager = Server::get(IAppManager::class);
73
+                if (!$appManager->isEnabledForAnyone($matches[1])) {
74
+                    $output->writeln('');
75
+                    $output->writeln('<comment>App ' . $matches[1] . ' is not enabled</comment>');
76
+                }
77
+            }
78
+            return self::FAILURE;
79
+        }
80 80
 
81
-		$row = [
82
-			'route' => $route['_route'],
83
-			'appid' => $route['caller'][0] ?? null,
84
-			'controller' => $route['caller'][1] ?? null,
85
-			'method' => $route['caller'][2] ?? null,
86
-		];
81
+        $row = [
82
+            'route' => $route['_route'],
83
+            'appid' => $route['caller'][0] ?? null,
84
+            'controller' => $route['caller'][1] ?? null,
85
+            'method' => $route['caller'][2] ?? null,
86
+        ];
87 87
 
88
-		if ($output->isVerbose()) {
89
-			$route = $this->router->getRouteCollection()->get($row['route']);
90
-			$row['path'] = $route->getPath();
91
-			if (str_starts_with($row['path'], '/ocsapp/')) {
92
-				$row['path'] = '/ocs/v2.php/' . substr($row['path'], strlen('/ocsapp/'));
93
-			}
94
-			$row['requirements'] = json_encode($route->getRequirements());
95
-		}
88
+        if ($output->isVerbose()) {
89
+            $route = $this->router->getRouteCollection()->get($row['route']);
90
+            $row['path'] = $route->getPath();
91
+            if (str_starts_with($row['path'], '/ocsapp/')) {
92
+                $row['path'] = '/ocs/v2.php/' . substr($row['path'], strlen('/ocsapp/'));
93
+            }
94
+            $row['requirements'] = json_encode($route->getRequirements());
95
+        }
96 96
 
97
-		$this->writeTableInOutputFormat($input, $output, [$row]);
98
-		return self::SUCCESS;
99
-	}
97
+        $this->writeTableInOutputFormat($input, $output, [$row]);
98
+        return self::SUCCESS;
99
+    }
100 100
 }
Please login to merge, or discard this patch.
core/Command/Router/ListRoutes.php 1 patch
Indentation   +106 added lines, -106 removed lines patch added patch discarded remove patch
@@ -20,110 +20,110 @@
 block discarded – undo
20 20
 
21 21
 class ListRoutes extends Base {
22 22
 
23
-	public function __construct(
24
-		protected IAppManager $appManager,
25
-		protected Router $router,
26
-	) {
27
-		parent::__construct();
28
-	}
29
-
30
-	protected function configure(): void {
31
-		parent::configure();
32
-		$this
33
-			->setName('router:list')
34
-			->setDescription('Find the target of a route or all routes of an app')
35
-			->addArgument(
36
-				'app',
37
-				InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
38
-				'Only list routes of these apps',
39
-			)
40
-			->addOption(
41
-				'ocs',
42
-				null,
43
-				InputOption::VALUE_NONE,
44
-				'Only list OCS routes',
45
-			)
46
-			->addOption(
47
-				'index',
48
-				null,
49
-				InputOption::VALUE_NONE,
50
-				'Only list index.php routes',
51
-			)
52
-		;
53
-	}
54
-
55
-	protected function execute(InputInterface $input, OutputInterface $output): int {
56
-		$apps = $input->getArgument('app');
57
-		if (empty($apps)) {
58
-			$this->router->loadRoutes();
59
-		} else {
60
-			foreach ($apps as $app) {
61
-				if ($app === 'core') {
62
-					$this->router->loadRoutes($app, false);
63
-					continue;
64
-				}
65
-
66
-				try {
67
-					$this->appManager->getAppPath($app);
68
-				} catch (AppPathNotFoundException $e) {
69
-					$output->writeln('<comment>App ' . $app . ' not found</comment>');
70
-					return self::FAILURE;
71
-				}
72
-
73
-				if (!$this->appManager->isEnabledForAnyone($app)) {
74
-					$output->writeln('<comment>App ' . $app . ' is not enabled</comment>');
75
-					return self::FAILURE;
76
-				}
77
-
78
-				$this->router->loadRoutes($app, true);
79
-			}
80
-		}
81
-
82
-		$ocsOnly = $input->getOption('ocs');
83
-		$indexOnly = $input->getOption('index');
84
-
85
-		$rows = [];
86
-		$collection = $this->router->getRouteCollection();
87
-		foreach ($collection->all() as $routeName => $route) {
88
-			if (str_starts_with($routeName, 'ocs.')) {
89
-				if ($indexOnly) {
90
-					continue;
91
-				}
92
-				$routeName = substr($routeName, 4);
93
-			} elseif ($ocsOnly) {
94
-				continue;
95
-			}
96
-
97
-			$path = $route->getPath();
98
-			if (str_starts_with($path, '/ocsapp/')) {
99
-				$path = '/ocs/v2.php/' . substr($path, strlen('/ocsapp/'));
100
-			}
101
-			$row = [
102
-				'route' => $routeName,
103
-				'request' => implode(', ', $route->getMethods()),
104
-				'path' => $path,
105
-			];
106
-
107
-			if ($output->isVerbose()) {
108
-				$row['requirements'] = json_encode($route->getRequirements());
109
-			}
110
-
111
-			$rows[] = $row;
112
-		}
113
-
114
-		usort($rows, static function (array $a, array $b): int {
115
-			$aRoute = $a['route'];
116
-			if (str_starts_with($aRoute, 'ocs.')) {
117
-				$aRoute = substr($aRoute, 4);
118
-			}
119
-			$bRoute = $b['route'];
120
-			if (str_starts_with($bRoute, 'ocs.')) {
121
-				$bRoute = substr($bRoute, 4);
122
-			}
123
-			return $aRoute <=> $bRoute;
124
-		});
125
-
126
-		$this->writeTableInOutputFormat($input, $output, $rows);
127
-		return self::SUCCESS;
128
-	}
23
+    public function __construct(
24
+        protected IAppManager $appManager,
25
+        protected Router $router,
26
+    ) {
27
+        parent::__construct();
28
+    }
29
+
30
+    protected function configure(): void {
31
+        parent::configure();
32
+        $this
33
+            ->setName('router:list')
34
+            ->setDescription('Find the target of a route or all routes of an app')
35
+            ->addArgument(
36
+                'app',
37
+                InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
38
+                'Only list routes of these apps',
39
+            )
40
+            ->addOption(
41
+                'ocs',
42
+                null,
43
+                InputOption::VALUE_NONE,
44
+                'Only list OCS routes',
45
+            )
46
+            ->addOption(
47
+                'index',
48
+                null,
49
+                InputOption::VALUE_NONE,
50
+                'Only list index.php routes',
51
+            )
52
+        ;
53
+    }
54
+
55
+    protected function execute(InputInterface $input, OutputInterface $output): int {
56
+        $apps = $input->getArgument('app');
57
+        if (empty($apps)) {
58
+            $this->router->loadRoutes();
59
+        } else {
60
+            foreach ($apps as $app) {
61
+                if ($app === 'core') {
62
+                    $this->router->loadRoutes($app, false);
63
+                    continue;
64
+                }
65
+
66
+                try {
67
+                    $this->appManager->getAppPath($app);
68
+                } catch (AppPathNotFoundException $e) {
69
+                    $output->writeln('<comment>App ' . $app . ' not found</comment>');
70
+                    return self::FAILURE;
71
+                }
72
+
73
+                if (!$this->appManager->isEnabledForAnyone($app)) {
74
+                    $output->writeln('<comment>App ' . $app . ' is not enabled</comment>');
75
+                    return self::FAILURE;
76
+                }
77
+
78
+                $this->router->loadRoutes($app, true);
79
+            }
80
+        }
81
+
82
+        $ocsOnly = $input->getOption('ocs');
83
+        $indexOnly = $input->getOption('index');
84
+
85
+        $rows = [];
86
+        $collection = $this->router->getRouteCollection();
87
+        foreach ($collection->all() as $routeName => $route) {
88
+            if (str_starts_with($routeName, 'ocs.')) {
89
+                if ($indexOnly) {
90
+                    continue;
91
+                }
92
+                $routeName = substr($routeName, 4);
93
+            } elseif ($ocsOnly) {
94
+                continue;
95
+            }
96
+
97
+            $path = $route->getPath();
98
+            if (str_starts_with($path, '/ocsapp/')) {
99
+                $path = '/ocs/v2.php/' . substr($path, strlen('/ocsapp/'));
100
+            }
101
+            $row = [
102
+                'route' => $routeName,
103
+                'request' => implode(', ', $route->getMethods()),
104
+                'path' => $path,
105
+            ];
106
+
107
+            if ($output->isVerbose()) {
108
+                $row['requirements'] = json_encode($route->getRequirements());
109
+            }
110
+
111
+            $rows[] = $row;
112
+        }
113
+
114
+        usort($rows, static function (array $a, array $b): int {
115
+            $aRoute = $a['route'];
116
+            if (str_starts_with($aRoute, 'ocs.')) {
117
+                $aRoute = substr($aRoute, 4);
118
+            }
119
+            $bRoute = $b['route'];
120
+            if (str_starts_with($bRoute, 'ocs.')) {
121
+                $bRoute = substr($bRoute, 4);
122
+            }
123
+            return $aRoute <=> $bRoute;
124
+        });
125
+
126
+        $this->writeTableInOutputFormat($input, $output, $rows);
127
+        return self::SUCCESS;
128
+    }
129 129
 }
Please login to merge, or discard this patch.