Merge branch '3.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / request / LinkHandler.class.php
CommitLineData
11ade432
AE
1<?php
2namespace wcf\system\request;
375ced03 3use wcf\data\page\PageCache;
3cd3cfc3 4use wcf\data\DatabaseObjectDecorator;
11ade432 5use wcf\system\application\ApplicationHandler;
3295fb92 6use wcf\system\language\LanguageFactory;
c8a4a1f5 7use wcf\system\Regex;
11ade432 8use wcf\system\SingletonFactory;
bc62b9c1 9use wcf\system\WCF;
1db37793 10use wcf\util\StringUtil;
11ade432
AE
11
12/**
13 * Handles relative links within the wcf.
14 *
9f959ced 15 * @author Marcel Werk
c839bd49 16 * @copyright 2001-2018 WoltLab GmbH
11ade432 17 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
e71525e4 18 * @package WoltLabSuite\Core\System\Request
11ade432
AE
19 */
20class LinkHandler extends SingletonFactory {
c8a4a1f5
AE
21 /**
22 * regex object to filter title
39abe192 23 * @var RegEx
c8a4a1f5 24 */
39abe192 25 protected $titleRegex;
c8a4a1f5 26
99a64f13
MW
27 /**
28 * title search strings
39abe192 29 * @var string[]
99a64f13 30 */
39abe192 31 protected $titleSearch = [];
99a64f13
MW
32
33 /**
34 * title replacement strings
39abe192 35 * @var string[]
99a64f13 36 */
39abe192 37 protected $titleReplace = [];
99a64f13 38
bc62b9c1 39
c8a4a1f5 40 /**
0fcfe5f6 41 * @inheritDoc
c8a4a1f5
AE
42 */
43 protected function init() {
8c5d0070 44 $this->titleRegex = new Regex('[^\p{L}\p{N}]+', Regex::UTF_8);
99a64f13
MW
45
46 if (defined('URL_TITLE_COMPONENT_REPLACEMENT') && URL_TITLE_COMPONENT_REPLACEMENT) {
47 $replacements = explode("\n", StringUtil::unifyNewlines(StringUtil::trim(URL_TITLE_COMPONENT_REPLACEMENT)));
48 foreach ($replacements as $replacement) {
49 if (strpos($replacement, '=') === false) continue;
50 $components = explode('=', $replacement);
51 $this->titleSearch[] = $components[0];
52 $this->titleReplace[] = $components[1];
53 }
54 }
c8a4a1f5
AE
55 }
56
11ade432
AE
57 /**
58 * Returns a relative link.
59 *
6dcaf901 60 * @param string $controller
39bea7dd
MS
61 * @param array $parameters
62 * @param string $url
11ade432
AE
63 * @return string
64 */
39abe192 65 public function getLink($controller = null, array $parameters = [], $url = '') {
f98e7cf2 66 $abbreviation = 'wcf';
ccedf3ab 67 $anchor = '';
f341086b 68 $isACP = $originIsACP = RequestHandler::getInstance()->isACPRequest();
39abe192 69 $isRaw = false;
d8954071
AE
70 $encodeTitle = true;
71
72 /**
e71525e4 73 * @deprecated 3.0 - no longer required
d8954071 74 */
13b7bb1f 75 /** @noinspection PhpUnusedLocalVariableInspection */
d8954071 76 $appendSession = false;
bfb61bb4
AE
77
78 // enforce a certain level of sanitation and protection for links embedded in emails
d13cae5b
MW
79 if (isset($parameters['isEmail'])) {
80 if ((bool)$parameters['isEmail']) {
81 $parameters['forceFrontend'] = true;
82 }
83
fda7b2bc 84 unset($parameters['isEmail']);
bfb61bb4
AE
85 }
86
f98e7cf2
AE
87 if (isset($parameters['application'])) {
88 $abbreviation = $parameters['application'];
f98e7cf2
AE
89 }
90 if (isset($parameters['isRaw'])) {
91 $isRaw = $parameters['isRaw'];
92 unset($parameters['isRaw']);
11ade432 93 }
876d2d83 94 if (isset($parameters['appendSession'])) {
876d2d83
AE
95 unset($parameters['appendSession']);
96 }
636f9c1f
AE
97 if (isset($parameters['isACP'])) {
98 $isACP = (bool) $parameters['isACP'];
99 unset($parameters['isACP']);
636f9c1f 100 }
c5614b1a
MW
101 if (isset($parameters['forceFrontend'])) {
102 if ($parameters['forceFrontend'] && $isACP) {
103 $isACP = false;
c5614b1a
MW
104 }
105 unset($parameters['forceFrontend']);
692c7b30 106 }
74622428 107 if (isset($parameters['forceWCF'])) {
e71525e4 108 /** @deprecated 3.0 */
74622428
AE
109 unset($parameters['forceWCF']);
110 }
5fb44399 111
19c8cd5f
MW
112 if (isset($parameters['encodeTitle'])) {
113 $encodeTitle = $parameters['encodeTitle'];
114 unset($parameters['encodeTitle']);
115 }
11ade432 116
ccedf3ab
AE
117 // remove anchor before parsing
118 if (($pos = strpos($url, '#')) !== false) {
119 $anchor = substr($url, $pos);
120 $url = substr($url, 0, $pos);
121 }
122
f98e7cf2 123 // build route
9c720493 124 if ($controller === null) {
191b8391
TD
125 if ($isACP) {
126 $controller = 'Index';
127 }
128 else {
dbf41d8a
AE
129 if (!empty($parameters['application']) && $abbreviation !== 'wcf') {
130 $application = ApplicationHandler::getInstance()->getApplication($abbreviation);
131 if ($application === null) {
132 throw new \RuntimeException("Unknown abbreviation '" . $abbreviation . "'.");
133 }
134
135 $landingPage = PageCache::getInstance()->getPage($application->landingPageID);
136 if ($landingPage === null) {
137 $landingPage = PageCache::getInstance()->getPageByController(WCF::getApplicationObject($application)->getPrimaryController());
138 }
139
140 if ($landingPage !== null) {
141 return $landingPage->getLink();
142 }
143 }
144
375ced03 145 return PageCache::getInstance()->getLandingPage()->getLink();
191b8391 146 }
9c720493
AE
147 }
148
149 // handle object
150 if (isset($parameters['object'])) {
c8a4a1f5 151 if (!($parameters['object'] instanceof IRouteController) && $parameters['object'] instanceof DatabaseObjectDecorator && $parameters['object']->getDecoratedObject() instanceof IRouteController) {
9c720493 152 $parameters['object'] = $parameters['object']->getDecoratedObject();
97b6b000
AE
153 }
154
c8a4a1f5 155 if ($parameters['object'] instanceof IRouteController) {
29846bb1 156 $parameters['id'] = $parameters['object']->getObjectID();
9c720493 157 $parameters['title'] = $parameters['object']->getTitle();
0602bb11 158 }
9c720493 159 }
461b9ab8 160 unset($parameters['object']);
9c720493
AE
161
162 if (isset($parameters['title'])) {
99a64f13
MW
163 // component replacement
164 if (!empty($this->titleSearch)) {
165 $parameters['title'] = str_replace($this->titleSearch, $this->titleReplace, $parameters['title']);
166 }
167
9c720493 168 // remove illegal characters
c8a4a1f5 169 $parameters['title'] = trim($this->titleRegex->replace($parameters['title'], '-'), '-');
a44843c3
AE
170
171 // trim to 80 characters
84299745 172 $parameters['title'] = rtrim(mb_substr($parameters['title'], 0, 80), '-');
b9f49efd 173 $parameters['title'] = mb_strtolower($parameters['title']);
a97905ff 174
19c8cd5f
MW
175 // encode title
176 if ($encodeTitle) $parameters['title'] = rawurlencode($parameters['title']);
9c720493
AE
177 }
178
f3aa5021 179 $parameters['controller'] = $controller;
c2de61fb 180 $routeURL = RouteHandler::getInstance()->buildRoute($abbreviation, $parameters, $isACP);
9c720493
AE
181 if (!$isRaw && !empty($url)) {
182 $routeURL .= (strpos($routeURL, '?') === false) ? '?' : '&';
11ade432 183 }
034b6a78
AE
184
185 // encode certain characters
186 if (!empty($url)) {
39abe192 187 $url = str_replace(['[', ']'], ['%5B', '%5D'], $url);
034b6a78
AE
188 }
189
9c720493 190 $url = $routeURL . $url;
11ade432 191
a75dfffb
AE
192 // handle applications
193 if (!PACKAGE_ID) {
39abe192 194 $url = RouteHandler::getHost() . RouteHandler::getPath(['acp']) . ($isACP ? 'acp/' : '') . $url;
a75dfffb
AE
195 }
196 else {
f341086b 197 if (RequestHandler::getInstance()->inRescueMode()) {
39abe192 198 $pageURL = RouteHandler::getHost() . str_replace('//', '/', RouteHandler::getPath(['acp']));
f98e7cf2 199 }
c3299383 200 else {
c308c947
AE
201 $application = ApplicationHandler::getInstance()->getApplication($abbreviation);
202 if ($application === null) {
203 throw new \InvalidArgumentException("Unknown application identifier '{$abbreviation}'.");
204 }
205
206 $pageURL = $application->getPageURL();
f98e7cf2
AE
207 }
208
c3299383 209 $url = $pageURL . ($isACP ? 'acp/' : '') . $url;
11ade432
AE
210 }
211
ccedf3ab
AE
212 // append previously removed anchor
213 $url .= $anchor;
214
f98e7cf2 215 return $url;
11ade432 216 }
3295fb92
AE
217
218 /**
219 * Returns the full URL to a CMS page. The `$languageID` parameter is optional and if not
220 * present (or the integer value `-1` is given) will cause the handler to pick the correct
221 * language version based upon the user's language.
222 *
223 * Passing in an illegal page id will cause this method to fail silently, returning an
224 * empty string.
225 *
2f53b086
MS
226 * @param integer $pageID page id
227 * @param integer $languageID language id, optional
228 * @return string full URL of empty string if `$pageID` is invalid
e71525e4 229 * @since 3.0
3295fb92
AE
230 */
231 public function getCmsLink($pageID, $languageID = -1) {
232 // use current language
233 if ($languageID === -1) {
234 $data = ControllerMap::getInstance()->lookupCmsPage($pageID, WCF::getLanguage()->languageID);
235
236 // no result
237 if ($data === null) {
238 // attempt to use the default language instead
239 if (LanguageFactory::getInstance()->getDefaultLanguageID() != WCF::getLanguage()->languageID) {
240 $data = ControllerMap::getInstance()->lookupCmsPage($pageID, LanguageFactory::getInstance()->getDefaultLanguageID());
241 }
242
243 // no result, possibly this is a non-multilingual page
244 if ($data === null) {
245 $data = ControllerMap::getInstance()->lookupCmsPage($pageID, null);
246 }
247
248 // still no result, page does not exist at all
249 if ($data === null) {
250 return '';
251 }
252 }
253 }
254 else {
255 $data = ControllerMap::getInstance()->lookupCmsPage($pageID, $languageID);
256
257 // no result, page does not exist or at least not in the given language
258 if ($data === null) {
259 return '';
260 }
261 }
262
263 return $this->getLink($data['controller'], [
b10d35d1
MW
264 'application' => $data['application'],
265 'forceFrontend' => true
3295fb92
AE
266 ]);
267 }
11ade432 268}