Commit | Line | Data |
---|---|---|
158bd3ca TD |
1 | <?php |
2 | namespace wcf\page; | |
9c9d5d4b AE |
3 | use wcf\form\DisclaimerForm; |
4 | use wcf\form\EmailActivationForm; | |
5 | use wcf\form\EmailNewActivationCodeForm; | |
6 | use wcf\form\LoginForm; | |
7 | use wcf\form\LostPasswordForm; | |
8 | use wcf\form\NewPasswordForm; | |
9 | use wcf\form\RegisterActivationForm; | |
10 | use wcf\form\RegisterForm; | |
11 | use wcf\form\RegisterNewActivationCodeForm; | |
158bd3ca | 12 | use wcf\system\event\EventHandler; |
d82d508e | 13 | use wcf\system\exception\IllegalLinkException; |
dc013e39 | 14 | use wcf\system\exception\PermissionDeniedException; |
264c6eea | 15 | use wcf\system\menu\acp\ACPMenu; |
9c9d5d4b | 16 | use wcf\system\request\LinkHandler; |
264c6eea | 17 | use wcf\system\request\RequestHandler; |
d82d508e | 18 | use wcf\system\WCF; |
909b697f | 19 | use wcf\util\HeaderUtil; |
7d878d79 | 20 | use wcf\util\StringUtil; |
27930682 | 21 | use wcf\util\Url; |
158bd3ca TD |
22 | |
23 | /** | |
264c6eea MS |
24 | * Abstract implementation of a page which fires the default event actions of a |
25 | * page: | |
26 | * - readParameters | |
27 | * - readData | |
28 | * - assignVariables | |
29 | * - show | |
158bd3ca TD |
30 | * |
31 | * @author Marcel Werk | |
c839bd49 | 32 | * @copyright 2001-2018 WoltLab GmbH |
158bd3ca | 33 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
e71525e4 | 34 | * @package WoltLabSuite\Core\Page |
158bd3ca | 35 | */ |
da5a61fb | 36 | abstract class AbstractPage implements IPage { |
158bd3ca | 37 | /** |
909b697f | 38 | * name of the active menu item |
9f959ced | 39 | * @var string |
158bd3ca | 40 | */ |
909b697f | 41 | public $activeMenuItem = ''; |
158bd3ca | 42 | |
2c8e27c9 | 43 | /** |
909b697f | 44 | * value of the given action parameter |
2c8e27c9 AE |
45 | * @var string |
46 | */ | |
909b697f | 47 | public $action = ''; |
2c8e27c9 | 48 | |
264c6eea | 49 | /** |
909b697f | 50 | * canonical URL of this page |
264c6eea MS |
51 | * @var string |
52 | */ | |
909b697f | 53 | public $canonicalURL = ''; |
264c6eea | 54 | |
6d8afc41 MS |
55 | /** |
56 | * is true if canonical URL will be enforced even if POST data is represent | |
57 | * @var boolean | |
58 | */ | |
59 | public $forceCanonicalURL = false; | |
60 | ||
f810a4c1 AE |
61 | /** |
62 | * is true if the redirect should use a 307 instead of the default 301, not recommended in general | |
63 | * @var boolean | |
64 | */ | |
65 | public $softRedirectCanonicalURL = false; | |
66 | ||
37e99e48 MS |
67 | /** |
68 | * indicates if you need to be logged in to access this page | |
69 | * @var boolean | |
70 | */ | |
71 | public $loginRequired = false; | |
72 | ||
158bd3ca | 73 | /** |
21489986 | 74 | * needed modules to view this page |
7a23a706 | 75 | * @var string[] |
158bd3ca | 76 | */ |
058cbd6a | 77 | public $neededModules = []; |
158bd3ca TD |
78 | |
79 | /** | |
21489986 | 80 | * needed permissions to view this page |
7a23a706 | 81 | * @var string[] |
7959340c | 82 | */ |
058cbd6a | 83 | public $neededPermissions = []; |
158bd3ca | 84 | |
596e20e2 | 85 | /** |
909b697f AE |
86 | * name of the template for the called page |
87 | * @var string | |
596e20e2 | 88 | */ |
909b697f AE |
89 | public $templateName = ''; |
90 | ||
887a3153 MS |
91 | /** |
92 | * abbreviation of the application the template belongs to | |
93 | * @var string | |
94 | */ | |
95 | public $templateNameApplication = ''; | |
96 | ||
909b697f AE |
97 | /** |
98 | * enables template usage | |
99 | * @var string | |
100 | */ | |
101 | public $useTemplate = true; | |
596e20e2 | 102 | |
0c5a00b3 | 103 | /** |
0fcfe5f6 | 104 | * @inheritDoc |
0c5a00b3 AE |
105 | */ |
106 | public final function __construct() { } | |
107 | ||
158bd3ca | 108 | /** |
0fcfe5f6 | 109 | * @inheritDoc |
158bd3ca | 110 | */ |
8f1ba4d2 | 111 | public function __run() { |
158bd3ca TD |
112 | // call default methods |
113 | $this->readParameters(); | |
114 | $this->show(); | |
115 | } | |
116 | ||
117 | /** | |
0fcfe5f6 | 118 | * @inheritDoc |
158bd3ca TD |
119 | */ |
120 | public function readParameters() { | |
121 | // call readParameters event | |
122 | EventHandler::getInstance()->fireAction($this, 'readParameters'); | |
123 | ||
124 | // read action parameter | |
125 | if (isset($_REQUEST['action'])) $this->action = $_REQUEST['action']; | |
126 | } | |
127 | ||
128 | /** | |
0fcfe5f6 | 129 | * @inheritDoc |
158bd3ca TD |
130 | */ |
131 | public function readData() { | |
132 | // call readData event | |
133 | EventHandler::getInstance()->fireAction($this, 'readData'); | |
134 | } | |
135 | ||
136 | /** | |
0fcfe5f6 | 137 | * @inheritDoc |
158bd3ca TD |
138 | */ |
139 | public function assignVariables() { | |
140 | // call assignVariables event | |
141 | EventHandler::getInstance()->fireAction($this, 'assignVariables'); | |
142 | ||
143 | // assign parameters | |
058cbd6a | 144 | WCF::getTPL()->assign([ |
158bd3ca | 145 | 'action' => $this->action, |
a5a4f02d | 146 | 'templateName' => $this->templateName, |
d19b42c3 | 147 | 'templateNameApplication' => $this->templateNameApplication, |
a5a4f02d | 148 | 'canonicalURL' => $this->canonicalURL |
058cbd6a | 149 | ]); |
158bd3ca TD |
150 | } |
151 | ||
152 | /** | |
0fcfe5f6 | 153 | * @inheritDoc |
158bd3ca TD |
154 | */ |
155 | public function checkModules() { | |
156 | // call checkModules event | |
157 | EventHandler::getInstance()->fireAction($this, 'checkModules'); | |
158 | ||
159 | // check modules | |
15fa2802 MS |
160 | foreach ($this->neededModules as $module) { |
161 | if (!defined($module) || !constant($module)) { | |
162 | throw new IllegalLinkException(); | |
158bd3ca TD |
163 | } |
164 | } | |
165 | } | |
166 | ||
167 | /** | |
0fcfe5f6 | 168 | * @inheritDoc |
158bd3ca TD |
169 | */ |
170 | public function checkPermissions() { | |
171 | // call checkPermissions event | |
172 | EventHandler::getInstance()->fireAction($this, 'checkPermissions'); | |
173 | ||
dc013e39 AE |
174 | // check permission, it is sufficient to have at least one permission |
175 | if (!empty($this->neededPermissions)) { | |
176 | $hasPermissions = false; | |
177 | foreach ($this->neededPermissions as $permission) { | |
178 | if (WCF::getSession()->getPermission($permission)) { | |
179 | $hasPermissions = true; | |
180 | break; | |
181 | } | |
182 | } | |
183 | ||
184 | if (!$hasPermissions) { | |
185 | throw new PermissionDeniedException(); | |
186 | } | |
158bd3ca TD |
187 | } |
188 | } | |
189 | ||
190 | /** | |
0fcfe5f6 | 191 | * @inheritDoc |
158bd3ca TD |
192 | */ |
193 | public function show() { | |
9c9d5d4b AE |
194 | if (FORCE_LOGIN && !RequestHandler::getInstance()->isACPRequest() && !WCF::getUser()->userID) { |
195 | $this->forceLogin(); | |
196 | } | |
197 | ||
37e99e48 MS |
198 | // check if active user is logged in |
199 | if ($this->loginRequired && !WCF::getUser()->userID) { | |
200 | throw new PermissionDeniedException(); | |
201 | } | |
202 | ||
909b697f | 203 | // check if current request URL matches the canonical URL |
6d8afc41 | 204 | if ($this->canonicalURL && (empty($_POST) || $this->forceCanonicalURL)) { |
27930682 | 205 | $canonicalURL = Url::parse(preg_replace('~[?&]s=[a-f0-9]{40}~', '', $this->canonicalURL)); |
7f9d94ab AE |
206 | |
207 | // use $_SERVER['REQUEST_URI'] because it represents the URL used to access the site and not the internally rewritten one | |
4e2bca91 AE |
208 | // IIS Rewrite-Module has a bug causing the REQUEST_URI to be ISO-encoded |
209 | $requestURI = (!empty($_SERVER['UNENCODED_URL'])) ? $_SERVER['UNENCODED_URL'] : $_SERVER['REQUEST_URI']; | |
210 | $requestURI = preg_replace('~[?&]s=[a-f0-9]{40}~', '', $requestURI); | |
211 | ||
1d8935ec AE |
212 | if (!StringUtil::isUTF8($requestURI)) { |
213 | $requestURI = StringUtil::convertEncoding('ISO-8859-1', 'UTF-8', $requestURI); | |
214 | } | |
d0642fa7 | 215 | |
a68fb810 AE |
216 | // some webservers output lower-case encoding (e.g. %c3 instead of %C3) |
217 | $requestURI = preg_replace_callback('~%(?P<encoded>[a-zA-Z0-9]{2})~', function($matches) { | |
218 | return '%' . strtoupper($matches['encoded']); | |
219 | }, $requestURI); | |
220 | ||
d223a661 AE |
221 | // reduce successive forwarded slashes into a single one |
222 | $requestURI = preg_replace('~/{2,}~', '/', $requestURI); | |
909b697f | 223 | |
27930682 | 224 | $requestURL = Url::parse($requestURI); |
909b697f | 225 | $redirect = false; |
9d9aede4 | 226 | if ($canonicalURL['path'] != $requestURL['path']) { |
909b697f AE |
227 | $redirect = true; |
228 | } | |
9d9aede4 | 229 | else if (isset($canonicalURL['query'])) { |
b9b36fa0 AE |
230 | if (!isset($requestURL['query'])) { |
231 | $redirect = true; | |
232 | } | |
233 | else { | |
9d9aede4 | 234 | parse_str($canonicalURL['query'], $cQueryString); |
b9b36fa0 AE |
235 | parse_str($requestURL['query'], $rQueryString); |
236 | ||
237 | foreach ($cQueryString as $key => $value) { | |
238 | if (!isset($rQueryString[$key]) || $rQueryString[$key] != $value) { | |
239 | $redirect = true; | |
240 | break; | |
241 | } | |
909b697f AE |
242 | } |
243 | } | |
244 | } | |
245 | ||
1d8935ec | 246 | if ($redirect) { |
909b697f AE |
247 | $redirectURL = $this->canonicalURL; |
248 | if (!empty($requestURL['query'])) { | |
909b697f AE |
249 | parse_str($requestURL['query'], $rQueryString); |
250 | ||
9d9aede4 AE |
251 | if (!empty($canonicalURL['query'])) { |
252 | parse_str($canonicalURL['query'], $cQueryString); | |
909b697f AE |
253 | |
254 | // clean query string | |
255 | foreach ($cQueryString as $key => $value) { | |
256 | if (isset($rQueryString[$key])) { | |
257 | unset($rQueryString[$key]); | |
258 | } | |
259 | } | |
260 | } | |
261 | ||
262 | // drop route data from query | |
9d9aede4 AE |
263 | foreach ($rQueryString as $key => $value) { |
264 | if ($value === '') { | |
265 | unset($rQueryString[$key]); | |
909b697f AE |
266 | } |
267 | } | |
268 | ||
269 | if (!empty($rQueryString)) { | |
270 | $redirectURL .= (mb_strpos($redirectURL, '?') === false ? '?' : '&') . http_build_query($rQueryString, '', '&'); | |
271 | } | |
272 | } | |
273 | ||
15bf7f68 AE |
274 | // force a permanent redirect as recommended by Google |
275 | // https://support.google.com/webmasters/answer/6033086?hl=en#a_note_about_redirects | |
f810a4c1 | 276 | HeaderUtil::redirect($redirectURL, true, $this->softRedirectCanonicalURL); |
909b697f AE |
277 | exit; |
278 | } | |
279 | } | |
280 | ||
264c6eea MS |
281 | // sets the active menu item |
282 | $this->setActiveMenuItem(); | |
283 | ||
158bd3ca TD |
284 | // check modules |
285 | $this->checkModules(); | |
286 | ||
287 | // check permission | |
288 | $this->checkPermissions(); | |
289 | ||
290 | // read data | |
291 | $this->readData(); | |
9f959ced | 292 | |
e4fa3018 AE |
293 | // assign variables |
294 | $this->assignVariables(); | |
295 | ||
296 | // call show event | |
297 | EventHandler::getInstance()->fireAction($this, 'show'); | |
298 | ||
91d3ce06 | 299 | // try to guess template name |
e4fa3018 | 300 | $classParts = explode('\\', get_class($this)); |
91d3ce06 | 301 | if (empty($this->templateName)) { |
91d3ce06 | 302 | $className = preg_replace('~(Form|Page)$~', '', array_pop($classParts)); |
744316e3 | 303 | |
06701166 AE |
304 | // check if this an *Edit page and use the add-template instead |
305 | if (substr($className, -4) == 'Edit') { | |
306 | $className = substr($className, 0, -4) . 'Add'; | |
307 | } | |
744316e3 | 308 | |
91d3ce06 | 309 | $this->templateName = lcfirst($className); |
d8cd853c AE |
310 | |
311 | // assign guessed template name | |
312 | WCF::getTPL()->assign('templateName', $this->templateName); | |
91d3ce06 | 313 | } |
887a3153 MS |
314 | if (empty($this->templateNameApplication)) { |
315 | $this->templateNameApplication = array_shift($classParts); | |
316 | ||
317 | // assign guessed template application | |
318 | WCF::getTPL()->assign('templateNameApplication', $this->templateNameApplication); | |
319 | } | |
91d3ce06 | 320 | |
2c8e27c9 | 321 | if ($this->useTemplate) { |
2c8e27c9 | 322 | // show template |
887a3153 | 323 | WCF::getTPL()->display($this->templateName, $this->templateNameApplication); |
158bd3ca TD |
324 | } |
325 | } | |
596e20e2 | 326 | |
264c6eea MS |
327 | /** |
328 | * Sets the active menu item of the page. | |
329 | */ | |
330 | protected function setActiveMenuItem() { | |
331 | if (!empty($this->activeMenuItem)) { | |
332 | if (RequestHandler::getInstance()->isACPRequest()) { | |
333 | ACPMenu::getInstance()->setActiveMenuItem($this->activeMenuItem); | |
334 | } | |
264c6eea MS |
335 | } |
336 | } | |
9c9d5d4b AE |
337 | |
338 | /** | |
339 | * Forces visitors to log-in themselves to access the site. | |
340 | */ | |
341 | protected function forceLogin() { | |
342 | $allowedControllers = [ | |
343 | DisclaimerForm::class, | |
344 | EmailActivationForm::class, | |
345 | EmailNewActivationCodeForm::class, | |
346 | LoginForm::class, | |
347 | LostPasswordForm::class, | |
348 | NewPasswordForm::class, | |
349 | RegisterActivationForm::class, | |
350 | RegisterForm::class, | |
351 | RegisterNewActivationCodeForm::class | |
352 | ]; | |
353 | if (in_array(get_class($this), $allowedControllers)) { | |
354 | // controller is allowed | |
355 | return; | |
356 | } | |
357 | ||
358 | if (WCF::getActiveRequest()->isAvailableDuringOfflineMode()) { | |
359 | // allow access to those pages that should be always available | |
360 | return; | |
361 | } | |
362 | ||
363 | // force redirect to login form | |
364 | WCF::getSession()->register('__wsc_forceLoginRedirect', true); | |
365 | HeaderUtil::redirect( | |
366 | LinkHandler::getInstance()->getLink('Login', [ | |
367 | 'url' => WCF::getRequestURI() | |
368 | ]) | |
369 | ); | |
370 | exit; | |
371 | } | |
dcb3a44c | 372 | } |