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

Passed
Pull Request — master (#1046)
by Dan
05:18
created

AbstractSmrAccount::setHofName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
// Exception thrown when an account cannot be found in the database
4
class AccountNotFoundException extends Exception {}
5
6
abstract class AbstractSmrAccount {
7
	const USER_RANKINGS_EACH_STAT_POW = .9;
8
	const USER_RANKINGS_TOTAL_SCORE_POW = .3;
9
	const USER_RANKINGS_RANK_BOUNDARY = 5.2;
10
	protected const USER_RANKINGS_SCORE = array(
11
		// [Stat, a, b]
12
		// Used as: pow(Stat * a, USER_RANKINGS_EACH_STAT_POW) * b
13
		array(array('Trade', 'Experience', 'Total'), .1, 0.5),
14
		array(array('Trade', 'Money', 'Profit'), 0.00005, 0.5),
15
		array(array('Killing', 'Kills'), 1000, 1)
16
		);
17
18
	protected static $CACHE_ACCOUNTS = array();
19
	protected const DEFAULT_HOTKEYS = array(
20
		'MoveUp' => array('w', 'up'),
21
		'ScanUp' => array('shift+w', 'shift+up'),
22
		'MoveLeft' => array('a', 'left'),
23
		'ScanLeft' => array('shift+a', 'shift+left'),
24
		'MoveRight' => array('d', 'right'),
25
		'ScanRight' => array('shift+d', 'shift+right'),
26
		'MoveDown' => array('s', 'down'),
27
		'ScanDown' => array('shift+s', 'shift+down'),
28
		'MoveWarp' => array('e', '0'),
29
		'ScanWarp' => array('shift+e', 'shift+0'),
30
		'ScanCurrent' => array('shift+1'),
31
		'CurrentSector' => array('1'),
32
		'LocalMap' => array('2'),
33
		'PlotCourse' => array('3'),
34
		'CurrentPlayers' => array('4'),
35
		'EnterPort' => array('q'),
36
		'AttackTrader' => array('f')
37
	);
38
39
	protected Smr\Database $db;
40
41
	protected int $account_id;
42
	protected string $login;
43
	protected string $passwordHash;
44
	protected string $email;
45
	protected bool $validated;
46
	protected string $validation_code;
47
	protected int $last_login;
48
	protected string $hofName;
49
	protected ?string $discordId;
50
	protected ?string $ircNick;
51
	protected bool $veteranForced;
52
	protected bool $logging;
53
	protected int $offset;
54
	protected bool $images;
55
	protected int $fontSize;
56
	protected string $passwordReset;
57
	protected int $points;
58
	protected bool $useAJAX;
59
	protected int $mailBanned;
60
	protected array $HOF;
61
	protected array $individualScores;
62
	protected int $score;
63
	protected ?string $cssLink;
64
	protected bool $defaultCSSEnabled;
65
	protected ?array $messageNotifications;
66
	protected bool $centerGalaxyMapOnPlayer;
67
	protected array $oldAccountIDs = array();
68
	protected int $maxRankAchieved;
69
	protected int $referrerID;
70
	protected int $credits; // SMR credits
71
	protected int $rewardCredits; // SMR reward credits
72
	protected string $dateFormat;
73
	protected string $timeFormat;
74
	protected string $template;
75
	protected string $colourScheme;
76
	protected array $hotkeys;
77
	protected array $permissions;
78
	protected string $friendlyColour;
79
	protected string $neutralColour;
80
	protected string $enemyColour;
81
	protected string $SQL;
82
83
	protected bool $npc;
84
85
	protected bool $hasChanged;
86
87
	public static function getDefaultHotkeys() : array {
88
		return self::DEFAULT_HOTKEYS;
89
	}
90
91
	public static function clearCache() : void {
92
		self::$CACHE_ACCOUNTS = [];
93
	}
94
95
	public static function getAccount(int $accountID, bool $forceUpdate = false) : SmrAccount {
96
		if ($forceUpdate || !isset(self::$CACHE_ACCOUNTS[$accountID])) {
97
			self::$CACHE_ACCOUNTS[$accountID] = new SmrAccount($accountID);
98
		}
99
		return self::$CACHE_ACCOUNTS[$accountID];
100
	}
101
102
	public static function getAccountByName(string $login, bool $forceUpdate = false) : ?SmrAccount {
103
		if (empty($login)) { return null; }
104
		$db = Smr\Database::getInstance();
105
		$db->query('SELECT account_id FROM account WHERE login = ' . $db->escapeString($login) . ' LIMIT 1');
106
		if ($db->nextRecord()) {
107
			return self::getAccount($db->getInt('account_id'), $forceUpdate);
108
		} else {
109
			return null;
110
		}
111
	}
112
113
	public static function getAccountByEmail(?string $email, bool $forceUpdate = false) : ?SmrAccount {
114
		if (empty($email)) { return null; }
115
		$db = Smr\Database::getInstance();
116
		$db->query('SELECT account_id FROM account WHERE email = ' . $db->escapeString($email) . ' LIMIT 1');
117
		if ($db->nextRecord()) {
118
			return self::getAccount($db->getInt('account_id'), $forceUpdate);
119
		} else {
120
			return null;
121
		}
122
	}
123
124
	public static function getAccountByDiscordId(?string $id, bool $forceUpdate = false) : ?SmrAccount {
125
		if (empty($id)) { return null; }
126
		$db = Smr\Database::getInstance();
127
		$db->query('SELECT account_id FROM account where discord_id = ' . $db->escapeString($id) . ' LIMIT 1');
128
		if ($db->nextRecord()) {
129
			return self::getAccount($db->getInt('account_id'), $forceUpdate);
130
		} else {
131
			return null;
132
		}
133
	}
134
135
	public static function getAccountByIrcNick(?string $nick, bool $forceUpdate = false) : ?SmrAccount {
136
		if (empty($nick)) { return null; }
137
		$db = Smr\Database::getInstance();
138
		$db->query('SELECT account_id FROM account WHERE irc_nick = ' . $db->escapeString($nick) . ' LIMIT 1');
139
		if ($db->nextRecord()) {
140
			return self::getAccount($db->getInt('account_id'), $forceUpdate);
141
		} else {
142
			return null;
143
		}
144
	}
145
146
	public static function getAccountBySocialLogin(Smr\SocialLogin\SocialLogin $social, bool $forceUpdate = false) : ?SmrAccount {
147
		if (!$social->isValid()) { return null; }
148
		$db = Smr\Database::getInstance();
149
		$db->query('SELECT account_id FROM account JOIN account_auth USING(account_id)
150
		            WHERE login_type = '.$db->escapeString($social->getLoginType()) . '
151
		              AND auth_key = '.$db->escapeString($social->getUserID()) . ' LIMIT 1');
152
		if ($db->nextRecord()) {
153
			return self::getAccount($db->getInt('account_id'), $forceUpdate);
154
		} else {
155
			return null;
156
		}
157
	}
158
159
	public static function createAccount(string $login, string $password, string $email, int $timez, int $referral) : SmrAccount {
160
		if ($referral != 0) {
161
			// Will throw if referral account doesn't exist
162
			SmrAccount::getAccount($referral);
163
		}
164
		$db = Smr\Database::getInstance();
165
		$passwordHash = password_hash($password, PASSWORD_DEFAULT);
166
		$db->query('INSERT INTO account (login, password, email, validation_code, last_login, offset, referral_id, hof_name, hotkeys) VALUES(' .
167
			$db->escapeString($login) . ', ' . $db->escapeString($passwordHash) . ', ' . $db->escapeString($email) . ', ' .
168
			$db->escapeString(random_string(10)) . ',' . $db->escapeNumber(Smr\Epoch::time()) . ',' . $db->escapeNumber($timez) . ',' . $db->escapeNumber($referral) . ',' . $db->escapeString($login) . ',' . $db->escapeObject([]) . ')');
169
		return self::getAccountByName($login);
170
	}
171
172
	public static function getUserScoreCaseStatement(Smr\Database $db) : array {
173
		$userRankingTypes = array();
174
		$case = 'FLOOR(SUM(CASE type ';
175
		foreach (self::USER_RANKINGS_SCORE as $userRankingScore) {
176
			$userRankingType = $db->escapeArray($userRankingScore[0], ':', false);
177
			$userRankingTypes[] = $userRankingType;
178
			$case .= ' WHEN ' . $userRankingType . ' THEN POW(amount*' . $userRankingScore[1] . ',' . SmrAccount::USER_RANKINGS_EACH_STAT_POW . ')*' . $userRankingScore[2];
179
		}
180
		$case .= ' END))';
181
		return array('CASE' => $case, 'IN' => join(',', $userRankingTypes));
182
	}
183
184
	protected function __construct(int $accountID) {
185
		$this->db = Smr\Database::getInstance();
186
		$this->SQL = 'account_id = ' . $this->db->escapeNumber($accountID);
187
		$this->db->query('SELECT * FROM account WHERE ' . $this->SQL . ' LIMIT 1');
188
189
		if ($this->db->nextRecord()) {
190
			$this->account_id = $this->db->getInt('account_id');
191
192
			$this->login = $this->db->getField('login');
193
			$this->passwordHash = $this->db->getField('password');
194
			$this->email = $this->db->getField('email');
195
			$this->validated = $this->db->getBoolean('validated');
196
197
			$this->last_login = $this->db->getInt('last_login');
198
			$this->validation_code = $this->db->getField('validation_code');
199
			$this->veteranForced = $this->db->getBoolean('veteran');
200
			$this->logging = $this->db->getBoolean('logging');
201
			$this->offset = $this->db->getInt('offset');
202
			$this->images = $this->db->getBoolean('images');
203
			$this->fontSize = $this->db->getInt('fontsize');
204
205
			$this->passwordReset = $this->db->getField('password_reset');
206
			$this->useAJAX = $this->db->getBoolean('use_ajax');
207
			$this->mailBanned = $this->db->getInt('mail_banned');
208
209
			$this->friendlyColour = $this->db->getField('friendly_colour');
210
			$this->neutralColour = $this->db->getField('neutral_colour');
211
			$this->enemyColour = $this->db->getField('enemy_colour');
212
213
			$this->cssLink = $this->db->getField('css_link');
214
			$this->defaultCSSEnabled = $this->db->getBoolean('default_css_enabled');
215
			$this->centerGalaxyMapOnPlayer = $this->db->getBoolean('center_galaxy_map_on_player');
216
217
			$this->messageNotifications = $this->db->getObject('message_notifications', false, true);
218
			$this->hotkeys = $this->db->getObject('hotkeys');
219
			foreach (self::DEFAULT_HOTKEYS as $hotkey => $binding) {
220
				if (!isset($this->hotkeys[$hotkey])) {
221
					$this->hotkeys[$hotkey] = $binding;
222
				}
223
			}
224
225
			foreach (Globals::getHistoryDatabases() as $databaseName => $oldColumn) {
226
				$this->oldAccountIDs[$databaseName] = $this->db->getInt($oldColumn);
227
			}
228
229
			$this->referrerID = $this->db->getInt('referral_id');
230
			$this->maxRankAchieved = $this->db->getInt('max_rank_achieved');
231
232
			$this->hofName = $this->db->getField('hof_name');
233
			$this->discordId = $this->db->getField('discord_id');
234
			$this->ircNick = $this->db->getField('irc_nick');
235
236
			$this->dateFormat = $this->db->getField('date_short');
237
			$this->timeFormat = $this->db->getField('time_short');
238
239
			$this->template = $this->db->getField('template');
240
			$this->colourScheme = $this->db->getField('colour_scheme');
241
242
			if (empty($this->hofName)) {
243
				$this->hofName = $this->login;
244
			}
245
		} else {
246
			throw new AccountNotFoundException('Account ID ' . $accountID . ' does not exist!');
247
		}
248
	}
249
250
	/**
251
	 * Check if the account is disabled.
252
	 */
253
	public function isDisabled() : array|false {
254
		$this->db->query('SELECT * FROM account_is_closed JOIN closing_reason USING(reason_id) ' .
255
			'WHERE ' . $this->SQL . ' LIMIT 1');
256
		if ($this->db->nextRecord()) {
257
			// get the expire time
258
			$expireTime = $this->db->getInt('expires');
259
260
			// are we over this time?
261
			if ($expireTime > 0 && $expireTime < Smr\Epoch::time()) {
262
				// get rid of the expire entry
263
				$this->unbanAccount();
264
				return false;
265
			}
266
			return array('Time' => $expireTime,
267
				'Reason' => $this->db->getField('reason'),
268
				'ReasonID' => $this->db->getInt('reason_id')
269
			);
270
		} else {
271
			return false;
272
		}
273
	}
274
275
	public function update() : void {
276
		$this->db->query('UPDATE account SET email = ' . $this->db->escapeString($this->email) .
277
			', validation_code = ' . $this->db->escapeString($this->validation_code) .
278
			', validated = ' . $this->db->escapeBoolean($this->validated) .
279
			', password = ' . $this->db->escapeString($this->passwordHash) .
280
			', images = ' . $this->db->escapeBoolean($this->images) .
281
			', password_reset = ' . $this->db->escapeString($this->passwordReset) .
282
			', use_ajax=' . $this->db->escapeBoolean($this->useAJAX) .
283
			', mail_banned=' . $this->db->escapeNumber($this->mailBanned) .
284
			', max_rank_achieved=' . $this->db->escapeNumber($this->maxRankAchieved) .
285
			', default_css_enabled=' . $this->db->escapeBoolean($this->defaultCSSEnabled) .
286
			', center_galaxy_map_on_player=' . $this->db->escapeBoolean($this->centerGalaxyMapOnPlayer) .
287
			', message_notifications=' . $this->db->escapeObject($this->messageNotifications, false, true) .
288
			', hotkeys=' . $this->db->escapeObject($this->hotkeys) .
289
			', last_login = ' . $this->db->escapeNumber($this->last_login) .
290
			', logging = ' . $this->db->escapeBoolean($this->logging) .
291
			', time_short = ' . $this->db->escapeString($this->timeFormat) .
292
			', date_short = ' . $this->db->escapeString($this->dateFormat) .
293
			', discord_id = ' . $this->db->escapeString($this->discordId, true) .
294
			', irc_nick = ' . $this->db->escapeString($this->ircNick, true) .
295
			', hof_name = ' . $this->db->escapeString($this->hofName) .
296
			', template = ' . $this->db->escapeString($this->template) .
297
			', colour_scheme = ' . $this->db->escapeString($this->colourScheme) .
298
			', fontsize = ' . $this->db->escapeNumber($this->fontSize) .
299
			', css_link = ' . $this->db->escapeString($this->cssLink, true) .
300
			', friendly_colour = ' . $this->db->escapeString($this->friendlyColour, true) .
301
			', neutral_colour = ' . $this->db->escapeString($this->neutralColour, true) .
302
			', enemy_colour = ' . $this->db->escapeString($this->enemyColour, true) .
303
			' WHERE ' . $this->SQL . ' LIMIT 1');
304
		$this->hasChanged = false;
305
	}
306
307
	public function updateIP() : void {
308
		$curr_ip = getIpAddress();
309
		$this->log(LOG_TYPE_LOGIN, 'logged in from ' . $curr_ip);
310
311
		// more than 50 elements in it?
312
313
		$this->db->query('SELECT time,ip FROM account_has_ip WHERE ' . $this->SQL . ' ORDER BY time ASC');
314
		if ($this->db->getNumRows() > 50 && $this->db->nextRecord()) {
315
			$delete_time = $this->db->getInt('time');
316
			$delete_ip = $this->db->getField('ip');
317
318
			$this->db->query('DELETE FROM account_has_ip
319
				WHERE '.$this->SQL . ' AND
320
				time = '.$this->db->escapeNumber($delete_time) . ' AND
321
				ip = '.$this->db->escapeString($delete_ip));
322
		}
323
		list($fi, $se, $th, $fo) = preg_split('/[.\s,]/', $curr_ip, 4);
324
		if ($curr_ip != 'unknown' && $curr_ip != 'unknown...' && $curr_ip != 'unknown, unknown') {
325
			$curr_ip = $fi . '.' . $se . '.' . $th . '.' . $fo;
326
			$host = gethostbyaddr($curr_ip);
327
		} else {
328
			$host = 'unknown';
329
		}
330
331
		// save...first make sure there isn't one for these keys (someone could double click and get error)
332
		$this->db->query('REPLACE INTO account_has_ip (account_id, time, ip, host) VALUES (' . $this->db->escapeNumber($this->account_id) . ', ' . $this->db->escapeNumber(Smr\Epoch::time()) . ', ' . $this->db->escapeString($curr_ip) . ', ' . $this->db->escapeString($host) . ')');
333
	}
334
335
	public function updateLastLogin() : void {
336
		if ($this->last_login == Smr\Epoch::time()) {
337
			return;
338
		}
339
		$this->last_login = Smr\Epoch::time();
340
		$this->hasChanged = true;
341
		$this->update();
342
	}
343
344
	public function getLastLogin() : int {
345
		return $this->last_login;
346
	}
347
348
	public function setLoggingEnabled(bool $bool) : void {
349
		if ($this->logging === $bool) {
350
			return;
351
		}
352
		$this->logging = $bool;
353
		$this->hasChanged = true;
354
	}
355
356
	public function isLoggingEnabled() : bool {
357
		return $this->logging;
358
	}
359
360
	public function isVeteranForced() : bool {
361
		return $this->veteranForced;
362
	}
363
364
	public function isVeteran() : bool {
365
		// Use maxRankAchieved to avoid a database call to get user stats.
366
		// This saves a lot of time on the CPL, Rankings, Rosters, etc.
367
		return $this->isVeteranForced() || $this->maxRankAchieved >= FLEDGLING;
368
	}
369
370
	public function isNPC() : bool {
371
		if (!isset($this->npc)) {
372
			$this->db->query('SELECT login FROM npc_logins WHERE login = ' . $this->db->escapeString($this->getLogin()) . ' LIMIT 1;');
373
			$this->npc = $this->db->nextRecord();
374
		}
375
		return $this->npc;
376
	}
377
378
	protected function getHOFData() : void {
379
		if (!isset($this->HOF)) {
380
			//Get Player HOF
381
			$this->db->query('SELECT type,sum(amount) as amount FROM player_hof WHERE ' . $this->SQL . ' AND game_id IN (SELECT game_id FROM game WHERE ignore_stats = \'FALSE\') GROUP BY type');
382
			$this->HOF = array();
383
			while ($this->db->nextRecord()) {
384
				$hof =& $this->HOF;
385
				$typeList = explode(':', $this->db->getField('type'));
386
				foreach ($typeList as $type) {
387
					if (!isset($hof[$type])) {
388
						$hof[$type] = array();
389
					}
390
					$hof =& $hof[$type];
391
				}
392
				$hof = $this->db->getFloat('amount');
393
			}
394
		}
395
	}
396
397
	/**
398
	 * Returns either the entire HOF array or the value for the given typeList.
399
	 */
400
	public function getHOF(array $typeList = null) : array|float {
401
		$this->getHOFData();
402
		if ($typeList == null) {
403
			return $this->HOF;
404
		}
405
		$hof = $this->HOF;
406
		foreach ($typeList as $type) {
407
			if (!isset($hof[$type])) {
408
				return 0;
409
			}
410
			$hof = $hof[$type];
411
		}
412
		return $hof;
413
	}
414
415
	public function getRankName() : string {
416
		$rankings = Globals::getUserRanking();
417
		if (isset($rankings[$this->getRank()])) {
418
			return $rankings[$this->getRank()];
419
		} else {
420
			return end($rankings);
421
		}
422
	}
423
424
	public function getScore() : int {
425
		if (!isset($this->score)) {
426
			$score = 0;
427
			foreach ($this->getIndividualScores() as $each) {
428
				$score += $each['Score'];
429
			}
430
			$this->score = IRound($score);
431
		}
432
		return $this->score;
433
	}
434
435
	public function getIndividualScores(SmrPlayer $player = null) : array {
436
		$gameID = 0;
437
		if ($player != null) {
438
			$gameID = $player->getGameID();
439
		}
440
		if (!isset($this->individualScores[$gameID])) {
441
			$this->individualScores[$gameID] = array();
442
			foreach (self::USER_RANKINGS_SCORE as $statScore) {
443
				if ($player == null) {
444
					$stat = $this->getHOF($statScore[0]);
445
				} else {
446
					$stat = $player->getHOF($statScore[0]);
447
				}
448
				$this->individualScores[$gameID][] = array('Stat'=>$statScore[0], 'Score'=>pow($stat * $statScore[1], self::USER_RANKINGS_EACH_STAT_POW) * $statScore[2]);
449
			}
450
		}
451
		return $this->individualScores[$gameID];
452
	}
453
454
	public function getRank() : int {
455
		$rank = ICeil(pow($this->getScore(), self::USER_RANKINGS_TOTAL_SCORE_POW) / self::USER_RANKINGS_RANK_BOUNDARY);
456
		if ($rank < 1) {
457
			$rank = 1;
458
		}
459
		if ($rank > $this->maxRankAchieved) {
460
			$this->updateMaxRankAchieved($rank);
461
		}
462
		return $rank;
463
	}
464
465
	protected function updateMaxRankAchieved(int $rank) : void {
466
		if ($rank <= $this->maxRankAchieved) {
467
			throw new Exception('Trying to set max rank achieved to a lower value: ' . $rank);
468
		}
469
		$delta = $rank - $this->maxRankAchieved;
470
		if ($this->hasReferrer()) {
471
			$this->getReferrer()->increaseSmrRewardCredits($delta * CREDITS_PER_DOLLAR);
472
		}
473
		$this->maxRankAchieved += $delta;
474
		$this->hasChanged = true;
475
		$this->update();
476
	}
477
478
	public function getReferrerID() : int {
479
		return $this->referrerID;
480
	}
481
482
	public function hasReferrer() : bool {
483
		return $this->referrerID > 0;
484
	}
485
486
	public function getReferrer() : SmrAccount {
487
		return SmrAccount::getAccount($this->getReferrerID());
488
	}
489
490
	public function log(int $log_type_id, string $msg, int $sector_id = 0) : void {
491
		if ($this->isLoggingEnabled()) {
492
			$this->db->query('INSERT INTO account_has_logs ' .
493
				'(account_id, microtime, log_type_id, message, sector_id) ' .
494
				'VALUES(' . $this->db->escapeNumber($this->account_id) . ', ' . $this->db->escapeMicrotime(Smr\Epoch::microtime()) . ', ' . $this->db->escapeNumber($log_type_id) . ', ' . $this->db->escapeString($msg) . ', ' . $this->db->escapeNumber($sector_id) . ')');
495
		}
496
	}
497
498
	protected function getSmrCreditsData() : void {
499
		if (!isset($this->credits) || !isset($this->rewardCredits)) {
500
			$this->credits = 0;
501
			$this->rewardCredits = 0;
502
			$this->db->query('SELECT * FROM account_has_credits WHERE ' . $this->SQL . ' LIMIT 1');
503
			if ($this->db->nextRecord()) {
504
				$this->credits = $this->db->getInt('credits_left');
505
				$this->rewardCredits = $this->db->getInt('reward_credits');
506
			}
507
		}
508
	}
509
510
	public function getTotalSmrCredits() : int {
511
		return $this->getSmrCredits() + $this->getSmrRewardCredits();
512
	}
513
514
	public function decreaseTotalSmrCredits(int $totalCredits) : void {
515
		if ($totalCredits == 0) {
516
			return;
517
		}
518
		if ($totalCredits < 0) {
519
			throw new Exception('You cannot use negative total credits');
520
		}
521
		if ($totalCredits > $this->getTotalSmrCredits()) {
522
			throw new Exception('You do not have that many credits in total to use');
523
		}
524
525
		$rewardCredits = $this->rewardCredits;
526
		$credits = $this->credits;
527
		$rewardCredits -= $totalCredits;
528
		if ($rewardCredits < 0) {
529
			$credits += $rewardCredits;
530
			$rewardCredits = 0;
531
		}
532
		if ($this->credits == 0 && $this->rewardCredits == 0) {
533
			$this->db->query('REPLACE INTO account_has_credits (account_id, credits_left, reward_credits) VALUES(' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber($credits) . ',' . $this->db->escapeNumber($rewardCredits) . ')');
534
		} else {
535
			$this->db->query('UPDATE account_has_credits SET credits_left=' . $this->db->escapeNumber($credits) . ', reward_credits=' . $this->db->escapeNumber($rewardCredits) . ' WHERE ' . $this->SQL . ' LIMIT 1');
536
		}
537
		$this->credits = $credits;
538
		$this->rewardCredits = $rewardCredits;
539
	}
540
541
	public function getSmrCredits() : int {
542
		$this->getSmrCreditsData();
543
		return $this->credits;
544
	}
545
546
	public function getSmrRewardCredits() : int {
547
		$this->getSmrCreditsData();
548
		return $this->rewardCredits;
549
	}
550
551
	public function setSmrCredits(int $credits) : void {
552
		if ($this->getSmrCredits() == $credits) {
553
			return;
554
		}
555
		if ($this->credits == 0 && $this->rewardCredits == 0) {
556
			$this->db->query('REPLACE INTO account_has_credits (account_id, credits_left) VALUES(' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber($credits) . ')');
557
		} else {
558
			$this->db->query('UPDATE account_has_credits SET credits_left=' . $this->db->escapeNumber($credits) . ' WHERE ' . $this->SQL . ' LIMIT 1');
559
		}
560
		$this->credits = $credits;
561
	}
562
563
	public function increaseSmrCredits(int $credits) : void {
564
		if ($credits == 0) {
565
			return;
566
		}
567
		if ($credits < 0) {
568
			throw new Exception('You cannot gain negative credits');
569
		}
570
		$this->setSmrCredits($this->getSmrCredits() + $credits);
571
	}
572
573
	public function decreaseSmrCredits(int $credits) : void {
574
		if ($credits == 0) {
575
			return;
576
		}
577
		if ($credits < 0) {
578
			throw new Exception('You cannot use negative credits');
579
		}
580
		if ($credits > $this->getSmrCredits()) {
581
			throw new Exception('You cannot use more credits than you have');
582
		}
583
		$this->setSmrCredits($this->getSmrCredits() - $credits);
584
	}
585
586
	public function setSmrRewardCredits(int $credits) : void {
587
		if ($this->getSmrRewardCredits() === $credits) {
588
			return;
589
		}
590
		if ($this->credits == 0 && $this->rewardCredits == 0) {
591
			$this->db->query('REPLACE INTO account_has_credits (account_id, reward_credits) VALUES(' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber($credits) . ')');
592
		} else {
593
			$this->db->query('UPDATE account_has_credits SET reward_credits=' . $this->db->escapeNumber($credits) . ' WHERE ' . $this->SQL . ' LIMIT 1');
594
		}
595
		$this->rewardCredits = $credits;
596
	}
597
598
	public function increaseSmrRewardCredits(int $credits) : void {
599
		if ($credits == 0) {
600
			return;
601
		}
602
		if ($credits < 0) {
603
			throw new Exception('You cannot gain negative reward credits');
604
		}
605
		$this->setSmrRewardCredits($this->getSmrRewardCredits() + $credits);
606
	}
607
608
	public function sendMessageToBox(int $boxTypeID, string $message) : void {
609
		// send him the message
610
		self::doMessageSendingToBox($this->getAccountID(), $boxTypeID, $message);
611
	}
612
613
	public static function doMessageSendingToBox(int $senderID, int $boxTypeID, string $message, int $gameID = 0) : void {
614
		$db = Smr\Database::getInstance();
615
		// send him the message
616
		$db->query('INSERT INTO message_boxes
617
			(box_type_id,game_id,message_text,
618
			sender_id,send_time) VALUES (' .
619
			$db->escapeNumber($boxTypeID) . ',' .
620
			$db->escapeNumber($gameID) . ',' .
621
			$db->escapeString($message) . ',' .
622
			$db->escapeNumber($senderID) . ',' .
623
			$db->escapeNumber(Smr\Epoch::time()) . ')'
624
		);
625
	}
626
627
	public function getAccountID() : int {
628
		return $this->account_id;
629
	}
630
631
	/**
632
	 * Return the ID associated with this account in the given history database.
633
	 */
634
	public function getOldAccountID(string $dbName) : int {
635
		return $this->oldAccountIDs[$dbName] ?? 0;
636
	}
637
638
	public function getLogin() : string {
639
		return $this->login;
640
	}
641
642
	public function getEmail() : string {
643
		return $this->email;
644
	}
645
646
	protected function setEmail(string $email) : void {
647
		if ($this->email === $email) {
648
			return;
649
		}
650
		$this->email = $email;
651
		$this->hasChanged = true;
652
	}
653
654
	/**
655
	 * Change e-mail address, unvalidate the account, and resend validation code
656
	 */
657
	public function changeEmail(string $email) : void {
658
		// get user and host for the provided address
659
		list($user, $host) = explode('@', $email);
660
661
		// check if the host got a MX or at least an A entry
662
		if (!checkdnsrr($host, 'MX') && !checkdnsrr($host, 'A')) {
663
			create_error('This is not a valid email address! The domain ' . $host . ' does not exist.');
664
		}
665
666
		if (strstr($email, ' ')) {
667
			create_error('The email is invalid! It cannot contain any spaces.');
668
		}
669
670
		$this->db->query('SELECT 1 FROM account WHERE email = ' . $this->db->escapeString($email) . ' and account_id != ' . $this->db->escapeNumber($this->getAccountID()) . ' LIMIT 1');
671
		if ($this->db->getNumRows() > 0) {
672
			create_error('This email address is already registered.');
673
		}
674
675
		$this->setEmail($email);
676
		$this->setValidationCode(random_string(10));
677
		$this->setValidated(false);
678
		$this->sendValidationEmail();
679
680
		// Remove an "Invalid email" ban (may or may not have one)
681
		if ($disabled = $this->isDisabled()) {
682
			if ($disabled['Reason'] == CLOSE_ACCOUNT_INVALID_EMAIL_REASON) {
683
				$this->unbanAccount($this);
684
			}
685
		}
686
	}
687
688
	public function sendValidationEmail() : void {
689
		// remember when we sent validation code
690
		$this->db->query('REPLACE INTO notification (notification_type, account_id, time)
691
				VALUES(\'validation_code\', '.$this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber(Smr\Epoch::time()) . ')');
692
693
		$emailMessage =
694
			'Your validation code is: ' . $this->getValidationCode() . EOL . EOL .
695
			'The Space Merchant Realms server is on the web at ' . URL;
696
697
		$mail = setupMailer();
698
		$mail->Subject = 'Space Merchant Realms Validation Code';
699
		$mail->setFrom('[email protected]', 'SMR Support');
700
		$mail->msgHTML(nl2br($emailMessage));
701
		$mail->addAddress($this->getEmail(), $this->getHofName());
702
		$mail->send();
703
	}
704
705
	public function getOffset() : int {
706
		return $this->offset;
707
	}
708
709
	public function getFontSize() : int {
710
		return $this->fontSize;
711
	}
712
713
	public function setFontSize(int $size) : void {
714
		if ($this->fontSize === $size) {
715
			return;
716
		}
717
		$this->fontSize = $size;
718
		$this->hasChanged = true;
719
	}
720
721
	// gets the extra CSS file linked in preferences
722
	public function getCssLink() : ?string {
723
		return $this->cssLink;
724
	}
725
726
	// sets the extra CSS file linked in preferences
727
	public function setCssLink(string $link) : void {
728
		if ($this->cssLink === $link) {
729
			return;
730
		}
731
		$this->cssLink = $link;
732
		$this->hasChanged = true;
733
	}
734
735
	public function getTemplate() : string {
736
		return $this->template;
737
	}
738
739
	public function setTemplate(string $template) : void {
740
		if ($this->template === $template) {
741
			return;
742
		}
743
		if (!in_array($template, Globals::getAvailableTemplates())) {
744
			throw new Exception('Template not allowed: ' . $template);
745
		}
746
		$this->template = $template;
747
		$this->hasChanged = true;
748
	}
749
750
	public function getColourScheme() : string {
751
		return $this->colourScheme;
752
	}
753
754
	public function setColourScheme(string $colourScheme) : void {
755
		if ($this->colourScheme === $colourScheme) {
756
			return;
757
		}
758
		if (!in_array($colourScheme, Globals::getAvailableColourSchemes($this->getTemplate()))) {
759
			throw new Exception('Colour scheme not allowed: ' . $colourScheme);
760
		}
761
		$this->colourScheme = $colourScheme;
762
		$this->hasChanged = true;
763
	}
764
765
	// gets the CSS URL based on the template name specified in preferences
766
	public function getCssUrl() : string {
767
		return CSS_URLS[$this->getTemplate()];
768
	}
769
770
	// gets the CSS_COLOUR URL based on the template and color scheme specified in preferences
771
	public function getCssColourUrl() : string {
772
		return CSS_COLOUR_URLS[$this->getTemplate()][$this->getColourScheme()];
773
	}
774
775
	/**
776
	 * The Hall Of Fame name is not html-escaped in the database, so to display
777
	 * it correctly we must escape html entities.
778
	 */
779
	public function getHofDisplayName(bool $linked = false) : string {
780
		$hofDisplayName = htmlspecialchars($this->getHofName());
781
		if ($linked) {
782
			return '<a href="' . $this->getPersonalHofHREF() . '">' . $hofDisplayName . '</a>';
783
		} else {
784
			return $hofDisplayName;
785
		}
786
	}
787
788
	public function getHofName() : string {
789
		return $this->hofName;
790
	}
791
792
	public function setHofName(string $name) : void {
793
		if ($this->hofName === $name) {
794
			return;
795
		}
796
		$this->hofName = $name;
797
		$this->hasChanged = true;
798
	}
799
800
	public function getIrcNick() : ?string {
801
		return $this->ircNick;
802
	}
803
804
	public function setIrcNick(?string $nick) : void {
805
		if ($this->ircNick === $nick) {
806
			return;
807
		}
808
		$this->ircNick = $nick;
809
		$this->hasChanged = true;
810
	}
811
812
	public function getDiscordId() : ?string {
813
		return $this->discordId;
814
	}
815
816
	public function setDiscordId(?string $id) : void {
817
		if ($this->discordId === $id) {
818
			return;
819
		}
820
		$this->discordId = $id;
821
		$this->hasChanged = true;
822
	}
823
824
	public function getReferralLink() : string {
825
		return URL . '/login_create.php?ref=' . $this->getAccountID();
826
	}
827
828
	/**
829
	 * Get the epoch format string including both date and time.
830
	 */
831
	public function getDateTimeFormat() : string {
832
		return $this->getDateFormat() . ' ' . $this->getTimeFormat();
833
	}
834
835
	/**
836
	 * Get the (HTML-only) epoch format string including both date and time,
837
	 * split across two lines.
838
	 */
839
	public function getDateTimeFormatSplit() : string {
840
		// We need to escape 'r' because it is a format specifier
841
		return $this->getDateFormat() . '\<b\r /\>' . $this->getTimeFormat();
842
	}
843
844
	public function getDateFormat() : string {
845
		return $this->dateFormat;
846
	}
847
848
	public function setDateFormat(string $format) : void {
849
		if ($this->dateFormat === $format) {
850
			return;
851
		}
852
		$this->dateFormat = $format;
853
		$this->hasChanged = true;
854
	}
855
856
	public function getTimeFormat() : string {
857
		return $this->timeFormat;
858
	}
859
860
	public function setTimeFormat(string $format) : void {
861
		if ($this->timeFormat === $format) {
862
			return;
863
		}
864
		$this->timeFormat = $format;
865
		$this->hasChanged = true;
866
	}
867
868
	public function getValidationCode() : string {
869
		return $this->validation_code;
870
	}
871
872
	protected function setValidationCode(string $code) : void {
873
		if ($this->validation_code === $code) {
874
			return;
875
		}
876
		$this->validation_code = $code;
877
		$this->hasChanged = true;
878
	}
879
880
	public function setValidated(bool $bool) : void {
881
		if ($this->validated === $bool) {
882
			return;
883
		}
884
		$this->validated = $bool;
885
		$this->hasChanged = true;
886
	}
887
888
	public function isValidated() : bool {
889
		return $this->validated;
890
	}
891
892
	public function isLoggedIn() : bool {
893
		$this->db->query('SELECT 1 FROM active_session WHERE account_id = ' . $this->db->escapeNumber($this->getAccountID()) . ' LIMIT 1');
894
		return $this->db->nextRecord();
895
	}
896
897
	/**
898
	 * Check if the given (plain-text) password is correct.
899
	 * Updates the password hash if necessary.
900
	 */
901
	public function checkPassword(string $password) : bool {
902
		// New (safe) password hashes will start with a $, but accounts logging
903
		// in for the first time since the transition from md5 will still have
904
		// hex-only hashes.
905
		if (strpos($this->passwordHash, '$') === 0) {
906
			$result = password_verify($password, $this->passwordHash);
907
		} else {
908
			$result = $this->passwordHash === md5($password);
909
		}
910
911
		// If password is correct, but hash algorithm has changed, update the hash.
912
		// This will also update any obsolete md5 password hashes.
913
		if ($result && password_needs_rehash($this->passwordHash, PASSWORD_DEFAULT)) {
914
			$this->setPassword($password);
915
			$this->update();
916
		}
917
918
		return $result;
919
	}
920
921
	/**
922
	 * Set the (plain-text) password for this account.
923
	 */
924
	public function setPassword(string $password) : void {
925
		$hash = password_hash($password, PASSWORD_DEFAULT);
926
		if ($this->passwordHash === $hash) {
927
			return;
928
		}
929
		$this->passwordHash = $hash;
930
		$this->generatePasswordReset();
931
		$this->hasChanged = true;
932
	}
933
934
	public function addAuthMethod(string $loginType, string $authKey) : void {
935
		$this->db->query('SELECT account_id FROM account_auth WHERE login_type=' . $this->db->escapeString($loginType) . ' AND auth_key = ' . $this->db->escapeString($authKey) . ';');
936
		if ($this->db->nextRecord()) {
937
			if ($this->db->getInt('account_id') != $this->getAccountID()) {
938
				throw new Exception('Another account already uses this form of auth.');
939
			}
940
			return;
941
		}
942
		$this->db->query('INSERT INTO account_auth values (' . $this->db->escapeNumber($this->getAccountID()) . ',' . $this->db->escapeString($loginType) . ',' . $this->db->escapeString($authKey) . ');');
943
	}
944
945
	public function generatePasswordReset() : void {
946
		$this->setPasswordReset(random_string(32));
947
	}
948
949
	public function getPasswordReset() : string {
950
		return $this->passwordReset;
951
	}
952
953
	protected function setPasswordReset(string $passwordReset) : void {
954
		if ($this->passwordReset === $passwordReset) {
955
			return;
956
		}
957
		$this->passwordReset = $passwordReset;
958
		$this->hasChanged = true;
959
	}
960
961
	public function isDisplayShipImages() : bool {
962
		return $this->images;
963
	}
964
965
	public function setDisplayShipImages(bool $bool) : void {
966
		if ($this->images === $bool) {
967
			return;
968
		}
969
		$this->images = $bool;
970
		$this->hasChanged = true;
971
	}
972
973
	public function isUseAJAX() : bool {
974
		return $this->useAJAX;
975
	}
976
977
	public function setUseAJAX(bool $bool) : void {
978
		if ($this->useAJAX === $bool) {
979
			return;
980
		}
981
		$this->useAJAX = $bool;
982
		$this->hasChanged = true;
983
	}
984
985
	public function isDefaultCSSEnabled() : bool {
986
		return $this->defaultCSSEnabled;
987
	}
988
989
	public function setDefaultCSSEnabled(bool $bool) : void {
990
		if ($this->defaultCSSEnabled === $bool) {
991
			return;
992
		}
993
		$this->defaultCSSEnabled = $bool;
994
		$this->hasChanged = true;
995
	}
996
997
	public function getHotkeys(string $hotkeyType = null) : array {
998
		if ($hotkeyType !== null) {
999
			if (isset($this->hotkeys[$hotkeyType])) {
1000
				return $this->hotkeys[$hotkeyType];
1001
			} else {
1002
				return array();
1003
			}
1004
		}
1005
		return $this->hotkeys;
1006
	}
1007
1008
	public function setHotkey(string $hotkeyType, array $bindings) : void {
1009
		if ($this->getHotkeys($hotkeyType) === $bindings) {
1010
			return;
1011
		}
1012
		$this->hotkeys[$hotkeyType] = $bindings;
1013
		$this->hasChanged = true;
1014
	}
1015
1016
	public function isReceivingMessageNotifications(int $messageTypeID) : bool {
1017
		return $this->getMessageNotifications($messageTypeID) > 0;
1018
	}
1019
1020
	public function getMessageNotifications(int $messageTypeID) : int {
1021
		return $this->messageNotifications[$messageTypeID] ?? 0;
1022
	}
1023
1024
	public function setMessageNotifications(int $messageTypeID, int $num) : void {
1025
		if ($this->getMessageNotifications($messageTypeID) == $num) {
1026
			return;
1027
		}
1028
		$this->messageNotifications[$messageTypeID] = $num;
1029
		$this->hasChanged = true;
1030
	}
1031
1032
	public function increaseMessageNotifications(int $messageTypeID, int $num) : void {
1033
		if ($num == 0) {
1034
			return;
1035
		}
1036
		if ($num < 0) {
1037
			throw new Exception('You cannot increase by a negative amount');
1038
		}
1039
		$this->setMessageNotifications($messageTypeID, $this->getMessageNotifications($messageTypeID) + $num);
1040
	}
1041
1042
	public function decreaseMessageNotifications(int $messageTypeID, int $num) : void {
1043
		if ($num == 0) {
1044
			return;
1045
		}
1046
		if ($num < 0) {
1047
			throw new Exception('You cannot decrease by a negative amount');
1048
		}
1049
		$this->setMessageNotifications($messageTypeID, $this->getMessageNotifications($messageTypeID) - $num);
1050
	}
1051
1052
	public function isCenterGalaxyMapOnPlayer() : bool {
1053
		return $this->centerGalaxyMapOnPlayer;
1054
	}
1055
1056
	public function setCenterGalaxyMapOnPlayer(bool $bool) : void {
1057
		if ($this->centerGalaxyMapOnPlayer === $bool) {
1058
			return;
1059
		}
1060
		$this->centerGalaxyMapOnPlayer = $bool;
1061
		$this->hasChanged = true;
1062
	}
1063
1064
	public function getMailBanned() : int {
1065
		return $this->mailBanned;
1066
	}
1067
1068
	public function isMailBanned() : bool {
1069
		return $this->mailBanned > Smr\Epoch::time();
1070
	}
1071
1072
	public function setMailBanned(int $time) : void {
1073
		if ($this->mailBanned === $time) {
1074
			return;
1075
		}
1076
		$this->mailBanned = $time;
1077
		$this->hasChanged = true;
1078
	}
1079
1080
	public function increaseMailBanned(int $increaseTime) : void {
1081
		$time = max(Smr\Epoch::time(), $this->getMailBanned());
1082
		$this->setMailBanned($time + $increaseTime);
1083
	}
1084
1085
	public function getPermissions() : array {
1086
		if (!isset($this->permissions)) {
1087
			$this->permissions = array();
1088
			$this->db->query('SELECT permission_id FROM account_has_permission WHERE ' . $this->SQL);
1089
			while ($this->db->nextRecord()) {
1090
				$this->permissions[$this->db->getInt('permission_id')] = true;
1091
			}
1092
		}
1093
		return $this->permissions;
1094
	}
1095
1096
	public function hasPermission(int $permissionID = null) : bool {
1097
		$permissions = $this->getPermissions();
1098
		if ($permissionID === null) {
1099
			return count($permissions) > 0;
1100
		}
1101
		return $permissions[$permissionID] ?? false;
1102
	}
1103
1104
	public function getPoints() : int {
1105
		if (!isset($this->points)) {
1106
			$this->points = 0;
1107
			$this->db->lockTable('account_has_points');
1108
			$this->db->query('SELECT * FROM account_has_points WHERE ' . $this->SQL . ' LIMIT 1');
1109
			if ($this->db->nextRecord()) {
1110
				$this->points = $this->db->getInt('points');
1111
				$lastUpdate = $this->db->getInt('last_update');
1112
				//we are gonna check for reducing points...
1113
				if ($this->points > 0 && $lastUpdate < Smr\Epoch::time() - (7 * 86400)) {
1114
					$removePoints = 0;
1115
					while ($lastUpdate < Smr\Epoch::time() - (7 * 86400)) {
1116
						$removePoints++;
1117
						$lastUpdate += (7 * 86400);
1118
					}
1119
					$this->removePoints($removePoints, $lastUpdate);
1120
				}
1121
			}
1122
			$this->db->unlock();
1123
		}
1124
		return $this->points;
1125
	}
1126
1127
	public function setPoints(int $numPoints, ?int $lastUpdate = null) : void {
1128
		$numPoints = max($numPoints, 0);
1129
		if ($this->getPoints() == $numPoints) {
1130
			return;
1131
		}
1132
		if ($this->points == 0) {
1133
			$this->db->query('INSERT INTO account_has_points (account_id, points, last_update) VALUES (' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber($numPoints) . ', ' . $this->db->escapeNumber($lastUpdate ?? Smr\Epoch::time()) . ')');
1134
		} elseif ($numPoints <= 0) {
1135
			$this->db->query('DELETE FROM account_has_points WHERE ' . $this->SQL . ' LIMIT 1');
1136
		} else {
1137
			$this->db->query('UPDATE account_has_points SET points = ' . $this->db->escapeNumber($numPoints) . (isset($lastUpdate) ? ', last_update = ' . $this->db->escapeNumber(Smr\Epoch::time()) : '') . ' WHERE ' . $this->SQL . ' LIMIT 1');
1138
		}
1139
		$this->points = $numPoints;
1140
	}
1141
1142
	public function removePoints(int $numPoints, ?int $lastUpdate = null) : void {
1143
		if ($numPoints > 0) {
1144
			$this->setPoints($this->getPoints() - $numPoints, $lastUpdate);
1145
		}
1146
	}
1147
1148
	public function addPoints(int $numPoints, SmrAccount $admin, int $reasonID, string $suspicion) : int|false {
1149
		//do we have points
1150
		$this->setPoints($this->getPoints() + $numPoints, Smr\Epoch::time());
1151
		$totalPoints = $this->getPoints();
1152
		if ($totalPoints < 10) {
1153
			return false; //leave scripts its only a warning
1154
		} elseif ($totalPoints < 20) {
1155
			$days = 2;
1156
		} elseif ($totalPoints < 30) {
1157
			$days = 4;
1158
		} elseif ($totalPoints < 50) {
1159
			$days = 7;
1160
		} elseif ($totalPoints < 75) {
1161
			$days = 15;
1162
		} elseif ($totalPoints < 100) {
1163
			$days = 30;
1164
		} elseif ($totalPoints < 125) {
1165
			$days = 60;
1166
		} elseif ($totalPoints < 150) {
1167
			$days = 120;
1168
		} elseif ($totalPoints < 175) {
1169
			$days = 240;
1170
		} elseif ($totalPoints < 200) {
1171
			$days = 480;
1172
		} else {
1173
			$days = 0; //Forever/indefinite
1174
		}
1175
1176
		if ($days == 0) {
1177
			$expireTime = 0;
1178
		} else {
1179
			$expireTime = Smr\Epoch::time() + $days * 86400;
1180
		}
1181
		$this->banAccount($expireTime, $admin, $reasonID, $suspicion);
1182
1183
		return $days;
1184
	}
1185
1186
	public function getFriendlyColour() : string {
1187
		return $this->friendlyColour;
1188
	}
1189
	public function setFriendlyColour(string $colour) : void {
1190
		$this->friendlyColour = $colour;
1191
		$this->hasChanged = true;
1192
	}
1193
	public function getNeutralColour() : string {
1194
		return $this->neutralColour;
1195
	}
1196
	public function setNeutralColour(string $colour) : void {
1197
		$this->neutralColour = $colour;
1198
		$this->hasChanged = true;
1199
	}
1200
	public function getEnemyColour() : string {
1201
		return $this->enemyColour;
1202
	}
1203
	public function setEnemyColour(string $colour) : void {
1204
		$this->enemyColour = $colour;
1205
		$this->hasChanged = true;
1206
	}
1207
1208
	public function banAccount(int $expireTime, SmrAccount $admin, int $reasonID, string $suspicion, bool $removeExceptions = false) : void {
1209
		$this->db->query('REPLACE INTO account_is_closed
1210
					(account_id, reason_id, suspicion, expires)
1211
					VALUES('.$this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber($reasonID) . ', ' . $this->db->escapeString($suspicion) . ', ' . $this->db->escapeNumber($expireTime) . ')');
1212
		$this->db->lockTable('active_session');
1213
		$this->db->query('DELETE FROM active_session WHERE ' . $this->SQL . ' LIMIT 1');
1214
		$this->db->unlock();
1215
1216
		$this->db->query('INSERT INTO account_has_closing_history
1217
						(account_id, time, admin_id, action)
1218
						VALUES(' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber(Smr\Epoch::time()) . ', ' . $this->db->escapeNumber($admin->getAccountID()) . ', ' . $this->db->escapeString('Closed') . ');');
1219
		$this->db->query('UPDATE player SET newbie_turns = 1
1220
						WHERE ' . $this->SQL . '
1221
						AND newbie_turns = 0
1222
						AND land_on_planet = ' . $this->db->escapeBoolean(false));
1223
1224
		$this->db->query('SELECT game_id FROM game JOIN player USING (game_id)
1225
						WHERE ' . $this->SQL . '
1226
						AND end_time >= ' . $this->db->escapeNumber(Smr\Epoch::time()));
1227
		while ($this->db->nextRecord()) {
1228
			$player = SmrPlayer::getPlayer($this->getAccountID(), $this->db->getInt('game_id'));
1229
			$player->updateTurns();
1230
			$player->update();
1231
		}
1232
		$this->log(LOG_TYPE_ACCOUNT_CHANGES, 'Account closed by ' . $admin->getLogin() . '.');
1233
		if ($removeExceptions !== false) {
1234
			$this->db->query('DELETE FROM account_exceptions WHERE ' . $this->SQL);
1235
		}
1236
	}
1237
1238
	public function unbanAccount(SmrAccount $admin = null, string $currException = null) {
1239
		$adminID = 0;
1240
		if ($admin !== null) {
1241
			$adminID = $admin->getAccountID();
1242
		}
1243
		$this->db->query('DELETE FROM account_is_closed WHERE ' . $this->SQL . ' LIMIT 1');
1244
		$this->db->query('INSERT INTO account_has_closing_history
1245
						(account_id, time, admin_id, action)
1246
						VALUES(' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber(Smr\Epoch::time()) . ', ' . $this->db->escapeNumber($adminID) . ', ' . $this->db->escapeString('Opened') . ')');
1247
		$this->db->query('UPDATE player SET last_turn_update = GREATEST(' . $this->db->escapeNumber(Smr\Epoch::time()) . ', last_turn_update) WHERE ' . $this->SQL);
1248
		if ($admin !== null) {
1249
			$this->log(LOG_TYPE_ACCOUNT_CHANGES, 'Account reopened by ' . $admin->getLogin() . '.');
1250
		} else {
1251
			$this->log(LOG_TYPE_ACCOUNT_CHANGES, 'Account automatically reopened.');
1252
		}
1253
		if ($currException !== null) {
1254
			$this->db->query('REPLACE INTO account_exceptions (account_id, reason)
1255
							VALUES (' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeString($currException) . ')');
1256
		}
1257
	}
1258
1259
	public function getToggleAJAXHREF() : string {
1260
		$var = Smr\Session::getInstance()->getCurrentVar();
1261
		return Page::create('toggle_processing.php', '', array('toggle'=>'AJAX', 'referrer'=>$var['body']))->href();
1262
	}
1263
1264
	public function getUserRankingHREF() : string {
1265
		return Page::create('skeleton.php', 'rankings_view.php')->href();
1266
	}
1267
1268
	public function getPersonalHofHREF() : string {
1269
		return Page::create('skeleton.php', 'hall_of_fame_player_detail.php', array('account_id' => $this->getAccountID()))->href();
1270
	}
1271
}
1272