Completed
Branch newinternal (6027bd)
by Simon
06:03
created

WebStart   B

Complexity

Total Complexity 29

Size/Duplication

Total Lines 309
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 32
Bugs 4 Features 0
Metric Value
wmc 29
c 32
b 4
f 0
lcom 1
cbo 16
dl 0
loc 309
rs 8.4614

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A setupHelpers() 0 23 3
A run() 0 19 4
A exceptionHandler() 0 62 3
A errorHandler() 0 5 1
B setupEnvironment() 0 38 4
B main() 0 29 2
A cleanupEnvironment() 0 9 3
A getExceptionData() 0 13 2
B checkForceLogout() 0 24 4
A isPublic() 0 4 1
A setPublic() 0 4 1
1
<?php
2
namespace Waca;
3
4
use ErrorException;
5
use Exception;
6
use Waca\DataObjects\User;
7
use Waca\Exceptions\EnvironmentException;
8
use Waca\Exceptions\ReadableException;
9
use Waca\Helpers\TypeAheadHelper;
10
use Waca\Providers\GlobalStateProvider;
11
use Waca\Router\IRequestRouter;
12
use Waca\Security\SecurityManager;
13
use Waca\Security\TokenManager;
14
use Waca\Tasks\ITask;
15
use Waca\Tasks\InternalPageBase;
16
use Waca\Tasks\PageBase;
17
18
/**
19
 * Internal application entry point.
20
 *
21
 * @package Waca
22
 */
23
class WebStart extends ApplicationBase
24
{
25
	/**
26
	 * @var IRequestRouter
27
	 */
28
	private $requestRouter;
29
	/** @var bool */
30
	private $isPublic;
31
32
	/**
33
	 * WebStart constructor.
34
	 *
35
	 * @param SiteConfiguration $configuration The site configuration
36
	 * @param IRequestRouter    $router        The request router to use
37
	 */
38
	public function __construct(SiteConfiguration $configuration, IRequestRouter $router)
39
	{
40
		parent::__construct($configuration);
41
42
		$this->requestRouter = $router;
43
	}
44
45
	/**
46
	 * @param ITask             $page
47
	 * @param SiteConfiguration $siteConfiguration
48
	 * @param PdoDatabase       $database
49
	 * @param PdoDatabase       $notificationsDatabase
50
	 *
51
	 * @return void
52
	 */
53
	protected function setupHelpers(
54
		ITask $page,
55
		SiteConfiguration $siteConfiguration,
56
		PdoDatabase $database,
57
		PdoDatabase $notificationsDatabase
58
	) {
59
		parent::setupHelpers($page, $siteConfiguration, $database, $notificationsDatabase);
60
61
		if ($page instanceof PageBase) {
62
			$page->setTokenManager(new TokenManager());
63
64
			if ($page instanceof InternalPageBase) {
65
				$page->setTypeAheadHelper(new TypeAheadHelper());
66
67
				$identificationVerifier = new IdentificationVerifier($page->getHttpHelper(), $siteConfiguration,
68
					$database);
69
				$page->setIdentificationVerifier($identificationVerifier);
70
71
				$page->setSecurityManager(new SecurityManager($identificationVerifier,
72
					$siteConfiguration->getForceIdentification()));
73
			}
74
		}
75
	}
76
77
	/**
78
	 * Application entry point.
79
	 *
80
	 * Sets up the environment and runs the application, performing any global cleanup operations when done.
81
	 */
82
	public function run()
83
	{
84
		try {
85
			if ($this->setupEnvironment()) {
86
				$this->main();
87
			}
88
		}
89
		catch (EnvironmentException $ex) {
90
			ob_end_clean();
91
			print Offline::getOfflineMessage(false, $ex->getMessage());
92
		}
93
		catch (ReadableException $ex) {
94
			ob_end_clean();
95
			print $ex->getReadableError();
96
		}
97
		finally {
98
			$this->cleanupEnvironment();
99
		}
100
	}
101
102
	/**
103
	 * Global exception handler
104
	 *
105
	 * Smarty would be nice to use, but it COULD BE smarty that throws the errors.
106
	 * Let's build something ourselves, and hope it works.
107
	 *
108
	 * @param $exception
109
	 *
110
	 * @category Security-Critical - has the potential to leak data when exception is thrown.
111
	 */
112
	public static function exceptionHandler(Exception $exception)
0 ignored issues
show
Coding Style introduced by
exceptionHandler uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
exceptionHandler uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
exceptionHandler uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
113
	{
114
		/** @global $siteConfiguration SiteConfiguration */
115
		global $siteConfiguration;
116
117
		$errorDocument = <<<HTML
118
<!DOCTYPE html>
119
<html lang="en"><head>
120
<meta charset="utf-8">
121
<title>Oops! Something went wrong!</title>
122
<meta name="viewport" content="width=device-width, initial-scale=1.0">
123
<link href="{$siteConfiguration->getBaseUrl()}/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet">
124
<style>
125
  body {
126
    padding-top: 60px;
127
  }
128
</style>
129
<link href="{$siteConfiguration->getBaseUrl()}/lib/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet">
130
</head><body><div class="container">
131
<h1>Oops! Something went wrong!</h1>
132
<p>We'll work on fixing this for you, so why not come back later?</p>
133
<p class="muted">If our trained monkeys ask, tell them this error ID: <code>$1$</code></p>
134
$2$
135
</div></body></html>
136
HTML;
137
138
		$errorData = self::getExceptionData($exception);
139
		$errorData['server'] = $_SERVER;
140
		$errorData['get'] = $_GET;
141
		$errorData['post'] = $_POST;
142
143
		$state = serialize($errorData);
144
		$errorId = sha1($state);
145
146
		// Save the error for later analysis
147
		file_put_contents($siteConfiguration->getErrorLog() . '/' . $errorId . '.log', $state);
148
149
		// clear and discard any content that's been saved to the output buffer
150
		if (ob_get_level() > 0) {
151
			ob_end_clean();
152
		}
153
154
		// push error ID into the document.
155
		$message = str_replace('$1$', $errorId, $errorDocument);
156
157
		if ($siteConfiguration->getDebuggingTraceEnabled()) {
158
			ob_start();
159
			var_dump($errorData);
1 ignored issue
show
Security Debugging Code introduced by
var_dump($errorData); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
160
			$textErrorData = ob_get_contents();
161
			ob_end_clean();
162
163
			$message = str_replace('$2$', $textErrorData, $message);
164
		}
165
		else {
166
			$message = str_replace('$2$', "", $message);
167
		}
168
169
		header('HTTP/1.1 500 Internal Server Error');
170
171
		// output the document
172
		print $message;
173
	}
174
175
	public static function errorHandler($err_severity, $err_msg, $err_file, $err_line)
176
	{
177
		// call into the main exception handler above
178
		throw new ErrorException($err_msg, 0, $err_severity, $err_file, $err_line);
179
	}
180
181
	/**
182
	 * Environment setup
183
	 *
184
	 * This method initialises the tool environment. If the tool cannot be initialised correctly, it will return false
185
	 * and shut down prematurely.
186
	 *
187
	 * @return bool
188
	 * @throws EnvironmentException
189
	 */
190
	protected function setupEnvironment()
191
	{
192
		// initialise global exception handler
193
		set_exception_handler(array(self::class, 'exceptionHandler'));
194
		set_error_handler(array(self::class, 'errorHandler'), E_RECOVERABLE_ERROR);
195
196
		// start output buffering if necessary
197
		if (ob_get_level() === 0) {
198
			ob_start();
199
		}
200
201
		// initialise super-global providers
202
		WebRequest::setGlobalStateProvider(new GlobalStateProvider());
203
204
		if (Offline::isOffline()) {
205
			print Offline::getOfflineMessage($this->isPublic());
206
			ob_end_flush();
207
208
			return false;
209
		}
210
211
		// Call parent setup
212
		if (!parent::setupEnvironment()) {
213
			return false;
214
		}
215
216
		// Start up sessions
217
		Session::start();
218
219
		// Check the user is allowed to be logged in still. This must be before we call any user-loading functions and
220
		// get the current user cached.
221
		// I'm not sure if this function call being here is particularly a good thing, but it's part of starting up a
222
		// session I suppose.
223
		$this->checkForceLogout();
224
225
		// environment initialised!
226
		return true;
227
	}
228
229
	/**
230
	 * Main application logic
231
	 */
232
	protected function main()
233
	{
234
		// Get the right route for the request
235
		$page = $this->requestRouter->route();
236
237
		$siteConfiguration = $this->getConfiguration();
238
		$database = PdoDatabase::getDatabaseConnection('acc');
239
240
		if ($siteConfiguration->getIrcNotificationsEnabled()) {
241
			$notificationsDatabase = PdoDatabase::getDatabaseConnection('notifications');
242
		}
243
		else {
244
			// @todo federated table here?
245
			$notificationsDatabase = $database;
246
		}
247
248
		$this->setupHelpers($page, $siteConfiguration, $database, $notificationsDatabase);
249
250
		/* @todo Remove this global statement! It's here for User.php, which does far more than it should. */
251
		global $oauthHelper;
252
		$oauthHelper = $page->getOAuthHelper();
253
254
		/* @todo Remove this global statement! It's here for Request.php, which does far more than it should. */
255
		global $globalXffTrustProvider;
256
		$globalXffTrustProvider = $page->getXffTrustProvider();
257
258
		// run the route code for the request.
259
		$page->execute();
260
	}
261
262
	/**
263
	 * Any cleanup tasks should go here
264
	 *
265
	 * Note that we need to be very careful here, as exceptions may have been thrown and handled.
266
	 * This should *only* be for cleaning up, no logic should go here.
267
	 */
268
	protected function cleanupEnvironment()
269
	{
270
		// Clean up anything we splurged after sending the page.
271
		if (ob_get_level() > 0) {
272
			for ($i = ob_get_level(); $i > 0; $i--) {
273
				ob_end_clean();
274
			}
275
		}
276
	}
277
278
	/**
279
	 * @param Exception $exception
280
	 *
281
	 * @return null|array
282
	 */
283
	private static function getExceptionData($exception)
284
	{
285
		if ($exception == null) {
286
			return null;
287
		}
288
289
		return array(
290
			'exception' => get_class($exception),
291
			'message'   => $exception->getMessage(),
292
			'stack'     => $exception->getTraceAsString(),
293
			'previous'  => self::getExceptionData($exception->getPrevious()),
294
		);
295
	}
296
297
	private function checkForceLogout()
298
	{
299
		$database = PdoDatabase::getDatabaseConnection('acc');
300
301
		$sessionUserId = WebRequest::getSessionUserId();
302
		iF ($sessionUserId === null) {
303
			return;
304
		}
305
306
		// Note, User::getCurrent() caches it's result, which we *really* don't want to trigger.
307
		$currentUser = User::getById($sessionUserId, $database);
308
309
		if ($currentUser === false) {
310
			// Umm... this user has a session cookie with a userId set, but no user exists...
311
			Session::restart();
312
		}
313
314
		if ($currentUser->getForceLogout()) {
315
			Session::restart();
316
317
			$currentUser->setForceLogout(false);
318
			$currentUser->save();
319
		}
320
	}
321
322
	public function isPublic()
323
	{
324
		return $this->isPublic;
325
	}
326
327
	public function setPublic($isPublic)
328
	{
329
		$this->isPublic = $isPublic;
330
	}
331
}
332