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 (#1473)
by Dan
04:50
created

Session::fetchVarInfo()   B

Complexity

Conditions 8
Paths 25

Size

Total Lines 36
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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

71
				$this->sessionID = /** @scrutinizer ignore-call */ random_string($idLength);
Loading history...
72
				$dbResult = $this->db->read('SELECT 1 FROM active_session WHERE session_id = ' . $this->db->escapeString($this->sessionID));
73
			} while ($dbResult->hasRecord()); //Make sure we haven't somehow clashed with someone else's session.
74
75
			// This is a minor hack to make sure that setcookie is not called
76
			// for CLI programs and tests (to avoid "headers already sent").
77
			if (headers_sent() === false) {
78
				setcookie('session_id', $this->sessionID);
79
			}
80
		}
81
82
		// Delete any expired sessions
83
		$this->db->write('DELETE FROM active_session WHERE last_accessed < ' . $this->db->escapeNumber(time() - self::TIME_BEFORE_EXPIRY));
84
85
		// try to get current session
86
		$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...
87
		$this->SN = Request::get('sn', '');
88
		$this->fetchVarInfo();
89
90
		if (!$this->ajax && $this->hasCurrentVar()) {
91
			$file = $this->getCurrentVar()->file;
92
			$loadDelay = self::URL_LOAD_DELAY[$file] ?? 0;
93
			$timeBetweenLoads = microtime(true) - $this->lastAccessed;
94
			if ($timeBetweenLoads < $loadDelay) {
95
				$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

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

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