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

Issues (412)

src/lib/Smr/Session.php (4 issues)

1
<?php declare(strict_types=1);
2
3
namespace Smr;
4
5
use AbstractSmrPlayer;
6
use Smr\Container\DiContainer;
7
use Smr\Exceptions\UserError;
8
use Smr\Page\Page;
9
use Smr\Pages\Player\AttackPlayerProcessor;
10
use Smr\Pages\Player\ExamineTrader;
11
use Smr\Pages\Player\ForcesDrop;
12
use Smr\Pages\Player\ForcesDropProcessor;
13
use Smr\Pages\Player\ForcesRefreshProcessor;
14
use Smr\Pages\Player\HardwareConfigure;
15
use Smr\Pages\Player\SectorJumpProcessor;
16
use Smr\Pages\Player\SectorMoveProcessor;
17
use Smr\Pages\Player\SectorScan;
18
use Smr\Pages\Player\ShopGoodsProcessor;
19
use SmrAccount;
20
use SmrPlayer;
21
22
class Session {
23
24
	private const TIME_BEFORE_EXPIRY = 172800; // 2 days
25
26
	private const URL_LOAD_DELAY = [
27
		HardwareConfigure::class => .4,
28
		ForcesDrop::class => .4,
29
		ForcesDropProcessor::class => .5,
30
		ForcesRefreshProcessor::class => .4,
31
		SectorJumpProcessor::class => .4,
32
		SectorMoveProcessor::class => .4,
33
		SectorScan::class => .4,
34
		ShopGoodsProcessor::class => .4,
35
		AttackPlayerProcessor::class => .75,
36
		ExamineTrader::class => .75,
37
	];
38
39
	protected Database $db;
40
41
	private string $sessionID;
42
	private int $gameID;
43
	/** @var array<string, Page> */
44
	private array $links = [];
45
	private ?Page $currentPage = null;
46
	/** @var array<string, mixed> */
47
	private array $requestData = [];
48
	private bool $generate;
49
	public readonly bool $ajax;
50
	private string $SN;
51
	private string $lastSN;
52
	private int $accountID;
53
	private float $lastAccessed;
54
55
	/** @var ?array<string, string> */
56
	protected ?array $previousAjaxReturns;
57
	/** @var array<string, string> */
58
	protected array $ajaxReturns = [];
59
60
	/**
61
	 * Return the Smr\Session in the DI container.
62
	 * If one does not exist yet, it will be created.
63
	 * This is the intended way to construct this class.
64
	 */
65
	public static function getInstance(): self {
66
		return DiContainer::get(self::class);
67
	}
68
69
	/**
70
	 * Smr\Session constructor.
71
	 * Not intended to be constructed by hand. Use Smr\Session::getInstance().
72
	 */
73
	public function __construct() {
74
		// Initialize the db connector here
75
		$this->db = Database::getInstance();
76
77
		// now try the cookie
78
		$idLength = 32;
79
		if (isset($_COOKIE['session_id']) && strlen($_COOKIE['session_id']) === $idLength) {
80
			$this->sessionID = $_COOKIE['session_id'];
81
		} else {
82
			// create a new session id
83
			do {
84
				$this->sessionID = random_string($idLength);
0 ignored issues
show
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

84
				$this->sessionID = /** @scrutinizer ignore-call */ random_string($idLength);
Loading history...
85
				$dbResult = $this->db->read('SELECT 1 FROM active_session WHERE session_id = ' . $this->db->escapeString($this->sessionID));
86
			} while ($dbResult->hasRecord()); //Make sure we haven't somehow clashed with someone else's session.
87
88
			// This is a minor hack to make sure that setcookie is not called
89
			// for CLI programs and tests (to avoid "headers already sent").
90
			if (headers_sent() === false) {
91
				setcookie('session_id', $this->sessionID);
92
			}
93
		}
94
95
		// Delete any expired sessions
96
		$this->db->write('DELETE FROM active_session WHERE last_accessed < ' . $this->db->escapeNumber(time() - self::TIME_BEFORE_EXPIRY));
97
98
		// try to get current session
99
		$this->ajax = Request::getInt('ajax', 0) === 1;
0 ignored issues
show
The property ajax is declared read-only in Smr\Session.
Loading history...
100
		$this->SN = Request::get('sn', '');
101
		$this->fetchVarInfo();
102
103
		if (!$this->ajax && $this->hasCurrentVar()) {
104
			$file = $this->getCurrentVar()::class;
105
			$loadDelay = self::URL_LOAD_DELAY[$file] ?? 0;
106
			$timeBetweenLoads = microtime(true) - $this->lastAccessed;
107
			if ($timeBetweenLoads < $loadDelay) {
108
				$sleepTime = IRound(($loadDelay - $timeBetweenLoads) * 1000000);
0 ignored issues
show
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

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

348
			$sn = /** @scrutinizer ignore-call */ random_alphabetic_string(6);
Loading history...
349
		} while (isset($this->links[$sn]) && $sn === $this->SN);
350
		$this->links[$sn] = $container;
351
		return $sn;
352
	}
353
354
	public function addAjaxReturns(string $element, string $contents): bool {
355
		$this->ajaxReturns[$element] = $contents;
356
		return isset($this->previousAjaxReturns[$element]) && $this->previousAjaxReturns[$element] == $contents;
357
	}
358
359
	public function saveAjaxReturns(): void {
360
		if (empty($this->ajaxReturns)) {
361
			return;
362
		}
363
		$this->db->write('UPDATE active_session SET ajax_returns=' . $this->db->escapeObject($this->ajaxReturns, true) .
364
				' WHERE session_id=' . $this->db->escapeString($this->sessionID));
365
	}
366
367
}
368