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:50
created

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