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 (#1017)
by Dan
04:28
created

AbstractSmrAccount::clearCache()   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
// 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 MySqlDatabase $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 $dateShort;
73
	protected string $timeShort;
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 = MySqlDatabase::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 = MySqlDatabase::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 = MySqlDatabase::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 = MySqlDatabase::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 = MySqlDatabase::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 = MySqlDatabase::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(SmrSession::getTime()) . ',' . $db->escapeNumber($timez) . ',' . $db->escapeNumber($referral) . ',' . $db->escapeString($login) . ',' . $db->escapeObject([]) . ')');
169
		return self::getAccountByName($login);
170
	}
171
172
	public static function getUserScoreCaseStatement(MySqlDatabase $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 = MySqlDatabase::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->dateShort = $this->db->getField('date_short');
237
			$this->timeShort = $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 < SmrSession::getTime()) {
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->timeShort) .
292
			', date_short = ' . $this->db->escapeString($this->dateShort) .
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(SmrSession::getTime()) . ', ' . $this->db->escapeString($curr_ip) . ', ' . $this->db->escapeString($host) . ')');
333
	}
334
335
	public function updateLastLogin() : void {
336
		if ($this->last_login == SmrSession::getTime()) {
337
			return;
338
		}
339
		$this->last_login = SmrSession::getTime();
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(SmrSession::getMicroTime()) . ', ' . $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 = MySqlDatabase::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(SmrSession::getTime()) . ')'
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(SmrSession::getTime()) . ')');
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
	public function getShortDateFormat() : string {
829
		return $this->dateShort;
830
	}
831
832
	public function setShortDateFormat(string $format) : void {
833
		if ($this->dateShort === $format) {
834
			return;
835
		}
836
		$this->dateShort = $format;
837
		$this->hasChanged = true;
838
	}
839
840
	public function getShortTimeFormat() : string {
841
		return $this->timeShort;
842
	}
843
844
	public function setShortTimeFormat(string $format) : void {
845
		if ($this->timeShort === $format) {
846
			return;
847
		}
848
		$this->timeShort = $format;
849
		$this->hasChanged = true;
850
	}
851
852
	public function getValidationCode() : string {
853
		return $this->validation_code;
854
	}
855
856
	protected function setValidationCode(string $code) : void {
857
		if ($this->validation_code === $code) {
858
			return;
859
		}
860
		$this->validation_code = $code;
861
		$this->hasChanged = true;
862
	}
863
864
	public function setValidated(bool $bool) : void {
865
		if ($this->validated === $bool) {
866
			return;
867
		}
868
		$this->validated = $bool;
869
		$this->hasChanged = true;
870
	}
871
872
	public function isValidated() : bool {
873
		return $this->validated;
874
	}
875
876
	public function isLoggedIn() : bool {
877
		$this->db->query('SELECT 1 FROM active_session WHERE account_id = ' . $this->db->escapeNumber($this->getAccountID()) . ' LIMIT 1');
878
		return $this->db->nextRecord();
879
	}
880
881
	/**
882
	 * Check if the given (plain-text) password is correct.
883
	 * Updates the password hash if necessary.
884
	 */
885
	public function checkPassword(string $password) : bool {
886
		// New (safe) password hashes will start with a $, but accounts logging
887
		// in for the first time since the transition from md5 will still have
888
		// hex-only hashes.
889
		if (strpos($this->passwordHash, '$') === 0) {
890
			$result = password_verify($password, $this->passwordHash);
891
		} else {
892
			$result = $this->passwordHash === md5($password);
893
		}
894
895
		// If password is correct, but hash algorithm has changed, update the hash.
896
		// This will also update any obsolete md5 password hashes.
897
		if ($result && password_needs_rehash($this->passwordHash, PASSWORD_DEFAULT)) {
898
			$this->setPassword($password);
899
			$this->update();
900
		}
901
902
		return $result;
903
	}
904
905
	/**
906
	 * Set the (plain-text) password for this account.
907
	 */
908
	public function setPassword(string $password) : void {
909
		$hash = password_hash($password, PASSWORD_DEFAULT);
910
		if ($this->passwordHash === $hash) {
911
			return;
912
		}
913
		$this->passwordHash = $hash;
914
		$this->generatePasswordReset();
915
		$this->hasChanged = true;
916
	}
917
918
	public function addAuthMethod(string $loginType, string $authKey) : void {
919
		$this->db->query('SELECT account_id FROM account_auth WHERE login_type=' . $this->db->escapeString($loginType) . ' AND auth_key = ' . $this->db->escapeString($authKey) . ';');
920
		if ($this->db->nextRecord()) {
921
			if ($this->db->getInt('account_id') != $this->getAccountID()) {
922
				throw new Exception('Another account already uses this form of auth.');
923
			}
924
			return;
925
		}
926
		$this->db->query('INSERT INTO account_auth values (' . $this->db->escapeNumber($this->getAccountID()) . ',' . $this->db->escapeString($loginType) . ',' . $this->db->escapeString($authKey) . ');');
927
	}
928
929
	public function generatePasswordReset() : void {
930
		$this->setPasswordReset(random_string(32));
931
	}
932
933
	public function getPasswordReset() : string {
934
		return $this->passwordReset;
935
	}
936
937
	protected function setPasswordReset(string $passwordReset) : void {
938
		if ($this->passwordReset === $passwordReset) {
939
			return;
940
		}
941
		$this->passwordReset = $passwordReset;
942
		$this->hasChanged = true;
943
	}
944
945
	public function isDisplayShipImages() : bool {
946
		return $this->images;
947
	}
948
949
	public function setDisplayShipImages(bool $bool) : void {
950
		if ($this->images === $bool) {
951
			return;
952
		}
953
		$this->images = $bool;
954
		$this->hasChanged = true;
955
	}
956
957
	public function isUseAJAX() : bool {
958
		return $this->useAJAX;
959
	}
960
961
	public function setUseAJAX(bool $bool) : void {
962
		if ($this->useAJAX === $bool) {
963
			return;
964
		}
965
		$this->useAJAX = $bool;
966
		$this->hasChanged = true;
967
	}
968
969
	public function isDefaultCSSEnabled() : bool {
970
		return $this->defaultCSSEnabled;
971
	}
972
973
	public function setDefaultCSSEnabled(bool $bool) : void {
974
		if ($this->defaultCSSEnabled === $bool) {
975
			return;
976
		}
977
		$this->defaultCSSEnabled = $bool;
978
		$this->hasChanged = true;
979
	}
980
981
	public function getHotkeys(string $hotkeyType = null) : array {
982
		if ($hotkeyType !== null) {
983
			if (isset($this->hotkeys[$hotkeyType])) {
984
				return $this->hotkeys[$hotkeyType];
985
			} else {
986
				return array();
987
			}
988
		}
989
		return $this->hotkeys;
990
	}
991
992
	public function setHotkey(string $hotkeyType, array $bindings) : void {
993
		if ($this->getHotkeys($hotkeyType) === $bindings) {
994
			return;
995
		}
996
		$this->hotkeys[$hotkeyType] = $bindings;
997
		$this->hasChanged = true;
998
	}
999
1000
	public function isReceivingMessageNotifications(int $messageTypeID) : bool {
1001
		return $this->getMessageNotifications($messageTypeID) > 0;
1002
	}
1003
1004
	public function getMessageNotifications(int $messageTypeID) : int {
1005
		return $this->messageNotifications[$messageTypeID] ?? 0;
1006
	}
1007
1008
	public function setMessageNotifications(int $messageTypeID, int $num) : void {
1009
		if ($this->getMessageNotifications($messageTypeID) == $num) {
1010
			return;
1011
		}
1012
		$this->messageNotifications[$messageTypeID] = $num;
1013
		$this->hasChanged = true;
1014
	}
1015
1016
	public function increaseMessageNotifications(int $messageTypeID, int $num) : void {
1017
		if ($num == 0) {
1018
			return;
1019
		}
1020
		if ($num < 0) {
1021
			throw new Exception('You cannot increase by a negative amount');
1022
		}
1023
		$this->setMessageNotifications($messageTypeID, $this->getMessageNotifications($messageTypeID) + $num);
1024
	}
1025
1026
	public function decreaseMessageNotifications(int $messageTypeID, int $num) : void {
1027
		if ($num == 0) {
1028
			return;
1029
		}
1030
		if ($num < 0) {
1031
			throw new Exception('You cannot decrease by a negative amount');
1032
		}
1033
		$this->setMessageNotifications($messageTypeID, $this->getMessageNotifications($messageTypeID) - $num);
1034
	}
1035
1036
	public function isCenterGalaxyMapOnPlayer() : bool {
1037
		return $this->centerGalaxyMapOnPlayer;
1038
	}
1039
1040
	public function setCenterGalaxyMapOnPlayer(bool $bool) : void {
1041
		if ($this->centerGalaxyMapOnPlayer === $bool) {
1042
			return;
1043
		}
1044
		$this->centerGalaxyMapOnPlayer = $bool;
1045
		$this->hasChanged = true;
1046
	}
1047
1048
	public function getMailBanned() : int {
1049
		return $this->mailBanned;
1050
	}
1051
1052
	public function isMailBanned() : bool {
1053
		return $this->mailBanned > SmrSession::getTime();
1054
	}
1055
1056
	public function setMailBanned(int $time) : void {
1057
		if ($this->mailBanned === $time) {
1058
			return;
1059
		}
1060
		$this->mailBanned = $time;
1061
		$this->hasChanged = true;
1062
	}
1063
1064
	public function increaseMailBanned(int $increaseTime) : void {
1065
		$time = max(SmrSession::getTime(), $this->getMailBanned());
1066
		$this->setMailBanned($time + $increaseTime);
1067
	}
1068
1069
	public function getPermissions() : array {
1070
		if (!isset($this->permissions)) {
1071
			$this->permissions = array();
1072
			$this->db->query('SELECT permission_id FROM account_has_permission WHERE ' . $this->SQL);
1073
			while ($this->db->nextRecord()) {
1074
				$this->permissions[$this->db->getInt('permission_id')] = true;
1075
			}
1076
		}
1077
		return $this->permissions;
1078
	}
1079
1080
	public function hasPermission(int $permissionID = null) : bool {
1081
		$permissions = $this->getPermissions();
1082
		if ($permissionID === null) {
1083
			return count($permissions) > 0;
1084
		}
1085
		return $permissions[$permissionID] ?? false;
1086
	}
1087
1088
	public function getPoints() : int {
1089
		if (!isset($this->points)) {
1090
			$this->points = 0;
1091
			$this->db->lockTable('account_has_points');
1092
			$this->db->query('SELECT * FROM account_has_points WHERE ' . $this->SQL . ' LIMIT 1');
1093
			if ($this->db->nextRecord()) {
1094
				$this->points = $this->db->getInt('points');
1095
				$lastUpdate = $this->db->getInt('last_update');
1096
				//we are gonna check for reducing points...
1097
				if ($this->points > 0 && $lastUpdate < SmrSession::getTime() - (7 * 86400)) {
1098
					$removePoints = 0;
1099
					while ($lastUpdate < SmrSession::getTime() - (7 * 86400)) {
1100
						$removePoints++;
1101
						$lastUpdate += (7 * 86400);
1102
					}
1103
					$this->removePoints($removePoints, $lastUpdate);
1104
				}
1105
			}
1106
			$this->db->unlock();
1107
		}
1108
		return $this->points;
1109
	}
1110
1111
	public function setPoints(int $numPoints, ?int $lastUpdate = null) : void {
1112
		$numPoints = max($numPoints, 0);
1113
		if ($this->getPoints() == $numPoints) {
1114
			return;
1115
		}
1116
		if ($this->points == 0) {
1117
			$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 ?? SmrSession::getTime()) . ')');
1118
		} elseif ($numPoints <= 0) {
1119
			$this->db->query('DELETE FROM account_has_points WHERE ' . $this->SQL . ' LIMIT 1');
1120
		} else {
1121
			$this->db->query('UPDATE account_has_points SET points = ' . $this->db->escapeNumber($numPoints) . (isset($lastUpdate) ? ', last_update = ' . $this->db->escapeNumber(SmrSession::getTime()) : '') . ' WHERE ' . $this->SQL . ' LIMIT 1');
1122
		}
1123
		$this->points = $numPoints;
1124
	}
1125
1126
	public function removePoints(int $numPoints, ?int $lastUpdate = null) : void {
1127
		if ($numPoints > 0) {
1128
			$this->setPoints($this->getPoints() - $numPoints, $lastUpdate);
1129
		}
1130
	}
1131
1132
	public function addPoints(int $numPoints, SmrAccount $admin, int $reasonID, string $suspicion) : int|false {
1133
		//do we have points
1134
		$this->setPoints($this->getPoints() + $numPoints, SmrSession::getTime());
1135
		$totalPoints = $this->getPoints();
1136
		if ($totalPoints < 10) {
1137
			return false; //leave scripts its only a warning
1138
		} elseif ($totalPoints < 20) {
1139
			$days = 2;
1140
		} elseif ($totalPoints < 30) {
1141
			$days = 4;
1142
		} elseif ($totalPoints < 50) {
1143
			$days = 7;
1144
		} elseif ($totalPoints < 75) {
1145
			$days = 15;
1146
		} elseif ($totalPoints < 100) {
1147
			$days = 30;
1148
		} elseif ($totalPoints < 125) {
1149
			$days = 60;
1150
		} elseif ($totalPoints < 150) {
1151
			$days = 120;
1152
		} elseif ($totalPoints < 175) {
1153
			$days = 240;
1154
		} elseif ($totalPoints < 200) {
1155
			$days = 480;
1156
		} else {
1157
			$days = 0; //Forever/indefinite
1158
		}
1159
1160
		if ($days == 0) {
1161
			$expireTime = 0;
1162
		} else {
1163
			$expireTime = SmrSession::getTime() + $days * 86400;
1164
		}
1165
		$this->banAccount($expireTime, $admin, $reasonID, $suspicion);
1166
1167
		return $days;
1168
	}
1169
1170
	public function getFriendlyColour() : string {
1171
		return $this->friendlyColour;
1172
	}
1173
	public function setFriendlyColour(string $colour) : void {
1174
		$this->friendlyColour = $colour;
1175
		$this->hasChanged = true;
1176
	}
1177
	public function getNeutralColour() : string {
1178
		return $this->neutralColour;
1179
	}
1180
	public function setNeutralColour(string $colour) : void {
1181
		$this->neutralColour = $colour;
1182
		$this->hasChanged = true;
1183
	}
1184
	public function getEnemyColour() : string {
1185
		return $this->enemyColour;
1186
	}
1187
	public function setEnemyColour(string $colour) : void {
1188
		$this->enemyColour = $colour;
1189
		$this->hasChanged = true;
1190
	}
1191
1192
	public function banAccount(int $expireTime, SmrAccount $admin, int $reasonID, string $suspicion, bool $removeExceptions = false) : void {
1193
		$this->db->query('REPLACE INTO account_is_closed
1194
					(account_id, reason_id, suspicion, expires)
1195
					VALUES('.$this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber($reasonID) . ', ' . $this->db->escapeString($suspicion) . ', ' . $this->db->escapeNumber($expireTime) . ')');
1196
		$this->db->lockTable('active_session');
1197
		$this->db->query('DELETE FROM active_session WHERE ' . $this->SQL . ' LIMIT 1');
1198
		$this->db->unlock();
1199
1200
		$this->db->query('INSERT INTO account_has_closing_history
1201
						(account_id, time, admin_id, action)
1202
						VALUES(' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber(SmrSession::getTime()) . ', ' . $this->db->escapeNumber($admin->getAccountID()) . ', ' . $this->db->escapeString('Closed') . ');');
1203
		$this->db->query('UPDATE player SET newbie_turns = 1
1204
						WHERE ' . $this->SQL . '
1205
						AND newbie_turns = 0
1206
						AND land_on_planet = ' . $this->db->escapeBoolean(false));
1207
1208
		$this->db->query('SELECT game_id FROM game JOIN player USING (game_id)
1209
						WHERE ' . $this->SQL . '
1210
						AND end_time >= ' . $this->db->escapeNumber(SmrSession::getTime()));
1211
		while ($this->db->nextRecord()) {
1212
			$player = SmrPlayer::getPlayer($this->getAccountID(), $this->db->getInt('game_id'));
1213
			$player->updateTurns();
1214
			$player->update();
1215
		}
1216
		$this->log(LOG_TYPE_ACCOUNT_CHANGES, 'Account closed by ' . $admin->getLogin() . '.');
1217
		if ($removeExceptions !== false) {
1218
			$this->db->query('DELETE FROM account_exceptions WHERE ' . $this->SQL);
1219
		}
1220
	}
1221
1222
	public function unbanAccount(SmrAccount $admin = null, string $currException = null) {
1223
		$adminID = 0;
1224
		if ($admin !== null) {
1225
			$adminID = $admin->getAccountID();
1226
		}
1227
		$this->db->query('DELETE FROM account_is_closed WHERE ' . $this->SQL . ' LIMIT 1');
1228
		$this->db->query('INSERT INTO account_has_closing_history
1229
						(account_id, time, admin_id, action)
1230
						VALUES(' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber(SmrSession::getTime()) . ', ' . $this->db->escapeNumber($adminID) . ', ' . $this->db->escapeString('Opened') . ')');
1231
		$this->db->query('UPDATE player SET last_turn_update = GREATEST(' . $this->db->escapeNumber(SmrSession::getTime()) . ', last_turn_update) WHERE ' . $this->SQL);
1232
		if ($admin !== null) {
1233
			$this->log(LOG_TYPE_ACCOUNT_CHANGES, 'Account reopened by ' . $admin->getLogin() . '.');
1234
		} else {
1235
			$this->log(LOG_TYPE_ACCOUNT_CHANGES, 'Account automatically reopened.');
1236
		}
1237
		if ($currException !== null) {
1238
			$this->db->query('REPLACE INTO account_exceptions (account_id, reason)
1239
							VALUES (' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeString($currException) . ')');
1240
		}
1241
	}
1242
1243
	public function getToggleAJAXHREF() : string {
1244
		global $var;
1245
		return SmrSession::getNewHREF(create_container('toggle_processing.php', '', array('toggle'=>'AJAX', 'referrer'=>$var['body'])));
1246
	}
1247
1248
	public function getUserRankingHREF() : string {
1249
		return SmrSession::getNewHREF(create_container('skeleton.php', 'rankings_view.php'));
1250
	}
1251
1252
	public function getPersonalHofHREF() : string {
1253
		return SmrSession::getNewHREF(create_container('skeleton.php', 'hall_of_fame_player_detail.php', array('account_id' => $this->getAccountID())));
1254
	}
1255
}
1256