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
Push — main ( 623909...273242 )
by Dan
28s queued 19s
created

src/lib/Smr/Session.php (1 issue)

Labels
Severity
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);
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
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);
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);
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