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 — master (#1034)
by Dan
04:23
created

SmrSession::getLastAccessed()   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
if (!defined('USING_AJAX')) {
4
	define('USING_AJAX', false);
5
}
6
7
class SmrSession {
8
9
	const TIME_BEFORE_EXPIRY = 3600;
10
11
	private const URL_LOAD_DELAY = array(
12
		'configure_hardware.php' => .4,
13
		'forces_drop.php' => .4,
14
		'forces_drop_processing.php' => .5,
15
		'forces_refresh_processing.php' => .4,
16
		'sector_jump_processing.php' => .4,
17
		'sector_move_processing.php' => .4,
18
		'sector_scan.php' => .4,
19
		'shop_goods_processing.php' => .4,
20
		'trader_attack_processing.php' => .75,
21
		'trader_examine.php' => .75
22
	);
23
24
	protected MySqlDatabase $db;
25
26
	private string $sessionID;
27
	private int $gameID;
28
	private array $var;
29
	private array $commonIDs = [];
30
	private bool $generate;
31
	private string $SN = '';
32
	private string $lastSN;
33
	private int $accountID;
34
	public int $lastAccessed;
35
36
	protected ?array $previousAjaxReturns;
37
	protected array $ajaxReturns = array();
38
39
	/**
40
	 * Return the SmrSession in the DI container.
41
	 * If one does not exist yet, it will be created.
42
	 * This is the intended way to construct this class.
43
	 */
44
	public static function getInstance() : self {
45
		return Smr\Container\DiContainer::get(self::class);
46
	}
47
48
	/**
49
	 * SmrSession constructor.
50
	 * Not intended to be constructed by hand. Use SmrSession::getInstance().
51
	 */
52
	public function __construct() {
53
54
		// Initialize the db connector here
55
		$this->db = MySqlDatabase::getInstance();
56
57
		// now try the cookie
58
		if (isset($_COOKIE['session_id']) && strlen($_COOKIE['session_id']) === 32) {
59
			$this->sessionID = $_COOKIE['session_id'];
60
		} else {
61
			// create a new session id
62
			do {
63
				$this->sessionID = md5(uniqid(strval(rand())));
64
				$this->db->query('SELECT 1 FROM active_session WHERE session_id = ' . $this->db->escapeString($this->sessionID) . ' LIMIT 1');
65
			} while ($this->db->nextRecord()); //Make sure we haven't somehow clashed with someone else's session.
66
67
			// This is a minor hack to make sure that setcookie is not called
68
			// for CLI programs and tests (to avoid "headers already sent").
69
			if (headers_sent() === false) {
70
				setcookie('session_id', $this->sessionID);
71
			}
72
		}
73
74
		// try to get current session
75
		$this->db->query('DELETE FROM active_session WHERE last_accessed < ' . $this->db->escapeNumber(time() - self::TIME_BEFORE_EXPIRY));
76
		$this->fetchVarInfo();
77
78
		$sn = Request::get('sn', '');
79
		if (!USING_AJAX && !empty($sn) && !empty($this->var[$sn])) {
80
			$var = $this->var[$sn];
81
			$currentPage = $var['url'] == 'skeleton.php' ? $var['body'] : $var['url'];
82
			$loadDelay = self::URL_LOAD_DELAY[$currentPage] ?? 0;
83
			$initialTimeBetweenLoads = microtime(true) - $var['PreviousRequestTime'];
84
			while (($timeBetweenLoads = microtime(true) - $var['PreviousRequestTime']) < $loadDelay) {
85
				$sleepTime = IRound(($loadDelay - $timeBetweenLoads) * 1000000);
86
			//	echo 'Sleeping for: ' . $sleepTime . 'us';
87
				usleep($sleepTime);
88
			}
89
			if (ENABLE_DEBUG) {
90
				$this->db->query('INSERT INTO debug VALUES (' . $this->db->escapeString('Delay: ' . $currentPage) . ',' . $this->db->escapeNumber($this->accountID) . ',' . $this->db->escapeNumber($initialTimeBetweenLoads) . ',' . $this->db->escapeNumber($timeBetweenLoads) . ')');
91
			}
92
		}
93
	}
94
95
	public function fetchVarInfo() : void {
96
		$this->db->query('SELECT * FROM active_session WHERE session_id = ' . $this->db->escapeString($this->sessionID) . ' LIMIT 1');
97
		if ($this->db->nextRecord()) {
98
			$this->generate = false;
99
			$this->sessionID = $this->db->getField('session_id');
100
			$this->accountID = $this->db->getInt('account_id');
101
			$this->gameID = $this->db->getInt('game_id');
102
			$this->lastAccessed = $this->db->getInt('last_accessed');
103
			$this->lastSN = $this->db->getField('last_sn');
104
			// We may not have ajax_returns if ajax was disabled
105
			$this->previousAjaxReturns = $this->db->getObject('ajax_returns', true, true);
106
107
			$this->var = $this->db->getObject('session_var', true);
108
109
			foreach ($this->var as $key => $value) {
110
				if ($value['Expires'] > 0 && $value['Expires'] <= Smr\Epoch::time()) { // Use 0 for infinity
111
					//This link is no longer valid
112
					unset($this->var[$key]);
113
				} elseif ($value['RemainingPageLoads'] < 0) {
114
					//This link is no longer valid
115
					unset($this->var[$key]);
116
				} else {
117
					$this->var[$key]['RemainingPageLoads'] -= 1;
118
					if (isset($value['CommonID'])) {
119
						$this->commonIDs[$value['CommonID']] = $key;
120
					}
121
				}
122
			}
123
		} else {
124
			$this->generate = true;
125
			$this->accountID = 0;
126
			$this->gameID = 0;
127
			$this->var = array();
128
		}
129
	}
130
131
	public function update() : void {
132
		foreach ($this->var as $key => $value) {
133
			if ($value['RemainingPageLoads'] <= 0) {
134
				//This link was valid this load but will not be in the future, removing it now saves database space and data transfer.
135
				unset($this->var[$key]);
136
			}
137
		}
138
		if (!$this->generate) {
139
			$this->db->query('UPDATE active_session SET account_id=' . $this->db->escapeNumber($this->accountID) . ',game_id=' . $this->db->escapeNumber($this->gameID) . (!USING_AJAX ? ',last_accessed=' . $this->db->escapeNumber(Smr\Epoch::time()) : '') . ',session_var=' . $this->db->escapeObject($this->var, true) .
140
					',last_sn=' . $this->db->escapeString($this->SN) .
141
					' WHERE session_id=' . $this->db->escapeString($this->sessionID) . (USING_AJAX ? ' AND last_sn=' . $this->db->escapeString($this->lastSN) : '') . ' LIMIT 1');
142
		} else {
143
			$this->db->query('DELETE FROM active_session WHERE account_id = ' . $this->db->escapeNumber($this->accountID) . ' AND game_id = ' . $this->db->escapeNumber($this->gameID));
144
			$this->db->query('INSERT INTO active_session (session_id, account_id, game_id, last_accessed, session_var) VALUES(' . $this->db->escapeString($this->sessionID) . ',' . $this->db->escapeNumber($this->accountID) . ',' . $this->db->escapeNumber($this->gameID) . ',' . $this->db->escapeNumber(Smr\Epoch::time()) . ',' . $this->db->escapeObject($this->var, true) . ')');
145
			$this->generate = false;
146
		}
147
	}
148
149
	/**
150
	 * Returns the Game ID associated with the session.
151
	 */
152
	public function getGameID() : int {
153
		return $this->gameID;
154
	}
155
156
	/**
157
	 * Returns true if the session is inside a game, false otherwise.
158
	 */
159
	public function hasGame() : bool {
160
		return $this->gameID != 0;
161
	}
162
163
	public function hasAccount() : bool {
164
		return $this->accountID > 0;
165
	}
166
167
	public function getAccountID() : int {
168
		return $this->accountID;
169
	}
170
171
	public function getAccount() : SmrAccount {
172
		return SmrAccount::getAccount($this->accountID);
173
	}
174
175
	/**
176
	 * Sets the `accountID` attribute of this session.
177
	 */
178
	public function setAccount(AbstractSmrAccount $account) : void {
179
		$this->accountID = $account->getAccountID();
180
	}
181
182
	/**
183
	 * Updates the `gameID` attribute of the session and deletes any other
184
	 * active sessions in this game for this account.
185
	 */
186
	public function updateGame(int $gameID) : void {
187
		if ($this->gameID == $gameID) {
188
			return;
189
		}
190
		$this->gameID = $gameID;
191
		$this->db->query('DELETE FROM active_session WHERE account_id = ' . $this->db->escapeNumber($this->accountID) . ' AND game_id = ' . $this->gameID);
192
		$this->db->query('UPDATE active_session SET game_id=' . $this->db->escapeNumber($this->gameID) . ' WHERE session_id=' . $this->db->escapeString($this->sessionID));
193
	}
194
195
	/**
196
	 * Returns true if the current SN is different than the previous SN.
197
	 */
198
	public function hasChangedSN() : bool {
199
		return $this->SN != $this->lastSN;
200
	}
201
202
	private function updateSN() : void {
203
		if (!USING_AJAX) {
204
			$this->db->query('UPDATE active_session SET last_sn=' . $this->db->escapeString($this->SN) .
205
				' WHERE session_id=' . $this->db->escapeString($this->sessionID) . ' LIMIT 1');
206
		}
207
	}
208
209
	public function destroy() : void {
210
		$this->db->query('DELETE FROM active_session WHERE session_id = ' . $this->db->escapeString($this->sessionID));
211
		unset($this->sessionID);
212
		unset($this->accountID);
213
		unset($this->gameID);
214
	}
215
216
	public function getLastAccessed() : int {
217
		return $this->lastAccessed;
218
	}
219
220
	/**
221
	 * @deprecated Function to facilitate the transition to a non-static
222
	 * SmrSession. This is a wrapper for the non-static SmrSession::time
223
	 * and will be removed in a future update.
224
	 */
225
	public static function getTime() : int {
226
		return self::getInstance()->time();
227
	}
228
229
	/**
230
	 * Returns the time (in seconds) associated with this page request.
231
	 */
232
	public function time() : int {
233
		return $this->pageRequestTime->getTime();
234
	}
235
236
	/**
237
	 * Returns the time (in seconds, with microsecond-level precision)
238
	 * associated with this page request.
239
	 */
240
	public function microtime() : float {
241
		return $this->pageRequestTime->getMicroTime();
242
	}
243
244
	/**
245
	 * Update the time associated with this page request.
246
	 *
247
	 * NOTE: This should never be called by normal page requests, and should
248
	 * only be used by the CLI programs that run continuously.
249
	 */
250
	public function updateTime() : void {
251
		if (!defined('NPC_SCRIPT')) {
252
			throw new Exception('Only call this function from CLI programs!');
253
		}
254
		$this->pageRequestTime = new Time();
0 ignored issues
show
Bug Best Practice introduced by
The property pageRequestTime does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
255
	}
256
257
	/**
258
	 * Retrieve the session var for the page given by $sn.
259
	 * If $sn is not specified, use the current page (i.e. $this->SN).
260
	 */
261
	public function retrieveVar(string $sn = null) : Page|false {
262
		if (is_null($sn)) {
263
			$sn = $this->SN;
264
		}
265
		if (empty($this->var[$sn])) {
266
			return false;
267
		}
268
		$this->SN = $sn;
269
		$this->updateSN();
270
		if (isset($this->var[$sn]['body']) && isset($this->var[$sn]['CommonID'])) {
271
//			if(preg_match('/processing/',$this->var[$sn]['body']))
272
			unset($this->commonIDs[$this->var[$sn]['CommonID']]); //Do not store common id for current page
273
			unset($this->var[$sn]['CommonID']);
274
		}
275
276
		$this->var[$sn]['RemainingPageLoads'] += 1; // Allow refreshing
277
		$this->var[$sn]['Expires'] = 0; // Allow refreshing forever
278
		return $this->var[$sn];
279
	}
280
281
	/**
282
	 * Gets a var from $var, $_REQUEST, or $default. Then stores it in the
283
	 * session so that it can still be retrieved when the page auto-refreshes.
284
	 * This is the recommended way to get $_REQUEST data for display pages.
285
	 * For processing pages, see the Request class.
286
	 */
287
	public function getRequestVar(string $varName, string $default = null) : string {
288
		$result = Request::getVar($varName, $default);
289
		$this->updateVar($varName, $result);
290
		return $result;
291
	}
292
293
	public function getRequestVarInt(string $varName, int $default = null) : int {
294
		$result = Request::getVarInt($varName, $default);
295
		$this->updateVar($varName, $result);
296
		return $result;
297
	}
298
299
	public function getRequestVarIntArray(string $varName, array $default = null) : array {
300
		$result = Request::getVarIntArray($varName, $default);
301
		$this->updateVar($varName, $result);
302
		return $result;
303
	}
304
305
	public function resetLink(Page $container, string $sn) : string {
306
		//Do not allow sharing SN, useful for forwarding.
307
		global $lock;
308
		if (isset($this->var[$sn]['CommonID'])) {
309
			unset($this->commonIDs[$this->var[$sn]['CommonID']]); //Do not store common id for reset page, to allow refreshing to always give the same page in response
310
		}
311
		$this->SN = $sn;
312
		if (!isset($container['Expires'])) {
313
			$container['Expires'] = 0; // Lasts forever
314
		}
315
		if (!isset($container['RemainingPageLoads'])) {
316
			$container['RemainingPageLoads'] = 1; // Allow refreshing
317
		}
318
		if (!isset($container['PreviousRequestTime'])) {
319
			if (isset($this->var[$sn]['PreviousRequestTime'])) {
320
				$container['PreviousRequestTime'] = $this->var[$sn]['PreviousRequestTime']; // Copy across the previous request time if not explicitly set.
321
			}
322
		}
323
324
		$this->var[$sn] = $container;
325
		if (!$lock && !USING_AJAX) {
326
			$this->update();
327
		}
328
		return $sn;
329
	}
330
331
	public function updateVar(string $key, mixed $value) : void {
332
		global $var;
333
		if ($value === null) {
334
			if (isset($var[$key])) {
335
				unset($var[$key]);
336
			}
337
			if (isset($var[$this->SN][$key])) {
338
				unset($this->var[$this->SN][$key]);
339
			}
340
		} else {
341
			$var[$key] = $value;
342
			$this->var[$this->SN][$key] = $value;
343
		}
344
	}
345
346
	public function clearLinks() : void {
347
		$this->var = array($this->SN => $this->var[$this->SN]);
348
		$this->commonIDs = array();
349
	}
350
351
	public function addLink(Page $container) : string {
352
		$sn = $this->generateSN($container);
353
		$this->var[$sn] = $container;
354
		return $sn;
355
	}
356
357
	protected function generateSN(Page $container) : string {
358
		if (isset($this->commonIDs[$container['CommonID']])) {
359
			$sn = $this->commonIDs[$container['CommonID']];
360
			$container['PreviousRequestTime'] = isset($this->var[$sn]) ? $this->var[$sn]['PreviousRequestTime'] : Smr\Epoch::microtime();
361
		} else {
362
			do {
363
				$sn = random_alphabetic_string(6);
364
			} while (isset($this->var[$sn]));
365
			$container['PreviousRequestTime'] = Smr\Epoch::microtime();
366
		}
367
		$this->commonIDs[$container['CommonID']] = $sn;
368
		return $sn;
369
	}
370
371
	public function addAjaxReturns(string $element, string $contents) : bool {
372
		$this->ajaxReturns[$element] = $contents;
373
		return isset($this->previousAjaxReturns[$element]) && $this->previousAjaxReturns[$element] == $contents;
374
	}
375
376
	public function saveAjaxReturns() : void {
377
		if (empty($this->ajaxReturns)) {
378
			return;
379
		}
380
		$this->db->query('UPDATE active_session SET ajax_returns=' . $this->db->escapeObject($this->ajaxReturns, true) .
381
				' WHERE session_id=' . $this->db->escapeString($this->sessionID) . ' LIMIT 1');
382
	}
383
}
384