We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.
| Total Complexity | 54 | 
| Total Lines | 331 | 
| Duplicated Lines | 0 % | 
| Changes | 2 | ||
| Bugs | 0 | Features | 0 | 
Complex classes like Session often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Session, and based on these observations, apply Extract Interface, too.
| 1 | <?php declare(strict_types=1);  | 
            ||
| 12 | class Session { | 
            ||
| 13 | |||
| 14 | private const TIME_BEFORE_EXPIRY = 172800; // 2 days  | 
            ||
| 15 | |||
| 16 | private const URL_LOAD_DELAY = [  | 
            ||
| 17 | 'configure_hardware.php' => .4,  | 
            ||
| 18 | 'forces_drop.php' => .4,  | 
            ||
| 19 | 'forces_drop_processing.php' => .5,  | 
            ||
| 20 | 'forces_refresh_processing.php' => .4,  | 
            ||
| 21 | 'sector_jump_processing.php' => .4,  | 
            ||
| 22 | 'sector_move_processing.php' => .4,  | 
            ||
| 23 | 'sector_scan.php' => .4,  | 
            ||
| 24 | 'shop_goods_processing.php' => .4,  | 
            ||
| 25 | 'trader_attack_processing.php' => .75,  | 
            ||
| 26 | 'trader_examine.php' => .75,  | 
            ||
| 27 | ];  | 
            ||
| 28 | |||
| 29 | protected Database $db;  | 
            ||
| 30 | |||
| 31 | private string $sessionID;  | 
            ||
| 32 | private int $gameID;  | 
            ||
| 33 | /** @var array<string, Page> */  | 
            ||
| 34 | private array $links = [];  | 
            ||
| 35 | private ?Page $currentPage = null;  | 
            ||
| 36 | private bool $generate;  | 
            ||
| 37 | public readonly bool $ajax;  | 
            ||
| 38 | private string $SN;  | 
            ||
| 39 | private string $lastSN;  | 
            ||
| 40 | private int $accountID;  | 
            ||
| 41 | private float $lastAccessed;  | 
            ||
| 42 | |||
| 43 | /** @var ?array<string, string> */  | 
            ||
| 44 | protected ?array $previousAjaxReturns;  | 
            ||
| 45 | /** @var array<string, string> */  | 
            ||
| 46 | protected array $ajaxReturns = [];  | 
            ||
| 47 | |||
| 48 | /**  | 
            ||
| 49 | * Return the Smr\Session in the DI container.  | 
            ||
| 50 | * If one does not exist yet, it will be created.  | 
            ||
| 51 | * This is the intended way to construct this class.  | 
            ||
| 52 | */  | 
            ||
| 53 | 	public static function getInstance(): self { | 
            ||
| 54 | return DiContainer::get(self::class);  | 
            ||
| 55 | }  | 
            ||
| 56 | |||
| 57 | /**  | 
            ||
| 58 | * Smr\Session constructor.  | 
            ||
| 59 | * Not intended to be constructed by hand. Use Smr\Session::getInstance().  | 
            ||
| 60 | */  | 
            ||
| 61 | 	public function __construct() { | 
            ||
| 62 | // Initialize the db connector here  | 
            ||
| 63 | $this->db = Database::getInstance();  | 
            ||
| 64 | |||
| 65 | // now try the cookie  | 
            ||
| 66 | $idLength = 32;  | 
            ||
| 67 | 		if (isset($_COOKIE['session_id']) && strlen($_COOKIE['session_id']) === $idLength) { | 
            ||
| 68 | $this->sessionID = $_COOKIE['session_id'];  | 
            ||
| 69 | 		} else { | 
            ||
| 70 | // create a new session id  | 
            ||
| 71 | 			do { | 
            ||
| 72 | $this->sessionID = random_string($idLength);  | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 73 | 				$dbResult = $this->db->read('SELECT 1 FROM active_session WHERE session_id = ' . $this->db->escapeString($this->sessionID)); | 
            ||
| 74 | } while ($dbResult->hasRecord()); //Make sure we haven't somehow clashed with someone else's session.  | 
            ||
| 75 | |||
| 76 | // This is a minor hack to make sure that setcookie is not called  | 
            ||
| 77 | // for CLI programs and tests (to avoid "headers already sent").  | 
            ||
| 78 | 			if (headers_sent() === false) { | 
            ||
| 79 | 				setcookie('session_id', $this->sessionID); | 
            ||
| 80 | }  | 
            ||
| 81 | }  | 
            ||
| 82 | |||
| 83 | // Delete any expired sessions  | 
            ||
| 84 | 		$this->db->write('DELETE FROM active_session WHERE last_accessed < ' . $this->db->escapeNumber(time() - self::TIME_BEFORE_EXPIRY)); | 
            ||
| 85 | |||
| 86 | // try to get current session  | 
            ||
| 87 | 		$this->ajax = Request::getInt('ajax', 0) === 1; | 
            ||
| 88 | 		$this->SN = Request::get('sn', ''); | 
            ||
| 89 | $this->fetchVarInfo();  | 
            ||
| 90 | |||
| 91 | 		if (!$this->ajax && $this->hasCurrentVar()) { | 
            ||
| 92 | $file = $this->getCurrentVar()->file;  | 
            ||
| 93 | $loadDelay = self::URL_LOAD_DELAY[$file] ?? 0;  | 
            ||
| 94 | $timeBetweenLoads = microtime(true) - $this->lastAccessed;  | 
            ||
| 95 | 			if ($timeBetweenLoads < $loadDelay) { | 
            ||
| 96 | $sleepTime = IRound(($loadDelay - $timeBetweenLoads) * 1000000);  | 
            ||
| 97 | //echo 'Sleeping for: ' . $sleepTime . 'us';  | 
            ||
| 98 | usleep($sleepTime);  | 
            ||
| 99 | }  | 
            ||
| 100 | 			if (ENABLE_DEBUG) { | 
            ||
| 101 | 				$this->db->insert('debug', [ | 
            ||
| 102 | 					'debug_type' => $this->db->escapeString('Delay: ' . $file), | 
            ||
| 103 | 'account_id' => $this->db->escapeNumber($this->accountID),  | 
            ||
| 104 | 'value' => $this->db->escapeNumber($timeBetweenLoads),  | 
            ||
| 105 | ]);  | 
            ||
| 106 | }  | 
            ||
| 107 | }  | 
            ||
| 108 | }  | 
            ||
| 109 | |||
| 110 | 	public function fetchVarInfo(): void { | 
            ||
| 111 | 		$dbResult = $this->db->read('SELECT * FROM active_session WHERE session_id = ' . $this->db->escapeString($this->sessionID)); | 
            ||
| 112 | 		if ($dbResult->hasRecord()) { | 
            ||
| 113 | $dbRecord = $dbResult->record();  | 
            ||
| 114 | $this->generate = false;  | 
            ||
| 115 | 			$this->sessionID = $dbRecord->getString('session_id'); | 
            ||
| 116 | 			$this->accountID = $dbRecord->getInt('account_id'); | 
            ||
| 117 | 			$this->gameID = $dbRecord->getInt('game_id'); | 
            ||
| 118 | 			$this->lastAccessed = $dbRecord->getFloat('last_accessed'); | 
            ||
| 119 | 			$this->lastSN = $dbRecord->getString('last_sn'); | 
            ||
| 120 | // We may not have ajax_returns if ajax was disabled  | 
            ||
| 121 | 			$this->previousAjaxReturns = $dbRecord->getObject('ajax_returns', true, true); | 
            ||
| 122 | |||
| 123 | 			[$this->links, $lastPage] = $dbRecord->getObject('session_var', true); | 
            ||
| 124 | |||
| 125 | $ajaxRefresh = $this->ajax && !$this->hasChangedSN();  | 
            ||
| 126 | 			if ($ajaxRefresh) { | 
            ||
| 127 | $this->currentPage = $lastPage;  | 
            ||
| 128 | 			} elseif (isset($this->links[$this->SN])) { | 
            ||
| 129 | // If the current page is modified during page processing, we need  | 
            ||
| 130 | // to make sure the original link is unchanged. So we clone it here.  | 
            ||
| 131 | $this->currentPage = clone $this->links[$this->SN];  | 
            ||
| 132 | |||
| 133 | // If SN changes during an ajax update, it is an error unless user is  | 
            ||
| 134 | // requesting a page that is allowed to be executed in an ajax call.  | 
            ||
| 135 | $allowAjax = $this->currentPage['AJAX'] ?? false;  | 
            ||
| 136 | 				if (!$allowAjax && $this->ajax && $this->hasChangedSN()) { | 
            ||
| 137 | 					throw new UserError('The previous page failed to auto-refresh properly!'); | 
            ||
| 138 | }  | 
            ||
| 139 | }  | 
            ||
| 140 | |||
| 141 | 			if (!$ajaxRefresh) { // since form pages don't ajax refresh properly | 
            ||
| 142 | 				foreach ($this->links as $sn => $link) { | 
            ||
| 143 | 					if (!$link->reusable) { | 
            ||
| 144 | // This link is no longer valid  | 
            ||
| 145 | unset($this->links[$sn]);  | 
            ||
| 146 | }  | 
            ||
| 147 | }  | 
            ||
| 148 | }  | 
            ||
| 149 | 		} else { | 
            ||
| 150 | $this->generate = true;  | 
            ||
| 151 | $this->accountID = 0;  | 
            ||
| 152 | $this->gameID = 0;  | 
            ||
| 153 | }  | 
            ||
| 154 | }  | 
            ||
| 155 | |||
| 156 | 	public function update(): void { | 
            ||
| 157 | $sessionVar = [$this->links, $this->currentPage];  | 
            ||
| 158 | 		if (!$this->generate) { | 
            ||
| 159 | 			$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) . | 
            ||
| 160 | ',last_sn=' . $this->db->escapeString($this->SN) .  | 
            ||
| 161 | ' WHERE session_id=' . $this->db->escapeString($this->sessionID) . ($this->ajax ? ' AND last_sn=' . $this->db->escapeString($this->lastSN) : ''));  | 
            ||
| 162 | 		} else { | 
            ||
| 163 | 			$this->db->write('DELETE FROM active_session WHERE account_id = ' . $this->db->escapeNumber($this->accountID) . ' AND game_id = ' . $this->db->escapeNumber($this->gameID)); | 
            ||
| 164 | 			$this->db->insert('active_session', [ | 
            ||
| 165 | 'session_id' => $this->db->escapeString($this->sessionID),  | 
            ||
| 166 | 'account_id' => $this->db->escapeNumber($this->accountID),  | 
            ||
| 167 | 'game_id' => $this->db->escapeNumber($this->gameID),  | 
            ||
| 168 | 'last_accessed' => $this->db->escapeNumber(Epoch::microtime()),  | 
            ||
| 169 | 'session_var' => $this->db->escapeObject($sessionVar, true),  | 
            ||
| 170 | ]);  | 
            ||
| 171 | $this->generate = false;  | 
            ||
| 172 | }  | 
            ||
| 173 | }  | 
            ||
| 174 | |||
| 175 | /**  | 
            ||
| 176 | * Uniquely identifies the session in the database.  | 
            ||
| 177 | */  | 
            ||
| 178 | 	public function getSessionID(): string { | 
            ||
| 179 | return $this->sessionID;  | 
            ||
| 180 | }  | 
            ||
| 181 | |||
| 182 | /**  | 
            ||
| 183 | * Returns the Game ID associated with the session.  | 
            ||
| 184 | */  | 
            ||
| 185 | 	public function getGameID(): int { | 
            ||
| 186 | return $this->gameID;  | 
            ||
| 187 | }  | 
            ||
| 188 | |||
| 189 | /**  | 
            ||
| 190 | * Returns true if the session is inside a game, false otherwise.  | 
            ||
| 191 | */  | 
            ||
| 192 | 	public function hasGame(): bool { | 
            ||
| 193 | return $this->gameID != 0;  | 
            ||
| 194 | }  | 
            ||
| 195 | |||
| 196 | 	public function hasAccount(): bool { | 
            ||
| 197 | return $this->accountID > 0;  | 
            ||
| 198 | }  | 
            ||
| 199 | |||
| 200 | 	public function getAccountID(): int { | 
            ||
| 201 | return $this->accountID;  | 
            ||
| 202 | }  | 
            ||
| 203 | |||
| 204 | 	public function getAccount(): SmrAccount { | 
            ||
| 205 | return SmrAccount::getAccount($this->accountID);  | 
            ||
| 206 | }  | 
            ||
| 207 | |||
| 208 | 	public function getPlayer(bool $forceUpdate = false): AbstractSmrPlayer { | 
            ||
| 209 | return SmrPlayer::getPlayer($this->accountID, $this->gameID, $forceUpdate);  | 
            ||
| 210 | }  | 
            ||
| 211 | |||
| 212 | /**  | 
            ||
| 213 | * Sets the `accountID` attribute of this session.  | 
            ||
| 214 | */  | 
            ||
| 215 | 	public function setAccount(SmrAccount $account): void { | 
            ||
| 216 | $this->accountID = $account->getAccountID();  | 
            ||
| 217 | }  | 
            ||
| 218 | |||
| 219 | /**  | 
            ||
| 220 | * Updates the `gameID` attribute of the session and deletes any other  | 
            ||
| 221 | * active sessions in this game for this account.  | 
            ||
| 222 | */  | 
            ||
| 223 | 	public function updateGame(int $gameID): void { | 
            ||
| 224 | 		if ($this->gameID == $gameID) { | 
            ||
| 225 | return;  | 
            ||
| 226 | }  | 
            ||
| 227 | $this->gameID = $gameID;  | 
            ||
| 228 | 		$this->db->write('DELETE FROM active_session WHERE account_id = ' . $this->db->escapeNumber($this->accountID) . ' AND game_id = ' . $this->gameID); | 
            ||
| 229 | 		$this->db->write('UPDATE active_session SET game_id=' . $this->db->escapeNumber($this->gameID) . ' WHERE session_id=' . $this->db->escapeString($this->sessionID)); | 
            ||
| 230 | }  | 
            ||
| 231 | |||
| 232 | /**  | 
            ||
| 233 | * The SN is the URL parameter that defines the page being requested.  | 
            ||
| 234 | */  | 
            ||
| 235 | 	public function getSN(): string { | 
            ||
| 236 | return $this->SN;  | 
            ||
| 237 | }  | 
            ||
| 238 | |||
| 239 | /**  | 
            ||
| 240 | * Returns true if the current SN is different than the previous SN.  | 
            ||
| 241 | */  | 
            ||
| 242 | 	public function hasChangedSN(): bool { | 
            ||
| 243 | return $this->SN != $this->lastSN;  | 
            ||
| 244 | }  | 
            ||
| 245 | |||
| 246 | 	public function destroy(): void { | 
            ||
| 247 | 		$this->db->write('DELETE FROM active_session WHERE session_id = ' . $this->db->escapeString($this->sessionID)); | 
            ||
| 248 | unset($this->sessionID);  | 
            ||
| 249 | unset($this->accountID);  | 
            ||
| 250 | unset($this->gameID);  | 
            ||
| 251 | }  | 
            ||
| 252 | |||
| 253 | 	public function getLastAccessed(): float { | 
            ||
| 254 | return $this->lastAccessed;  | 
            ||
| 255 | }  | 
            ||
| 256 | |||
| 257 | /**  | 
            ||
| 258 | * Check if the session has a var associated with the current SN.  | 
            ||
| 259 | */  | 
            ||
| 260 | 	public function hasCurrentVar(): bool { | 
            ||
| 261 | return $this->currentPage !== null;  | 
            ||
| 262 | }  | 
            ||
| 263 | |||
| 264 | /**  | 
            ||
| 265 | * Returns the session var associated with the current SN.  | 
            ||
| 266 | */  | 
            ||
| 267 | 	public function getCurrentVar(): Page { | 
            ||
| 268 | return $this->currentPage;  | 
            ||
| 269 | }  | 
            ||
| 270 | |||
| 271 | /**  | 
            ||
| 272 | * Gets a var from $var, $_REQUEST, or $default. Then stores it in the  | 
            ||
| 273 | * session so that it can still be retrieved when the page auto-refreshes.  | 
            ||
| 274 | * This is the recommended way to get $_REQUEST data for display pages.  | 
            ||
| 275 | * For processing pages, see the Request class.  | 
            ||
| 276 | */  | 
            ||
| 277 | 	public function getRequestVar(string $varName, string $default = null): string { | 
            ||
| 278 | $result = Request::getVar($varName, $default);  | 
            ||
| 279 | $var = $this->getCurrentVar();  | 
            ||
| 280 | $var[$varName] = $result;  | 
            ||
| 281 | return $result;  | 
            ||
| 282 | }  | 
            ||
| 283 | |||
| 284 | 	public function getRequestVarInt(string $varName, int $default = null): int { | 
            ||
| 285 | $result = Request::getVarInt($varName, $default);  | 
            ||
| 286 | $var = $this->getCurrentVar();  | 
            ||
| 287 | $var[$varName] = $result;  | 
            ||
| 288 | return $result;  | 
            ||
| 289 | }  | 
            ||
| 290 | |||
| 291 | /**  | 
            ||
| 292 | * @param ?array<int> $default  | 
            ||
| 293 | * @return array<int>  | 
            ||
| 294 | */  | 
            ||
| 295 | 	public function getRequestVarIntArray(string $varName, array $default = null): array { | 
            ||
| 300 | }  | 
            ||
| 301 | |||
| 302 | /**  | 
            ||
| 303 | * Replace the global $var with the given $container.  | 
            ||
| 304 | */  | 
            ||
| 305 | 	public function setCurrentVar(Page $container): void { | 
            ||
| 306 | $this->currentPage = $container;  | 
            ||
| 307 | }  | 
            ||
| 308 | |||
| 309 | 	public function clearLinks(): void { | 
            ||
| 310 | $this->links = [];  | 
            ||
| 311 | }  | 
            ||
| 312 | |||
| 313 | /**  | 
            ||
| 314 | * Add a page to the session so that it can be used on next page load.  | 
            ||
| 315 | * It will be associated with an SN that will be used for linking.  | 
            ||
| 316 | */  | 
            ||
| 317 | 	public function addLink(Page $container): string { | 
            ||
| 318 | // If we already had a link to this exact page, use the existing SN for it.  | 
            ||
| 319 | 		foreach ($this->links as $sn => $link) { | 
            ||
| 320 | 			if ($container == $link) { // loose equality to compare contents | 
            ||
| 321 | return $sn;  | 
            ||
| 322 | }  | 
            ||
| 323 | }  | 
            ||
| 324 | // This page isn't an existing link, so give it a new SN.  | 
            ||
| 325 | 		do { | 
            ||
| 326 | $sn = random_alphabetic_string(6);  | 
            ||
| 327 | } while (isset($this->links[$sn]));  | 
            ||
| 328 | $this->links[$sn] = $container;  | 
            ||
| 329 | return $sn;  | 
            ||
| 330 | }  | 
            ||
| 331 | |||
| 332 | 	public function addAjaxReturns(string $element, string $contents): bool { | 
            ||
| 333 | $this->ajaxReturns[$element] = $contents;  | 
            ||
| 334 | return isset($this->previousAjaxReturns[$element]) && $this->previousAjaxReturns[$element] == $contents;  | 
            ||
| 335 | }  | 
            ||
| 336 | |||
| 337 | 	public function saveAjaxReturns(): void { | 
            ||
| 343 | }  | 
            ||
| 344 | |||
| 345 | }  | 
            ||
| 346 |