Completed
Branch newinternal (e32466)
by Simon
03:39
created

PageBase::setTokenManager()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 *                                                                            *
5
 * All code in this file is released into the public domain by the ACC        *
6
 * Development Team. Please see team.json for a list of contributors.         *
7
 ******************************************************************************/
8
9
namespace Waca\Tasks;
10
11
use Exception;
12
use Waca\DataObjects\SiteNotice;
13
use Waca\DataObjects\User;
14
use Waca\Exceptions\ApplicationLogicException;
15
use Waca\Exceptions\OptimisticLockFailedException;
16
use Waca\Fragments\TemplateOutput;
17
use Waca\Security\TokenManager;
18
use Waca\SessionAlert;
19
use Waca\WebRequest;
20
21
abstract class PageBase extends TaskBase implements IRoutedTask
22
{
23
	use TemplateOutput;
24
	/** @var string Smarty template to display */
25
	protected $template = "base.tpl";
26
	/** @var string HTML title. Currently unused. */
27
	protected $htmlTitle;
28
	/** @var bool Determines if the page is a redirect or not */
29
	protected $isRedirecting = false;
30
	/** @var array Queue of headers to be sent on successful completion */
31
	protected $headerQueue = array();
32
	/** @var string The name of the route to use, as determined by the request router. */
33
	private $routeName = null;
34
	/** @var TokenManager */
35
	protected $tokenManager;
36
37
	/**
38
	 * Sets the route the request will take. Only should be called from the request router or barrier test.
39
	 *
40
	 * @param string $routeName        The name of the route
41
	 * @param bool   $skipCallableTest Don't use this unless you know what you're doing, and what the implications are.
42
	 *
43
	 * @throws Exception
44
	 * @category Security-Critical
45
	 */
46 5
	final public function setRoute($routeName, $skipCallableTest = false)
47
	{
48
		// Test the new route is callable before adopting it.
49 5
		if (!$skipCallableTest && !is_callable(array($this, $routeName))) {
50
			throw new Exception("Proposed route '$routeName' is not callable.");
51
		}
52
53
		// Adopt the new route
54 5
		$this->routeName = $routeName;
55 5
	}
56
57
	/**
58
	 * Gets the name of the route that has been passed from the request router.
59
	 * @return string
60
	 */
61 5
	final public function getRouteName()
62
	{
63 5
		return $this->routeName;
64
	}
65
66
	/**
67
	 * Performs generic page setup actions
68
	 */
69
	final protected function setupPage()
70
	{
71
		$this->setUpSmarty();
72
73
		$siteNoticeText = SiteNotice::get($this->getDatabase());
74
75
		$this->assign('siteNoticeText', $siteNoticeText);
76
77
		$currentUser = User::getCurrent($this->getDatabase());
78
		$this->assign('currentUser', $currentUser);
79
		$this->assign('loggedIn', (!$currentUser->isCommunityUser()));
80
	}
81
82
	/**
83
	 * Runs the page logic as routed by the RequestRouter
84
	 *
85
	 * Only should be called after a security barrier! That means only from execute().
86
	 */
87
	final protected function runPage()
88
	{
89
		$database = $this->getDatabase();
90
91
		// initialise a database transaction
92
		if (!$database->beginTransaction()) {
93
			throw new Exception('Failed to start transaction on primary database.');
94
		}
95
96
		try {
97
			// run the page code
98
			$this->{$this->getRouteName()}();
99
100
			$database->commit();
101
		}
102
		catch (ApplicationLogicException $ex) {
103
			// it's an application logic exception, so nothing went seriously wrong with the site. We can use the
104
			// standard templating system for this.
105
106
			// Firstly, let's undo anything that happened to the database.
107
			$database->rollBack();
108
109
			// Reset smarty
110
			$this->setUpSmarty();
111
112
			// Set the template
113
			$this->setTemplate('exception/application-logic.tpl');
114
			$this->assign('message', $ex->getMessage());
115
116
			// Force this back to false
117
			$this->isRedirecting = false;
118
			$this->headerQueue = array();
119
		}
120
		catch (OptimisticLockFailedException $ex) {
121
			// it's an optimistic lock failure exception, so nothing went seriously wrong with the site. We can use the
122
			// standard templating system for this.
123
124
			// Firstly, let's undo anything that happened to the database.
125
			$database->rollBack();
126
127
			// Reset smarty
128
			$this->setUpSmarty();
129
130
			// Set the template
131
			$this->setTemplate('exception/optimistic-lock-failure.tpl');
132
			$this->assign('message', $ex->getMessage());
133
134
			// Force this back to false
135
			$this->isRedirecting = false;
136
			$this->headerQueue = array();
137
		}
138
		finally {
139
			// Catch any hanging on transactions
140
			if ($database->hasActiveTransaction()) {
141
				$database->rollBack();
142
			}
143
		}
144
145
		// run any finalisation code needed before we send the output to the browser.
146
		$this->finalisePage();
147
148
		// Send the headers
149
		$this->sendResponseHeaders();
150
151
		// Check we have a template to use!
152
		if ($this->template !== null) {
153
			$content = $this->fetchTemplate($this->template);
154
			ob_clean();
155
			print($content);
156
			ob_flush();
157
158
			return;
159
		}
160
	}
161
162
	/**
163
	 * Performs final tasks needed before rendering the page.
164
	 */
165
	protected function finalisePage()
166
	{
167
		if ($this->isRedirecting) {
168
			$this->template = null;
169
170
			return;
171
		}
172
173
		// as new users can actually stay logged in
174
		if (User::getCurrent($this->getDatabase())->isNew()) {
175
			$registeredSuccessfully = new SessionAlert(
176
				'Your request will be reviewed soon by a tool administrator, and you\'ll get an email informing you of the decision. You won\'t be able to access most of the tool until then.',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 180 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
177
				'Account Requested!', 'alert-success', false);
178
			SessionAlert::append($registeredSuccessfully);
179
		}
180
181
		// If we're actually displaying content, we want to add the session alerts here!
182
		$this->assign('alerts', SessionAlert::getAlerts());
183
		SessionAlert::clearAlerts();
184
185
		$this->assign('htmlTitle', $this->htmlTitle);
186
	}
187
188
	/**
189
	 * @return TokenManager
190
	 */
191
	public function getTokenManager()
192
	{
193
		return $this->tokenManager;
194
	}
195
196
	/**
197
	 * @param TokenManager $tokenManager
198
	 */
199
	public function setTokenManager($tokenManager)
200
	{
201
		$this->tokenManager = $tokenManager;
202
	}
203
204
	/**
205
	 * Sends the redirect headers to perform a GET at the destination page.
206
	 *
207
	 * Also nullifies the set template so Smarty does not render it.
208
	 *
209
	 * @param string      $page   The page to redirect requests to (as used in the UR)
210
	 * @param null|string $action The action to use on the page.
211
	 * @param null|array  $parameters
212
	 */
213
	final protected function redirect($page = '', $action = null, $parameters = null)
214
	{
215
		$pathInfo = array(WebRequest::scriptName());
216
217
		$pathInfo[1] = $page;
218
219
		if ($action !== null) {
220
			$pathInfo[2] = $action;
221
		}
222
223
		$url = implode('/', $pathInfo);
224
225
		if (is_array($parameters) && count($parameters) > 0) {
226
			$url .= '?' . http_build_query($parameters);
227
		}
228
229
		$this->redirectUrl($url);
230
	}
231
232
	/**
233
	 * Sends the redirect headers to perform a GET at the new address.
234
	 *
235
	 * Also nullifies the set template so Smarty does not render it.
236
	 *
237
	 * @param string $path URL to redirect to
238
	 */
239
	final protected function redirectUrl($path)
240
	{
241
		// 303 See Other = re-request at new address with a GET.
242
		$this->headerQueue[] = 'HTTP/1.1 303 See Other';
243
		$this->headerQueue[] = "Location: $path";
244
245
		$this->setTemplate(null);
246
		$this->isRedirecting = true;
247
	}
248
249
	/**
250
	 * Sets the name of the template this page should display.
251
	 *
252
	 * @param string $name
253
	 *
254
	 * @throws Exception
255
	 */
256
	final protected function setTemplate($name)
257
	{
258
		if ($this->isRedirecting) {
259
			throw new Exception('This page has been set as a redirect, no template can be displayed!');
260
		}
261
262
		$this->template = $name;
263
	}
264
265
	/**
266
	 * Main function for this page, when no specific actions are called.
267
	 * @return void
268
	 */
269
	abstract protected function main();
270
271
	/**
272
	 * @param string $title
273
	 */
274
	final protected function setHtmlTitle($title)
275
	{
276
		$this->htmlTitle = $title;
277
	}
278
279
	public function execute()
280
	{
281
		if ($this->getRouteName() === null) {
282
			throw new Exception('Request is unrouted.');
283
		}
284
285
		if ($this->getSiteConfiguration() === null) {
286
			throw new Exception('Page has no configuration!');
287
		}
288
289
		$this->setupPage();
290
291
		$this->runPage();
292
	}
293
294
	public function assignCSRFToken()
295
	{
296
		$token = $this->tokenManager->getNewToken();
297
		$this->assign('csrfTokenData', $token->getTokenData());
298
	}
299
300
	public function validateCSRFToken()
301
	{
302
		if (!$this->tokenManager->validateToken(WebRequest::postString('csrfTokenData'))) {
303
			throw new ApplicationLogicException('Form token is not valid, please reload and try again');
304
		}
305
	}
306
307
	protected function sendResponseHeaders()
308
	{
309
		foreach ($this->headerQueue as $item) {
310
			if (mb_strpos($item, "\r") !== false || mb_strpos($item, "\n") !== false) {
311
				// Oops. We're not allowed to do this.
312
				throw new Exception('Unable to split header');
313
			}
314
315
			header($item);
316
		}
317
	}
318
}