router.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. <?php
  2. /**
  3. * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl>
  4. * This file is licensed under the Affero General Public License version 3 or
  5. * later.
  6. * See the COPYING-README file.
  7. */
  8. namespace OC\Route;
  9. use OCP\Route\IRouter;
  10. use OCP\AppFramework\App;
  11. use Symfony\Component\Routing\Matcher\UrlMatcher;
  12. use Symfony\Component\Routing\Generator\UrlGenerator;
  13. use Symfony\Component\Routing\RequestContext;
  14. use Symfony\Component\Routing\RouteCollection;
  15. use Symfony\Component\Routing\Exception\ResourceNotFoundException;
  16. class Router implements IRouter {
  17. /**
  18. * @var \Symfony\Component\Routing\RouteCollection[]
  19. */
  20. protected $collections = array();
  21. /**
  22. * @var \Symfony\Component\Routing\RouteCollection
  23. */
  24. protected $collection = null;
  25. /**
  26. * @var string
  27. */
  28. protected $collectionName = null;
  29. /**
  30. * @var \Symfony\Component\Routing\RouteCollection
  31. */
  32. protected $root = null;
  33. /**
  34. * @var \Symfony\Component\Routing\Generator\UrlGenerator
  35. */
  36. protected $generator = null;
  37. /**
  38. * @var string[]
  39. */
  40. protected $routingFiles;
  41. /**
  42. * @var string
  43. */
  44. protected $cacheKey;
  45. protected $loaded = false;
  46. protected $loadedApps = array();
  47. public function __construct() {
  48. $baseUrl = \OC_Helper::linkTo('', 'index.php');
  49. if (!\OC::$CLI) {
  50. $method = $_SERVER['REQUEST_METHOD'];
  51. } else {
  52. $method = 'GET';
  53. }
  54. $request = \OC::$server->getRequest();
  55. $host = $request->getServerHost();
  56. $schema = $request->getServerProtocol();
  57. $this->context = new RequestContext($baseUrl, $method, $host, $schema);
  58. // TODO cache
  59. $this->root = $this->getCollection('root');
  60. }
  61. /**
  62. * Get the files to load the routes from
  63. *
  64. * @return string[]
  65. */
  66. public function getRoutingFiles() {
  67. if (!isset($this->routingFiles)) {
  68. $this->routingFiles = array();
  69. foreach (\OC_APP::getEnabledApps() as $app) {
  70. $file = \OC_App::getAppPath($app) . '/appinfo/routes.php';
  71. if (file_exists($file)) {
  72. $this->routingFiles[$app] = $file;
  73. }
  74. }
  75. }
  76. return $this->routingFiles;
  77. }
  78. /**
  79. * @return string
  80. */
  81. public function getCacheKey() {
  82. if (!isset($this->cacheKey)) {
  83. $files = $this->getRoutingFiles();
  84. $files[] = 'settings/routes.php';
  85. $files[] = 'core/routes.php';
  86. $files[] = 'ocs/routes.php';
  87. $this->cacheKey = \OC\Cache::generateCacheKeyFromFiles($files);
  88. }
  89. return $this->cacheKey;
  90. }
  91. /**
  92. * loads the api routes
  93. * @return void
  94. */
  95. public function loadRoutes($app = null) {
  96. $requestedApp = $app;
  97. if ($this->loaded) {
  98. return;
  99. }
  100. if (is_null($app)) {
  101. $this->loaded = true;
  102. $routingFiles = $this->getRoutingFiles();
  103. } else {
  104. if (isset($this->loadedApps[$app])) {
  105. return;
  106. }
  107. $file = \OC_App::getAppPath($app) . '/appinfo/routes.php';
  108. if (file_exists($file)) {
  109. $routingFiles = array($app => $file);
  110. } else {
  111. $routingFiles = array();
  112. }
  113. }
  114. \OC::$server->getEventLogger()->start('loadroutes' . $requestedApp, 'Loading Routes');
  115. foreach ($routingFiles as $app => $file) {
  116. if (!isset($this->loadedApps[$app])) {
  117. $this->loadedApps[$app] = true;
  118. $this->useCollection($app);
  119. $this->requireRouteFile($file, $app);
  120. $collection = $this->getCollection($app);
  121. $collection->addPrefix('/apps/' . $app);
  122. $this->root->addCollection($collection);
  123. }
  124. }
  125. if (!isset($this->loadedApps['core'])) {
  126. $this->loadedApps['core'] = true;
  127. $this->useCollection('root');
  128. require_once 'settings/routes.php';
  129. require_once 'core/routes.php';
  130. // include ocs routes
  131. require_once 'ocs/routes.php';
  132. $collection = $this->getCollection('ocs');
  133. $collection->addPrefix('/ocs');
  134. $this->root->addCollection($collection);
  135. }
  136. \OC::$server->getEventLogger()->end('loadroutes' . $requestedApp);
  137. }
  138. /**
  139. * @param string $name
  140. * @return \Symfony\Component\Routing\RouteCollection
  141. */
  142. protected function getCollection($name) {
  143. if (!isset($this->collections[$name])) {
  144. $this->collections[$name] = new RouteCollection();
  145. }
  146. return $this->collections[$name];
  147. }
  148. /**
  149. * Sets the collection to use for adding routes
  150. *
  151. * @param string $name Name of the collection to use.
  152. * @return void
  153. */
  154. public function useCollection($name) {
  155. $this->collection = $this->getCollection($name);
  156. $this->collectionName = $name;
  157. }
  158. /**
  159. * returns the current collection name in use for adding routes
  160. *
  161. * @return string the collection name
  162. */
  163. public function getCurrentCollection() {
  164. return $this->collectionName;
  165. }
  166. /**
  167. * Create a \OC\Route\Route.
  168. *
  169. * @param string $name Name of the route to create.
  170. * @param string $pattern The pattern to match
  171. * @param array $defaults An array of default parameter values
  172. * @param array $requirements An array of requirements for parameters (regexes)
  173. * @return \OC\Route\Route
  174. */
  175. public function create($name, $pattern, array $defaults = array(), array $requirements = array()) {
  176. $route = new Route($pattern, $defaults, $requirements);
  177. $this->collection->add($name, $route);
  178. return $route;
  179. }
  180. /**
  181. * Find the route matching $url
  182. *
  183. * @param string $url The url to find
  184. * @throws \Exception
  185. * @return void
  186. */
  187. public function match($url) {
  188. if (substr($url, 0, 6) === '/apps/') {
  189. // empty string / 'apps' / $app / rest of the route
  190. list(, , $app,) = explode('/', $url, 4);
  191. \OC::$REQUESTEDAPP = $app;
  192. $this->loadRoutes($app);
  193. } else if (substr($url, 0, 6) === '/core/' or substr($url, 0, 10) === '/settings/') {
  194. \OC::$REQUESTEDAPP = $url;
  195. if (!\OC_Config::getValue('maintenance', false) && !\OCP\Util::needUpgrade()) {
  196. \OC_App::loadApps();
  197. }
  198. $this->loadRoutes('core');
  199. } else {
  200. $this->loadRoutes();
  201. }
  202. $matcher = new UrlMatcher($this->root, $this->context);
  203. try {
  204. $parameters = $matcher->match($url);
  205. } catch (ResourceNotFoundException $e) {
  206. if (substr($url, -1) !== '/') {
  207. // We allow links to apps/files? for backwards compatibility reasons
  208. // However, since Symfony does not allow empty route names, the route
  209. // we need to match is '/', so we need to append the '/' here.
  210. try {
  211. $parameters = $matcher->match($url . '/');
  212. } catch (ResourceNotFoundException $newException) {
  213. // If we still didn't match a route, we throw the original exception
  214. throw $e;
  215. }
  216. } else {
  217. throw $e;
  218. }
  219. }
  220. \OC::$server->getEventLogger()->start('run_route', 'Run route');
  221. if (isset($parameters['action'])) {
  222. $action = $parameters['action'];
  223. if (!is_callable($action)) {
  224. throw new \Exception('not a callable action');
  225. }
  226. unset($parameters['action']);
  227. call_user_func($action, $parameters);
  228. } elseif (isset($parameters['file'])) {
  229. include $parameters['file'];
  230. } else {
  231. throw new \Exception('no action available');
  232. }
  233. \OC::$server->getEventLogger()->end('run_route');
  234. }
  235. /**
  236. * Get the url generator
  237. * @return \Symfony\Component\Routing\Generator\UrlGenerator
  238. *
  239. */
  240. public function getGenerator() {
  241. if (null !== $this->generator) {
  242. return $this->generator;
  243. }
  244. return $this->generator = new UrlGenerator($this->root, $this->context);
  245. }
  246. /**
  247. * Generate url based on $name and $parameters
  248. *
  249. * @param string $name Name of the route to use.
  250. * @param array $parameters Parameters for the route
  251. * @param bool $absolute
  252. * @return string
  253. */
  254. public function generate($name, $parameters = array(), $absolute = false) {
  255. $this->loadRoutes();
  256. return $this->getGenerator()->generate($name, $parameters, $absolute);
  257. }
  258. /**
  259. * To isolate the variable scope used inside the $file it is required in it's own method
  260. * @param string $file the route file location to include
  261. * @param string $appName
  262. */
  263. private function requireRouteFile($file, $appName) {
  264. $this->setupRoutes(include_once $file, $appName);
  265. }
  266. /**
  267. * If a routes.php file returns an array, try to set up the application and
  268. * register the routes for the app. The application class will be chosen by
  269. * camelcasing the appname, e.g.: my_app will be turned into
  270. * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default
  271. * App will be intialized. This makes it optional to ship an
  272. * appinfo/application.php by using the built in query resolver
  273. * @param array $routes the application routes
  274. * @param string $appName the name of the app.
  275. */
  276. private function setupRoutes($routes, $appName) {
  277. if (is_array($routes)) {
  278. $appNameSpace = App::buildAppNamespace($appName);
  279. $applicationClassName = $appNameSpace . '\\AppInfo\\Application';
  280. if (class_exists($applicationClassName)) {
  281. $application = new $applicationClassName();
  282. } else {
  283. $application = new App($appName);
  284. }
  285. $application->registerRoutes($this, $routes);
  286. }
  287. }
  288. }