URLGenerator.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  6. * SPDX-License-Identifier: AGPL-3.0-only
  7. */
  8. namespace OC;
  9. use OC\Route\Router;
  10. use OCA\Theming\ThemingDefaults;
  11. use OCP\App\AppPathNotFoundException;
  12. use OCP\App\IAppManager;
  13. use OCP\ICacheFactory;
  14. use OCP\IConfig;
  15. use OCP\INavigationManager;
  16. use OCP\IRequest;
  17. use OCP\IURLGenerator;
  18. use OCP\IUserSession;
  19. use RuntimeException;
  20. /**
  21. * Class to generate URLs
  22. */
  23. class URLGenerator implements IURLGenerator {
  24. /** @var IConfig */
  25. private $config;
  26. /** @var IUserSession */
  27. public $userSession;
  28. /** @var ICacheFactory */
  29. private $cacheFactory;
  30. /** @var IRequest */
  31. private $request;
  32. /** @var Router */
  33. private $router;
  34. /** @var null|string */
  35. private $baseUrl = null;
  36. private ?IAppManager $appManager = null;
  37. private ?INavigationManager $navigationManager = null;
  38. public function __construct(IConfig $config,
  39. IUserSession $userSession,
  40. ICacheFactory $cacheFactory,
  41. IRequest $request,
  42. Router $router,
  43. ) {
  44. $this->config = $config;
  45. $this->userSession = $userSession;
  46. $this->cacheFactory = $cacheFactory;
  47. $this->request = $request;
  48. $this->router = $router;
  49. }
  50. private function getAppManager(): IAppManager {
  51. if ($this->appManager !== null) {
  52. return $this->appManager;
  53. }
  54. $this->appManager = \OCP\Server::get(IAppManager::class);
  55. return $this->appManager;
  56. }
  57. private function getNavigationManager(): INavigationManager {
  58. if ($this->navigationManager !== null) {
  59. return $this->navigationManager;
  60. }
  61. $this->navigationManager = \OCP\Server::get(INavigationManager::class);
  62. return $this->navigationManager;
  63. }
  64. /**
  65. * Creates an url using a defined route
  66. *
  67. * @param string $routeName
  68. * @param array $arguments args with param=>value, will be appended to the returned url
  69. * @return string the url
  70. *
  71. * Returns a url to the given route.
  72. */
  73. public function linkToRoute(string $routeName, array $arguments = []): string {
  74. return $this->router->generate($routeName, $arguments);
  75. }
  76. /**
  77. * Creates an absolute url using a defined route
  78. * @param string $routeName
  79. * @param array $arguments args with param=>value, will be appended to the returned url
  80. * @return string the url
  81. *
  82. * Returns an absolute url to the given route.
  83. */
  84. public function linkToRouteAbsolute(string $routeName, array $arguments = []): string {
  85. return $this->getAbsoluteURL($this->linkToRoute($routeName, $arguments));
  86. }
  87. public function linkToOCSRouteAbsolute(string $routeName, array $arguments = []): string {
  88. // Returns `/subfolder/index.php/ocsapp/…` with `'htaccess.IgnoreFrontController' => false` in config.php
  89. // And `/subfolder/ocsapp/…` with `'htaccess.IgnoreFrontController' => true` in config.php
  90. $route = $this->router->generate('ocs.' . $routeName, $arguments, false);
  91. // Cut off `/subfolder`
  92. if (\OC::$WEBROOT !== '' && str_starts_with($route, \OC::$WEBROOT)) {
  93. $route = substr($route, \strlen(\OC::$WEBROOT));
  94. }
  95. if (str_starts_with($route, '/index.php/')) {
  96. $route = substr($route, 10);
  97. }
  98. // Remove `ocsapp/` bit
  99. $route = substr($route, 7);
  100. // Prefix with ocs/v2.php endpoint
  101. $route = '/ocs/v2.php' . $route;
  102. // Turn into an absolute URL
  103. return $this->getAbsoluteURL($route);
  104. }
  105. /**
  106. * Creates an url
  107. *
  108. * @param string $appName app
  109. * @param string $file file
  110. * @param array $args array with param=>value, will be appended to the returned url
  111. * The value of $args will be urlencoded
  112. * @return string the url
  113. *
  114. * Returns a url to the given app and file.
  115. */
  116. public function linkTo(string $appName, string $file, array $args = []): string {
  117. $frontControllerActive = ($this->config->getSystemValueBool('htaccess.IgnoreFrontController', false) || getenv('front_controller_active') === 'true');
  118. if ($appName !== '') {
  119. $app_path = $this->getAppManager()->getAppPath($appName);
  120. // Check if the app is in the app folder
  121. if (file_exists($app_path . '/' . $file)) {
  122. if (str_ends_with($file, 'php')) {
  123. $urlLinkTo = \OC::$WEBROOT . '/index.php/apps/' . $appName;
  124. if ($frontControllerActive) {
  125. $urlLinkTo = \OC::$WEBROOT . '/apps/' . $appName;
  126. }
  127. $urlLinkTo .= ($file !== 'index.php') ? '/' . $file : '';
  128. } else {
  129. $urlLinkTo = $this->getAppManager()->getAppWebPath($appName) . '/' . $file;
  130. }
  131. } else {
  132. $urlLinkTo = \OC::$WEBROOT . '/' . $appName . '/' . $file;
  133. }
  134. } else {
  135. if (file_exists(\OC::$SERVERROOT . '/core/' . $file)) {
  136. $urlLinkTo = \OC::$WEBROOT . '/core/' . $file;
  137. } else {
  138. if ($frontControllerActive && $file === 'index.php') {
  139. $urlLinkTo = \OC::$WEBROOT . '/';
  140. } else {
  141. $urlLinkTo = \OC::$WEBROOT . '/' . $file;
  142. }
  143. }
  144. }
  145. if ($args && $query = http_build_query($args, '', '&')) {
  146. $urlLinkTo .= '?' . $query;
  147. }
  148. return $urlLinkTo;
  149. }
  150. /**
  151. * Creates path to an image
  152. *
  153. * @param string $appName app
  154. * @param string $file image name
  155. * @throws \RuntimeException If the image does not exist
  156. * @return string the url
  157. *
  158. * Returns the path to the image.
  159. */
  160. public function imagePath(string $appName, string $file): string {
  161. $cache = $this->cacheFactory->createDistributed('imagePath-' . md5($this->getBaseUrl()) . '-');
  162. $cacheKey = $appName . '-' . $file;
  163. if ($key = $cache->get($cacheKey)) {
  164. return $key;
  165. }
  166. // Read the selected theme from the config file
  167. $theme = \OC_Util::getTheme();
  168. //if a theme has a png but not an svg always use the png
  169. $basename = substr(basename($file), 0, -4);
  170. try {
  171. $appPath = $this->getAppManager()->getAppPath($appName);
  172. } catch (AppPathNotFoundException $e) {
  173. if ($appName === 'core' || $appName === '') {
  174. $appName = 'core';
  175. $appPath = false;
  176. } else {
  177. throw new RuntimeException('image not found: image: ' . $file . ' webroot: ' . \OC::$WEBROOT . ' serverroot: ' . \OC::$SERVERROOT);
  178. }
  179. }
  180. // Check if the app is in the app folder
  181. $path = '';
  182. $themingEnabled = $this->config->getSystemValueBool('installed', false) && $this->getAppManager()->isEnabledForUser('theming');
  183. $themingImagePath = false;
  184. if ($themingEnabled) {
  185. $themingDefaults = \OC::$server->get('ThemingDefaults');
  186. if ($themingDefaults instanceof ThemingDefaults) {
  187. $themingImagePath = $themingDefaults->replaceImagePath($appName, $file);
  188. }
  189. }
  190. if (file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$appName/img/$file")) {
  191. $path = \OC::$WEBROOT . "/themes/$theme/apps/$appName/img/$file";
  192. } elseif (!file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$appName/img/$basename.svg")
  193. && file_exists(\OC::$SERVERROOT . "/themes/$theme/apps/$appName/img/$basename.png")) {
  194. $path = \OC::$WEBROOT . "/themes/$theme/apps/$appName/img/$basename.png";
  195. } elseif (!empty($appName) and file_exists(\OC::$SERVERROOT . "/themes/$theme/$appName/img/$file")) {
  196. $path = \OC::$WEBROOT . "/themes/$theme/$appName/img/$file";
  197. } elseif (!empty($appName) and (!file_exists(\OC::$SERVERROOT . "/themes/$theme/$appName/img/$basename.svg")
  198. && file_exists(\OC::$SERVERROOT . "/themes/$theme/$appName/img/$basename.png"))) {
  199. $path = \OC::$WEBROOT . "/themes/$theme/$appName/img/$basename.png";
  200. } elseif (file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$file")) {
  201. $path = \OC::$WEBROOT . "/themes/$theme/core/img/$file";
  202. } elseif (!file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$basename.svg")
  203. && file_exists(\OC::$SERVERROOT . "/themes/$theme/core/img/$basename.png")) {
  204. $path = \OC::$WEBROOT . "/themes/$theme/core/img/$basename.png";
  205. } elseif ($themingEnabled && $themingImagePath) {
  206. $path = $themingImagePath;
  207. } elseif ($appPath && file_exists($appPath . "/img/$file")) {
  208. $path = $this->getAppManager()->getAppWebPath($appName) . "/img/$file";
  209. } elseif ($appPath && !file_exists($appPath . "/img/$basename.svg")
  210. && file_exists($appPath . "/img/$basename.png")) {
  211. $path = $this->getAppManager()->getAppWebPath($appName) . "/img/$basename.png";
  212. } elseif (!empty($appName) and file_exists(\OC::$SERVERROOT . "/$appName/img/$file")) {
  213. $path = \OC::$WEBROOT . "/$appName/img/$file";
  214. } elseif (!empty($appName) and (!file_exists(\OC::$SERVERROOT . "/$appName/img/$basename.svg")
  215. && file_exists(\OC::$SERVERROOT . "/$appName/img/$basename.png"))) {
  216. $path = \OC::$WEBROOT . "/$appName/img/$basename.png";
  217. } elseif (file_exists(\OC::$SERVERROOT . "/core/img/$file")) {
  218. $path = \OC::$WEBROOT . "/core/img/$file";
  219. } elseif (!file_exists(\OC::$SERVERROOT . "/core/img/$basename.svg")
  220. && file_exists(\OC::$SERVERROOT . "/core/img/$basename.png")) {
  221. $path = \OC::$WEBROOT . "/themes/$theme/core/img/$basename.png";
  222. }
  223. if ($path !== '') {
  224. $cache->set($cacheKey, $path);
  225. return $path;
  226. }
  227. throw new RuntimeException('image not found: image:' . $file . ' webroot:' . \OC::$WEBROOT . ' serverroot:' . \OC::$SERVERROOT);
  228. }
  229. /**
  230. * Makes an URL absolute
  231. * @param string $url the url in the Nextcloud host
  232. * @return string the absolute version of the url
  233. */
  234. public function getAbsoluteURL(string $url): string {
  235. $separator = str_starts_with($url, '/') ? '' : '/';
  236. if (\OC::$CLI && !\defined('PHPUNIT_RUN')) {
  237. return rtrim($this->config->getSystemValueString('overwrite.cli.url'), '/') . '/' . ltrim($url, '/');
  238. }
  239. // The Nextcloud web root could already be prepended.
  240. if (\OC::$WEBROOT !== '' && str_starts_with($url, \OC::$WEBROOT)) {
  241. $url = substr($url, \strlen(\OC::$WEBROOT));
  242. }
  243. return $this->getBaseUrl() . $separator . $url;
  244. }
  245. /**
  246. * @param string $key
  247. * @return string url to the online documentation
  248. */
  249. public function linkToDocs(string $key): string {
  250. $theme = \OC::$server->get('ThemingDefaults');
  251. return $theme->buildDocLinkToKey($key);
  252. }
  253. /**
  254. * Returns the URL of the default page based on the system configuration
  255. * and the apps visible for the current user
  256. * @return string
  257. */
  258. public function linkToDefaultPageUrl(): string {
  259. // Deny the redirect if the URL contains a @
  260. // This prevents unvalidated redirects like ?redirect_url=:user@domain.com
  261. if (isset($_REQUEST['redirect_url']) && !str_contains($_REQUEST['redirect_url'], '@')) {
  262. return $this->getAbsoluteURL(urldecode($_REQUEST['redirect_url']));
  263. }
  264. $defaultPage = $this->config->getAppValue('core', 'defaultpage');
  265. if ($defaultPage) {
  266. return $this->getAbsoluteURL($defaultPage);
  267. }
  268. $entryId = $this->getNavigationManager()->getDefaultEntryIdForUser();
  269. $entry = $this->getNavigationManager()->get($entryId);
  270. $href = (string)$entry['href'];
  271. if ($href === '') {
  272. throw new \InvalidArgumentException('Default navigation entry is missing href: ' . $entryId);
  273. }
  274. if (str_starts_with($href, '/index.php/') && ($this->config->getSystemValueBool('htaccess.IgnoreFrontController', false) || getenv('front_controller_active') === 'true')) {
  275. $href = substr($href, 10);
  276. }
  277. return $this->getAbsoluteURL($href);
  278. }
  279. /**
  280. * @return string base url of the current request
  281. */
  282. public function getBaseUrl(): string {
  283. // BaseUrl can be equal to 'http(s)://' during the first steps of the initial setup.
  284. if ($this->baseUrl === null || $this->baseUrl === 'http://' || $this->baseUrl === 'https://') {
  285. $this->baseUrl = $this->request->getServerProtocol() . '://' . $this->request->getServerHost() . \OC::$WEBROOT;
  286. }
  287. return $this->baseUrl;
  288. }
  289. /**
  290. * @return string webroot part of the base url
  291. */
  292. public function getWebroot(): string {
  293. return \OC::$WEBROOT;
  294. }
  295. }