Completed
Branch newinternal (ffe884)
by Simon
04:07
created

PageBase::execute()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 14
ccs 0
cts 8
cp 0
rs 9.4285
cc 3
eloc 7
nc 3
nop 0
crap 12
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.
39
	 *
40
	 * @param $routeName string
41
	 *
42
	 * @throws Exception
43
	 * @category Security-Critical
44
	 */
45 5
	final public function setRoute($routeName)
46
	{
47
		// Test the new route is callable before adopting it.
48 5
		if (!is_callable(array($this, $routeName))) {
49
			throw new Exception("Proposed route '$routeName' is not callable.");
50
		}
51
52
		// Adopt the new route
53 5
		$this->routeName = $routeName;
54 5
	}
55
56
	/**
57
	 * Gets the name of the route that has been passed from the request router.
58
	 * @return string
59
	 */
60 5
	final public function getRouteName()
61
	{
62 5
		return $this->routeName;
63
	}
64
65
	/**
66
	 * Performs generic page setup actions
67
	 */
68
	final protected function setupPage()
69
	{
70
		$this->setUpSmarty();
71
72
		$siteNoticeText = SiteNotice::get($this->getDatabase());
73
74
		$this->assign('siteNoticeText', $siteNoticeText);
75
76
		$currentUser = User::getCurrent($this->getDatabase());
77
		$this->assign('currentUser', $currentUser);
78
		$this->assign('loggedIn', (!$currentUser->isCommunityUser()));
79
	}
80
81
	/**
82
	 * Runs the page logic as routed by the RequestRouter
83
	 *
84
	 * Only should be called after a security barrier! That means only from execute().
85
	 */
86
	final protected function runPage()
87
	{
88
		$database = $this->getDatabase();
89
90
		// initialise a database transaction
91
		if (!$database->beginTransaction()) {
92
			throw new Exception('Failed to start transaction on primary database.');
93
		}
94
95
		try {
96
			// run the page code
97
			$this->{$this->getRouteName()}();
98
99
			$database->commit();
100
		}
101
		catch (ApplicationLogicException $ex) {
102
			// it's an application logic exception, so nothing went seriously wrong with the site. We can use the
103
			// standard templating system for this.
104
105
			// Firstly, let's undo anything that happened to the database.
106
			$database->rollBack();
107
108
			// Reset smarty
109
			$this->setUpSmarty();
110
111
			// Set the template
112
			$this->setTemplate('exception/application-logic.tpl');
113
			$this->assign('message', $ex->getMessage());
114
115
			// Force this back to false
116
			$this->isRedirecting = false;
117
			$this->headerQueue = array();
118
		}
119
		catch (OptimisticLockFailedException $ex) {
120
			// it's an optimistic lock failure exception, so nothing went seriously wrong with the site. We can use the
121
			// standard templating system for this.
122
123
			// Firstly, let's undo anything that happened to the database.
124
			$database->rollBack();
125
126
			// Reset smarty
127
			$this->setUpSmarty();
128
129
			// Set the template
130
			$this->setTemplate('exception/optimistic-lock-failure.tpl');
131
			$this->assign('message', $ex->getMessage());
132
133
			// Force this back to false
134
			$this->isRedirecting = false;
135
			$this->headerQueue = array();
136
		}
137
		finally {
138
			// Catch any hanging on transactions
139
			if ($database->hasActiveTransaction()) {
140
				$database->rollBack();
141
			}
142
		}
143
144
		// run any finalisation code needed before we send the output to the browser.
145
		$this->finalisePage();
146
147
		// Send the headers
148
		$this->sendResponseHeaders();
149
150
		// Check we have a template to use!
151
		if ($this->template !== null) {
152
			$content = $this->fetchTemplate($this->template);
153
			ob_clean();
154
			print($content);
155
			ob_flush();
156
157
			return;
158
		}
159
	}
160
161
	/**
162
	 * Performs final tasks needed before rendering the page.
163
	 */
164
	protected function finalisePage()
165
	{
166
		if ($this->isRedirecting) {
167
			$this->template = null;
168
169
			return;
170
		}
171
172
		// as new users can actually stay logged in
173
		if (User::getCurrent($this->getDatabase())->isNew()) {
174
			$registeredSuccessfully = new SessionAlert(
175
				'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...
176
				'Account Requested!', 'alert-success', false);
177
			SessionAlert::append($registeredSuccessfully);
178
		}
179
180
		// If we're actually displaying content, we want to add the session alerts here!
181
		$this->assign('alerts', SessionAlert::getAlerts());
182
		SessionAlert::clearAlerts();
183
184
		$this->assign('htmlTitle', $this->htmlTitle);
185
	}
186
187
	/**
188
	 * @return TokenManager
189
	 */
190
	public function getTokenManager()
191
	{
192
		return $this->tokenManager;
193
	}
194
195
	/**
196
	 * @param TokenManager $tokenManager
197
	 */
198
	public function setTokenManager($tokenManager)
199
	{
200
		$this->tokenManager = $tokenManager;
201
	}
202
203
	/**
204
	 * Sends the redirect headers to perform a GET at the destination page.
205
	 *
206
	 * Also nullifies the set template so Smarty does not render it.
207
	 *
208
	 * @param string      $page   The page to redirect requests to (as used in the UR)
209
	 * @param null|string $action The action to use on the page.
210
	 * @param null|array  $parameters
211
	 */
212
	final protected function redirect($page = '', $action = null, $parameters = null)
213
	{
214
		$pathInfo = array(WebRequest::scriptName());
215
216
		$pathInfo[1] = $page;
217
218
		if ($action !== null) {
219
			$pathInfo[2] = $action;
220
		}
221
222
		$url = implode('/', $pathInfo);
223
224
		if (is_array($parameters) && count($parameters) > 0) {
225
			$url .= '?' . http_build_query($parameters);
226
		}
227
228
		$this->redirectUrl($url);
229
	}
230
231
	/**
232
	 * Sends the redirect headers to perform a GET at the new address.
233
	 *
234
	 * Also nullifies the set template so Smarty does not render it.
235
	 *
236
	 * @param string $path URL to redirect to
237
	 */
238
	final protected function redirectUrl($path)
239
	{
240
		// 303 See Other = re-request at new address with a GET.
241
		$this->headerQueue[] = 'HTTP/1.1 303 See Other';
242
		$this->headerQueue[] = "Location: $path";
243
244
		$this->setTemplate(null);
245
		$this->isRedirecting = true;
246
	}
247
248
	/**
249
	 * Sets the name of the template this page should display.
250
	 *
251
	 * @param string $name
252
	 *
253
	 * @throws Exception
254
	 */
255
	final protected function setTemplate($name)
256
	{
257
		if ($this->isRedirecting) {
258
			throw new Exception('This page has been set as a redirect, no template can be displayed!');
259
		}
260
261
		$this->template = $name;
262
	}
263
264
	/**
265
	 * Main function for this page, when no specific actions are called.
266
	 * @return void
267
	 */
268
	abstract protected function main();
269
270
	/**
271
	 * @param string $title
272
	 */
273
	final protected function setHtmlTitle($title)
274
	{
275
		$this->htmlTitle = $title;
276
	}
277
278
	public function execute()
279
	{
280
		if ($this->getRouteName() === null) {
281
			throw new Exception('Request is unrouted.');
282
		}
283
284
		if ($this->getSiteConfiguration() === null) {
285
			throw new Exception('Page has no configuration!');
286
		}
287
288
		$this->setupPage();
289
290
		$this->runPage();
291
	}
292
293
	public function assignCSRFToken()
294
	{
295
		$token = $this->tokenManager->getNewToken();
296
		$this->assign('csrfTokenData', $token->getTokenData());
297
	}
298
299
	public function validateCSRFToken()
300
	{
301
		if (!$this->tokenManager->validateToken(WebRequest::postString('csrfTokenData'))) {
302
			throw new ApplicationLogicException('Form token is not valid, please reload and try again');
303
		}
304
	}
305
306
	protected function sendResponseHeaders()
307
	{
308
		foreach ($this->headerQueue as $item) {
309
			if (mb_strpos($item, "\r") !== false || mb_strpos($item, "\n") !== false) {
310
				// Oops. We're not allowed to do this.
311
				throw new Exception('Unable to split header');
312
			}
313
314
			header($item);
315
		}
316
	}
317
}