Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Failed Conditions
Pull Request — main (#1487)
by Dan
06:01
created

Session::getSN()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Smr;
4
5
use AbstractSmrPlayer;
6
use Page;
7
use Smr\Container\DiContainer;
8
use Smr\Exceptions\UserError;
9
use SmrAccount;
10
use SmrPlayer;
11
12
class Session {
13
14
	private const TIME_BEFORE_EXPIRY = 172800; // 2 days
15
16
	private const URL_LOAD_DELAY = [
17
		'configure_hardware.php' => .4,
18
		'forces_drop.php' => .4,
19
		'forces_drop_processing.php' => .5,
20
		'forces_refresh_processing.php' => .4,
21
		'sector_jump_processing.php' => .4,
22
		'sector_move_processing.php' => .4,
23
		'sector_scan.php' => .4,
24
		'shop_goods_processing.php' => .4,
25
		'trader_attack_processing.php' => .75,
26
		'trader_examine.php' => .75,
27
	];
28
29
	protected Database $db;
30
31
	private string $sessionID;
32
	private int $gameID;
33
	/** @var array<string, Page> */
34
	private array $links = [];
35
	private ?Page $currentPage = null;
36
	private bool $generate;
37
	public readonly bool $ajax;
38
	private string $SN;
39
	private string $lastSN;
40
	private int $accountID;
41
	private float $lastAccessed;
42
43
	/** @var ?array<string, string> */
44
	protected ?array $previousAjaxReturns;
45
	/** @var array<string, string> */
46
	protected array $ajaxReturns = [];
47
48
	/**
49
	 * Return the Smr\Session in the DI container.
50
	 * If one does not exist yet, it will be created.
51
	 * This is the intended way to construct this class.
52
	 */
53
	public static function getInstance(): self {
54
		return DiContainer::get(self::class);
55
	}
56
57
	/**
58
	 * Smr\Session constructor.
59
	 * Not intended to be constructed by hand. Use Smr\Session::getInstance().
60
	 */
61
	public function __construct() {
62
		// Initialize the db connector here
63
		$this->db = Database::getInstance();
64
65
		// now try the cookie
66
		$idLength = 32;
67
		if (isset($_COOKIE['session_id']) && strlen($_COOKIE['session_id']) === $idLength) {
68
			$this->sessionID = $_COOKIE['session_id'];
69
		} else {
70
			// create a new session id
71
			do {
72
				$this->sessionID = random_string($idLength);
0 ignored issues
show
Bug introduced by
The function random_string was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

72
				$this->sessionID = /** @scrutinizer ignore-call */ random_string($idLength);
Loading history...
73
				$dbResult = $this->db->read('SELECT 1 FROM active_session WHERE session_id = ' . $this->db->escapeString($this->sessionID));
74
			} while ($dbResult->hasRecord()); //Make sure we haven't somehow clashed with someone else's session.
75
76
			// This is a minor hack to make sure that setcookie is not called
77
			// for CLI programs and tests (to avoid "headers already sent").
78
			if (headers_sent() === false) {
79
				setcookie('session_id', $this->sessionID);
80
			}
81
		}
82
83
		// Delete any expired sessions
84
		$this->db->write('DELETE FROM active_session WHERE last_accessed < ' . $this->db->escapeNumber(time() - self::TIME_BEFORE_EXPIRY));
85
86
		// try to get current session
87
		$this->ajax = Request::getInt('ajax', 0) === 1;
0 ignored issues
show
Bug introduced by
The property ajax is declared read-only in Smr\Session.
Loading history...
88
		$this->SN = Request::get('sn', '');
89
		$this->fetchVarInfo();
90
91
		if (!$this->ajax && $this->hasCurrentVar()) {
92
			$file = $this->getCurrentVar()->file;
93
			$loadDelay = self::URL_LOAD_DELAY[$file] ?? 0;
94
			$timeBetweenLoads = microtime(true) - $this->lastAccessed;
95
			if ($timeBetweenLoads < $loadDelay) {
96
				$sleepTime = IRound(($loadDelay - $timeBetweenLoads) * 1000000);
0 ignored issues
show
Bug introduced by
The function IRound was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

96
				$sleepTime = /** @scrutinizer ignore-call */ IRound(($loadDelay - $timeBetweenLoads) * 1000000);
Loading history...
97
				//echo 'Sleeping for: ' . $sleepTime . 'us';
98
				usleep($sleepTime);
99
			}
100
			if (ENABLE_DEBUG) {
101
				$this->db->insert('debug', [
102
					'debug_type' => $this->db->escapeString('Delay: ' . $file),
103
					'account_id' => $this->db->escapeNumber($this->accountID),
104
					'value' => $this->db->escapeNumber($timeBetweenLoads),
105
				]);
106
			}
107
		}
108
	}
109
110
	public function fetchVarInfo(): void {
111
		$dbResult = $this->db->read('SELECT * FROM active_session WHERE session_id = ' . $this->db->escapeString($this->sessionID));
112
		if ($dbResult->hasRecord()) {
113
			$dbRecord = $dbResult->record();
114
			$this->generate = false;
115
			$this->sessionID = $dbRecord->getString('session_id');
116
			$this->accountID = $dbRecord->getInt('account_id');
117
			$this->gameID = $dbRecord->getInt('game_id');
118
			$this->lastAccessed = $dbRecord->getFloat('last_accessed');
119
			$this->lastSN = $dbRecord->getString('last_sn');
120
			// We may not have ajax_returns if ajax was disabled
121
			$this->previousAjaxReturns = $dbRecord->getObject('ajax_returns', true, true);
122
123
			[$this->links, $lastPage] = $dbRecord->getObject('session_var', true);
124
125
			$ajaxRefresh = $this->ajax && !$this->hasChangedSN();
126
			if ($ajaxRefresh) {
127
				$this->currentPage = $lastPage;
128
			} elseif (isset($this->links[$this->SN])) {
129
				// If the current page is modified during page processing, we need
130
				// to make sure the original link is unchanged. So we clone it here.
131
				$this->currentPage = clone $this->links[$this->SN];
132
133
				// If SN changes during an ajax update, it is an error unless user is
134
				// requesting a page that is allowed to be executed in an ajax call.
135
				$allowAjax = $this->currentPage['AJAX'] ?? false;
136
				if (!$allowAjax && $this->ajax && $this->hasChangedSN()) {
137
					throw new UserError('The previous page failed to auto-refresh properly!');
138
				}
139
			}
140
141
			if (!$ajaxRefresh) { // since form pages don't ajax refresh properly
142
				foreach ($this->links as $sn => $link) {
143
					if (!$link->reusable) {
144
						// This link is no longer valid
145
						unset($this->links[$sn]);
146
					}
147
				}
148
			}
149
		} else {
150
			$this->generate = true;
151
			$this->accountID = 0;
152
			$this->gameID = 0;
153
		}
154
	}
155
156
	public function update(): void {
157
		$sessionVar = [$this->links, $this->currentPage];
158
		if (!$this->generate) {
159
			$this->db->write('UPDATE active_session SET account_id=' . $this->db->escapeNumber($this->accountID) . ',game_id=' . $this->db->escapeNumber($this->gameID) . (!$this->ajax ? ',last_accessed=' . $this->db->escapeNumber(Epoch::microtime()) : '') . ',session_var=' . $this->db->escapeObject($sessionVar, true) .
160
					',last_sn=' . $this->db->escapeString($this->SN) .
161
					' WHERE session_id=' . $this->db->escapeString($this->sessionID) . ($this->ajax ? ' AND last_sn=' . $this->db->escapeString($this->lastSN) : ''));
162
		} else {
163
			$this->db->write('DELETE FROM active_session WHERE account_id = ' . $this->db->escapeNumber($this->accountID) . ' AND game_id = ' . $this->db->escapeNumber($this->gameID));
164
			$this->db->insert('active_session', [
165
				'session_id' => $this->db->escapeString($this->sessionID),
166
				'account_id' => $this->db->escapeNumber($this->accountID),
167
				'game_id' => $this->db->escapeNumber($this->gameID),
168
				'last_accessed' => $this->db->escapeNumber(Epoch::microtime()),
169
				'session_var' => $this->db->escapeObject($sessionVar, true),
170
			]);
171
			$this->generate = false;
172
		}
173
	}
174
175
	/**
176
	 * Uniquely identifies the session in the database.
177
	 */
178
	public function getSessionID(): string {
179
		return $this->sessionID;
180
	}
181
182
	/**
183
	 * Returns the Game ID associated with the session.
184
	 */
185
	public function getGameID(): int {
186
		return $this->gameID;
187
	}
188
189
	/**
190
	 * Returns true if the session is inside a game, false otherwise.
191
	 */
192
	public function hasGame(): bool {
193
		return $this->gameID != 0;
194
	}
195
196
	public function hasAccount(): bool {
197
		return $this->accountID > 0;
198
	}
199
200
	public function getAccountID(): int {
201
		return $this->accountID;
202
	}
203
204
	public function getAccount(): SmrAccount {
205
		return SmrAccount::getAccount($this->accountID);
206
	}
207
208
	public function getPlayer(bool $forceUpdate = false): AbstractSmrPlayer {
209
		return SmrPlayer::getPlayer($this->accountID, $this->gameID, $forceUpdate);
210
	}
211
212
	/**
213
	 * Sets the `accountID` attribute of this session.
214
	 */
215
	public function setAccount(SmrAccount $account): void {
216
		$this->accountID = $account->getAccountID();
217
	}
218
219
	/**
220
	 * Updates the `gameID` attribute of the session and deletes any other
221
	 * active sessions in this game for this account.
222
	 */
223
	public function updateGame(int $gameID): void {
224
		if ($this->gameID == $gameID) {
225
			return;
226
		}
227
		$this->gameID = $gameID;
228
		$this->db->write('DELETE FROM active_session WHERE account_id = ' . $this->db->escapeNumber($this->accountID) . ' AND game_id = ' . $this->gameID);
229
		$this->db->write('UPDATE active_session SET game_id=' . $this->db->escapeNumber($this->gameID) . ' WHERE session_id=' . $this->db->escapeString($this->sessionID));
230
	}
231
232
	/**
233
	 * The SN is the URL parameter that defines the page being requested.
234
	 */
235
	public function getSN(): string {
236
		return $this->SN;
237
	}
238
239
	/**
240
	 * Returns true if the current SN is different than the previous SN.
241
	 */
242
	public function hasChangedSN(): bool {
243
		return $this->SN != $this->lastSN;
244
	}
245
246
	public function destroy(): void {
247
		$this->db->write('DELETE FROM active_session WHERE session_id = ' . $this->db->escapeString($this->sessionID));
248
		unset($this->sessionID);
249
		unset($this->accountID);
250
		unset($this->gameID);
251
	}
252
253
	public function getLastAccessed(): float {
254
		return $this->lastAccessed;
255
	}
256
257
	/**
258
	 * Check if the session has a var associated with the current SN.
259
	 */
260
	public function hasCurrentVar(): bool {
261
		return $this->currentPage !== null;
262
	}
263
264
	/**
265
	 * Returns the session var associated with the current SN.
266
	 */
267
	public function getCurrentVar(): Page {
268
		return $this->currentPage;
269
	}
270
271
	/**
272
	 * Gets a var from $var, $_REQUEST, or $default. Then stores it in the
273
	 * session so that it can still be retrieved when the page auto-refreshes.
274
	 * This is the recommended way to get $_REQUEST data for display pages.
275
	 * For processing pages, see the Request class.
276
	 */
277
	public function getRequestVar(string $varName, string $default = null): string {
278
		$result = Request::getVar($varName, $default);
279
		$var = $this->getCurrentVar();
280
		$var[$varName] = $result;
281
		return $result;
282
	}
283
284
	public function getRequestVarInt(string $varName, int $default = null): int {
285
		$result = Request::getVarInt($varName, $default);
286
		$var = $this->getCurrentVar();
287
		$var[$varName] = $result;
288
		return $result;
289
	}
290
291
	/**
292
	 * @param ?array<int> $default
293
	 * @return array<int>
294
	 */
295
	public function getRequestVarIntArray(string $varName, array $default = null): array {
296
		$result = Request::getVarIntArray($varName, $default);
297
		$var = $this->getCurrentVar();
298
		$var[$varName] = $result;
299
		return $result;
300
	}
301
302
	/**
303
	 * Replace the global $var with the given $container.
304
	 */
305
	public function setCurrentVar(Page $container): void {
306
		$this->currentPage = $container;
307
	}
308
309
	public function clearLinks(): void {
310
		$this->links = [];
311
	}
312
313
	/**
314
	 * Add a page to the session so that it can be used on next page load.
315
	 * It will be associated with an SN that will be used for linking.
316
	 */
317
	public function addLink(Page $container): string {
318
		// If we already had a link to this exact page, use the existing SN for it.
319
		foreach ($this->links as $sn => $link) {
320
			if ($container == $link) { // loose equality to compare contents
321
				return $sn;
322
			}
323
		}
324
		// This page isn't an existing link, so give it a new SN.
325
		do {
326
			$sn = random_alphabetic_string(6);
0 ignored issues
show
Bug introduced by
The function random_alphabetic_string was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

326
			$sn = /** @scrutinizer ignore-call */ random_alphabetic_string(6);
Loading history...
327
		} while (isset($this->links[$sn]));
328
		$this->links[$sn] = $container;
329
		return $sn;
330
	}
331
332
	public function addAjaxReturns(string $element, string $contents): bool {
333
		$this->ajaxReturns[$element] = $contents;
334
		return isset($this->previousAjaxReturns[$element]) && $this->previousAjaxReturns[$element] == $contents;
335
	}
336
337
	public function saveAjaxReturns(): void {
338
		if (empty($this->ajaxReturns)) {
339
			return;
340
		}
341
		$this->db->write('UPDATE active_session SET ajax_returns=' . $this->db->escapeObject($this->ajaxReturns, true) .
342
				' WHERE session_id=' . $this->db->escapeString($this->sessionID));
343
	}
344
345
}
346