Merge branch '3.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / page / AbstractPage.class.php
CommitLineData
158bd3ca
TD
1<?php
2namespace wcf\page;
9c9d5d4b
AE
3use wcf\form\DisclaimerForm;
4use wcf\form\EmailActivationForm;
5use wcf\form\EmailNewActivationCodeForm;
6use wcf\form\LoginForm;
7use wcf\form\LostPasswordForm;
8use wcf\form\NewPasswordForm;
9use wcf\form\RegisterActivationForm;
10use wcf\form\RegisterForm;
11use wcf\form\RegisterNewActivationCodeForm;
158bd3ca 12use wcf\system\event\EventHandler;
d82d508e 13use wcf\system\exception\IllegalLinkException;
dc013e39 14use wcf\system\exception\PermissionDeniedException;
264c6eea 15use wcf\system\menu\acp\ACPMenu;
9c9d5d4b 16use wcf\system\request\LinkHandler;
264c6eea 17use wcf\system\request\RequestHandler;
d82d508e 18use wcf\system\WCF;
909b697f 19use wcf\util\HeaderUtil;
7d878d79 20use wcf\util\StringUtil;
27930682 21use 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 36abstract 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}