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
Push — dependabot/composer/phpunit/ph... ( 7173cc...f8df30 )
by
unknown
12:14 queued 06:41
created

smrBBCode()   F

Complexity

Conditions 38
Paths 132

Size

Total Lines 109
Code Lines 80

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 38
eloc 80
nc 132
nop 6
dl 0
loc 109
rs 3.9
c 2
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
function parseBoolean(mixed $check): bool {
4
	// Only negative strings are not implicitly converted to the correct bool
5
	if (is_string($check) && (strcasecmp($check, 'NO') == 0 || strcasecmp($check, 'FALSE') == 0)) {
6
		return false;
7
	}
8
	return (bool)$check;
9
}
10
11
function linkCombatLog(int $logID): string {
12
	$container = Page::create('combat_log_viewer_verify.php');
13
	$container['log_id'] = $logID;
14
	return '<a href="' . $container->href() . '"><img src="images/notify.gif" width="14" height="11" border="0" title="View the combat log" /></a>';
15
}
16
17
/**
18
 * Converts a BBCode tag into some other text depending on the tag and value.
19
 * This is called in two stages: first with action BBCODE_CHECK (where the
20
 * returned value must be a boolean) and second, if the first check passes,
21
 * with action BBCODE_OUTPUT.
22
 */
23
function smrBBCode($bbParser, $action, $tagName, $default, $tagParams, $tagContent) {
0 ignored issues
show
Unused Code introduced by
The parameter $bbParser is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

23
function smrBBCode(/** @scrutinizer ignore-unused */ $bbParser, $action, $tagName, $default, $tagParams, $tagContent) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
24
	global $overrideGameID, $disableBBLinks;
25
	$session = Smr\Session::getInstance();
26
	try {
27
		switch ($tagName) {
28
			case 'combatlog':
29
				if ($action == \Nbbc\BBCode::BBCODE_CHECK) {
30
					return is_numeric($default);
31
				}
32
				$logID = (int)$default;
33
				return linkCombatLog($logID);
34
35
			case 'player':
36
				if ($action == \Nbbc\BBCode::BBCODE_CHECK) {
37
					return is_numeric($default);
38
				}
39
				$playerID = (int)$default;
40
				$bbPlayer = SmrPlayer::getPlayerByPlayerID($playerID, $overrideGameID);
41
				$showAlliance = isset($tagParams['showalliance']) ? parseBoolean($tagParams['showalliance']) : false;
42
				if ($disableBBLinks === false && $overrideGameID == $session->getGameID()) {
43
					return $bbPlayer->getLinkedDisplayName($showAlliance);
44
				}
45
				return $bbPlayer->getDisplayName($showAlliance);
46
47
			case 'alliance':
48
				if ($action == \Nbbc\BBCode::BBCODE_CHECK) {
49
					return is_numeric($default);
50
				}
51
				$allianceID = (int)$default;
52
				$alliance = SmrAlliance::getAlliance($allianceID, $overrideGameID);
53
				if ($disableBBLinks === false && $overrideGameID == $session->getGameID()) {
54
					$container = Page::create('skeleton.php');
55
					$container['alliance_id'] = $alliance->getAllianceID();
56
					if ($session->hasGame() && $alliance->getAllianceID() == $session->getPlayer()->getAllianceID()) {
57
						$container['body'] = 'alliance_mod.php';
58
					} else {
59
						$container['body'] = 'alliance_roster.php';
60
					}
61
					return create_link($container, $alliance->getAllianceDisplayName());
62
				}
63
				return $alliance->getAllianceDisplayName();
64
65
			case 'race':
66
				$raceNameID = $default;
67
				foreach (Smr\Race::getAllNames() as $raceID => $raceName) {
68
					if ((is_numeric($raceNameID) && $raceNameID == $raceID)
69
						|| $raceNameID == $raceName) {
70
						if ($action == \Nbbc\BBCode::BBCODE_CHECK) {
71
							return true;
72
						}
73
						$linked = $disableBBLinks === false && $overrideGameID == $session->getGameID();
74
						$player = $session->hasGame() ? $session->getPlayer() : null;
75
						return AbstractSmrPlayer::getColouredRaceNameOrDefault($raceID, $player, $linked);
76
					}
77
				}
78
				break;
79
80
			case 'servertimetouser':
81
				if ($action == \Nbbc\BBCode::BBCODE_CHECK) {
82
					return true;
83
				}
84
				$timeString = $default;
85
				if ($timeString != '' && ($time = strtotime($timeString)) !== false) {
86
					$time += $session->getAccount()->getOffset() * 3600;
87
					return date($session->getAccount()->getDateTimeFormat(), $time);
88
				}
89
				break;
90
91
			case 'chess':
92
				if ($action == \Nbbc\BBCode::BBCODE_CHECK) {
93
					return is_numeric($default);
94
				}
95
				$chessGameID = (int)$default;
96
				$chessGame = Smr\Chess\ChessGame::getChessGame($chessGameID);
97
				return '<a href="' . $chessGame->getPlayGameHREF() . '">chess game (' . $chessGame->getChessGameID() . ')</a>';
98
99
			case 'sector':
100
				if ($action == \Nbbc\BBCode::BBCODE_CHECK) {
101
					return is_numeric($default);
102
				}
103
104
				$sectorID = (int)$default;
105
				$sectorTag = '<span class="sectorColour">#' . $sectorID . '</span>';
106
107
				if ($disableBBLinks === false
108
					&& $session->hasGame()
109
					&& $session->getGameID() == $overrideGameID
110
					&& SmrSector::sectorExists($overrideGameID, $sectorID)) {
111
					return '<a href="' . Globals::getPlotCourseHREF($session->getPlayer()->getSectorID(), $sectorID) . '">' . $sectorTag . '</a>';
112
				}
113
				return $sectorTag;
114
115
			case 'join_alliance':
116
				if ($action == \Nbbc\BBCode::BBCODE_CHECK) {
117
					return is_numeric($default);
118
				}
119
				$allianceID = (int)$default;
120
				$alliance = SmrAlliance::getAlliance($allianceID, $overrideGameID);
121
				$container = Page::create('alliance_invite_accept_processing.php');
122
				$container['alliance_id'] = $alliance->getAllianceID();
123
				return '<div class="buttonA"><a class="buttonA" href="' . $container->href() . '">Join ' . $alliance->getAllianceDisplayName() . '</a></div>';
124
		}
125
	} catch (Throwable) {
126
		// If there's an error, we will silently display the original text
127
	}
128
	if ($action == \Nbbc\BBCode::BBCODE_CHECK) {
129
		return false;
130
	}
131
	return htmlspecialchars($tagParams['_tag']) . $tagContent . htmlspecialchars($tagParams['_endtag']);
132
}
133
134
function inify(string $text): string {
135
	return str_replace(',', '', html_entity_decode($text));
136
}
137
138
function bbifyMessage(string $message, bool $noLinks = false): string {
139
	static $bbParser;
140
	if (!isset($bbParser)) {
141
		$bbParser = new \Nbbc\BBCode();
142
		$bbParser->setEnableSmileys(false);
143
		$bbParser->removeRule('wiki');
144
		$bbParser->removeRule('img');
145
		$bbParser->setURLTarget('_blank');
146
		$bbParser->setURLTargetable('override');
147
		$bbParser->setEscapeContent(false); // don't escape HTML, needed for News etc.
148
		$smrRule = [
149
				'mode' => \Nbbc\BBCode::BBCODE_MODE_CALLBACK,
150
				'method' => 'smrBBCode',
151
				'class' => 'link',
152
				'allow_in' => ['listitem', 'block', 'columns', 'inline'],
153
				'end_tag' => \Nbbc\BBCode::BBCODE_PROHIBIT,
154
				'content' => \Nbbc\BBCode::BBCODE_PROHIBIT,
155
			];
156
		$bbParser->addRule('combatlog', $smrRule);
157
		$bbParser->addRule('player', $smrRule);
158
		$bbParser->addRule('alliance', $smrRule);
159
		$bbParser->addRule('race', $smrRule);
160
		$bbParser->addRule('servertimetouser', $smrRule);
161
		$bbParser->addRule('chess', $smrRule);
162
		$bbParser->addRule('sector', $smrRule);
163
		$bbParser->addRule('join_alliance', $smrRule);
164
	}
165
166
	global $disableBBLinks;
167
	$disableBBLinks = $noLinks;
168
169
	if (strpos($message, '[') !== false) { //We have BBCode so let's do a full parse.
170
		$message = $bbParser->parse($message);
171
	} else { //Otherwise just convert newlines
172
		$message = nl2br($message, true);
173
	}
174
	return $message;
175
}
176
177
function create_error(string $message): never {
0 ignored issues
show
Bug introduced by
The type never was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
178
	$container = Page::create('skeleton.php', 'error.php');
179
	$container['message'] = $message;
180
	if (USING_AJAX) {
181
		// To avoid the page just not refreshing when an error is encountered
182
		// during ajax updates, use javascript to auto-redirect to the
183
		// appropriate error page.
184
		$errorHREF = $container->href();
185
		// json_encode the HREF as a safety precaution
186
		$template = Smr\Template::getInstance();
187
		$template->addJavascriptForAjax('EVAL', 'location.href = ' . json_encode($errorHREF));
188
	}
189
	$container->go();
190
}
191
192
function create_link(Page|string $container, string $text, string $class = null): string {
193
	return '<a' . ($class === null ? '' : ' class="' . $class . '"') . ' href="' . (is_string($container) ? $container : $container->href()) . '">' . $text . '</a>';
194
}
195
196
function create_submit_link(Page $container, string $text): string {
197
	return '<a href="' . $container->href() . '" class="submitStyle">' . $text . '</a>';
198
}
199
200
function get_colored_text_range(float $value, float $maxValue, string $text = null, float $minValue = 0, string $type = 'Game', string $return_type = 'Normal'): string {
201
	if ($text === null) {
202
		$text = number_format($value);
203
	}
204
	if ($maxValue - $minValue == 0) {
205
		return $text;
206
	} else {
207
		$normalisedValue = IRound(510 * max(0, min($maxValue, $value) - $minValue) / ($maxValue - $minValue)) - 255;
208
	}
209
	if ($type == 'Game') {
210
		if ($normalisedValue < 0) {
211
			$r_component = 'ff';
212
			$g_component = dechex(255 + $normalisedValue);
213
			if (strlen($g_component) == 1) {
214
				$g_component = '0' . $g_component;
215
			}
216
		} elseif ($normalisedValue > 0) {
217
			$g_component = 'ff';
218
			$r_component = dechex(255 - $normalisedValue);
219
			if (strlen($r_component) == 1) {
220
				$r_component = '0' . $r_component;
221
			}
222
		} else {
223
			$r_component = 'ff';
224
			$g_component = 'ff';
225
		}
226
		$colour = $r_component . $g_component . '00';
227
		if ($return_type == 'Colour') {
228
			return $colour;
229
		}
230
		return '<span style="color:#' . $colour . '">' . $text . '</span>';
231
	} elseif ($type == 'IRC') {
232
		//IRC color codes
233
		if ($normalisedValue == 255) {
234
			$colour = '[k03]';
235
		} elseif ($normalisedValue == -255) {
236
			$colour = '[k04]';
237
		} else {
238
			$colour = '[k08]';
239
		}
240
		if ($return_type == 'Colour') {
241
			return $colour;
242
		}
243
		return $colour . $text;
244
	}
245
	throw new Exception('Unknown type: ' . $type);
246
}
247
248
function get_colored_text(float $value, string $text = null, string $type = 'Game', string $return_type = 'Normal'): string {
249
	return get_colored_text_range($value, 300, $text, -300, $type, $return_type);
250
}
251
252
function word_filter(string $string): string {
253
	static $words;
254
255
	if (!is_array($words)) {
256
		$db = Smr\Database::getInstance();
257
		$dbResult = $db->read('SELECT word_value, word_replacement FROM word_filter');
258
		$words = [];
259
		foreach ($dbResult->records() as $dbRecord) {
260
			$row = $dbRecord->getRow();
261
			$words[] = ['word_value' => '/' . str_replace('/', '\/', $row['word_value']) . '/i', 'word_replacement' => $row['word_replacement']];
262
		}
263
	}
264
265
	foreach ($words as $word) {
266
		$string = preg_replace($word['word_value'], $word['word_replacement'], $string);
267
	}
268
269
	return $string;
270
}
271
272
// choose correct pluralization based on amount
273
function pluralise(string $word, float $count = 0): string {
274
	if ($count == 1) {
275
		return $word;
276
	}
277
	if (strtolower($word) == 'is') {
278
		return 'are';
279
	}
280
	return $word . 's';
281
}
282
283
/**
284
 * This function is a hack around the old style http forward mechanism.
285
 * It is also responsible for setting most of the global variables
286
 * (see loader.php for the initialization of the globals).
287
 */
288
function do_voodoo(): never {
289
	global $lock;
290
291
	$session = Smr\Session::getInstance();
292
	$var = $session->getCurrentVar();
293
294
	if (!defined('AJAX_CONTAINER')) {
295
		define('AJAX_CONTAINER', isset($var['AJAX']) && $var['AJAX'] === true);
296
	}
297
298
	if (!AJAX_CONTAINER && USING_AJAX && $session->hasChangedSN()) {
299
		exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
300
	}
301
	//ob_clean();
302
303
	// create account object
304
	$account = $session->getAccount();
305
306
	$db = Smr\Database::getInstance();
307
308
	if ($session->hasGame()) {
309
		if (SmrGame::getGame($session->getGameID())->hasEnded()) {
310
			Page::create('game_leave_processing.php', 'game_play.php', ['errorMsg' => 'The game has ended.'])->go();
311
		}
312
		// We need to acquire locks BEFORE getting the player information
313
		// Otherwise we could be working on stale information
314
		$dbResult = $db->read('SELECT sector_id FROM player WHERE account_id=' . $db->escapeNumber($account->getAccountID()) . ' AND game_id=' . $db->escapeNumber($session->getGameID()) . ' LIMIT 1');
315
		$sector_id = $dbResult->record()->getInt('sector_id');
316
317
		global $locksFailed;
318
		if (!USING_AJAX //AJAX should never do anything that requires a lock.
319
			//&& !isset($var['url']) && ($var['body'] == 'current_sector.php' || $var['body'] == 'map_local.php') //Neither should CS or LM and they gets loaded a lot so should reduce lag issues with big groups.
320
		) {
321
			if (!$lock && !isset($locksFailed[$sector_id])) {
322
				if (!acquire_lock($sector_id)) {
323
					throw new Smr\Exceptions\UserError('Failed to acquire sector lock');
324
				}
325
				//Refetch var info in case it changed between grabbing lock.
326
				$session->fetchVarInfo();
327
				if ($session->hasCurrentVar() === false) {
328
					if (ENABLE_DEBUG) {
329
						$db->insert('debug', [
330
							'debug_type' => $db->escapeString('SPAM'),
331
							'account_id' => $db->escapeNumber($account->getAccountID()),
332
							'value' => 0,
333
							'value_2' => 0,
334
						]);
335
					}
336
					throw new Smr\Exceptions\UserError('Please do not spam click!');
337
				}
338
				$var = $session->getCurrentVar();
339
			}
340
		}
341
342
		// Now that they've acquire a lock we can move on
343
		$player = $session->getPlayer();
344
345
		if ($player->isDead() && $var['url'] != 'death_processing.php' && !isset($var['override_death'])) {
346
			Page::create('death_processing.php')->go();
347
		}
348
349
		// update turns on that player
350
		$player->updateTurns();
351
352
		if (!$player->isDead() && $player->getNewbieTurns() <= NEWBIE_TURNS_WARNING_LIMIT &&
353
			$player->getNewbieWarning() &&
354
			$var['url'] != 'newbie_warning_processing.php') {
355
			Page::create('newbie_warning_processing.php')->go();
356
		}
357
	}
358
359
	// Execute the engine files.
360
	// This is where the majority of the page-specific work is performed.
361
	$var->process();
362
363
	// Populate the template
364
	$template = Smr\Template::getInstance();
365
	if ($session->hasGame()) {
366
		$template->assign('UnderAttack', $player->removeUnderAttack());
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $player does not seem to be defined for all execution paths leading up to this point.
Loading history...
367
	}
368
369
	if ($lock) { //Only save if we have the lock.
370
		SmrSector::saveSectors();
371
		SmrShip::saveShips();
372
		SmrPlayer::savePlayers();
373
		SmrForce::saveForces();
374
		SmrPort::savePorts();
375
		SmrPlanet::savePlanets();
376
		if (class_exists('WeightedRandom', false)) {
377
			WeightedRandom::saveWeightedRandoms();
378
		}
379
		//Update session here to make sure current page $var is up to date before releasing lock.
380
		$session->update();
381
		release_lock();
382
	}
383
384
	//Nothing below this point should require the lock.
385
386
	$template->assign('TemplateBody', $var['body']);
387
	if ($session->hasGame()) {
388
		$template->assign('ThisSector', $player->getSector());
389
		$template->assign('ThisPlayer', $player);
390
		$template->assign('ThisShip', $player->getShip());
391
	}
392
	$template->assign('ThisAccount', $account);
393
	if ($account->getCssLink() != null) {
394
		$template->assign('ExtraCSSLink', $account->getCssLink());
395
	}
396
	doSkeletonAssigns($template, $db);
397
398
	// Set ajax refresh time
399
	$ajaxRefresh = $var['AllowAjax'] ?? true; // hack for bar_gambling_processing.php
400
	if (!$account->isUseAJAX()) {
401
		$ajaxRefresh = false;
402
	}
403
	if ($ajaxRefresh) {
404
		// If we can refresh, specify the refresh interval in millisecs
405
		if ($session->hasGame() && $player->canFight()) {
406
			$ajaxRefresh = AJAX_UNPROTECTED_REFRESH_TIME;
407
		} else {
408
			$ajaxRefresh = AJAX_DEFAULT_REFRESH_TIME;
409
		}
410
	}
411
	$template->assign('AJAX_ENABLE_REFRESH', $ajaxRefresh);
412
413
	$template->display($var['url'], USING_AJAX || AJAX_CONTAINER);
414
415
	$session->update();
416
417
	exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
418
}
419
420
//xdebug_dump_function_profile(2);
421
422
// This is hackish, but without row level locking it's the best we can do
423
function acquire_lock(int $sector): bool {
424
	global $lock, $locksFailed;
425
426
	if ($lock) {
427
		return true;
428
	}
429
430
	// Insert ourselves into the queue.
431
	$session = Smr\Session::getInstance();
432
	$db = Smr\Database::getInstance();
433
	$lock = $db->insert('locks_queue', [
434
		'game_id' => $db->escapeNumber($session->getGameID()),
435
		'account_id' => $db->escapeNumber($session->getAccountID()),
436
		'sector_id' => $db->escapeNumber($sector),
437
		'timestamp' => $db->escapeNumber(Smr\Epoch::time()),
438
	]);
439
440
	for ($i = 0; $i < 250; ++$i) {
441
		if (time() - Smr\Epoch::time() >= LOCK_DURATION - LOCK_BUFFER) {
442
			break;
443
		}
444
		// If there is someone else before us in the queue we sleep for a while
445
		$dbResult = $db->read('SELECT COUNT(*) FROM locks_queue WHERE lock_id<' . $db->escapeNumber($lock) . ' AND sector_id=' . $db->escapeNumber($sector) . ' AND game_id=' . $db->escapeNumber($session->getGameID()) . ' AND timestamp > ' . $db->escapeNumber(Smr\Epoch::time() - LOCK_DURATION));
446
		$locksInQueue = $dbResult->record()->getInt('COUNT(*)');
447
		if ($locksInQueue > 0) {
448
			//usleep(100000 + mt_rand(0,50000));
449
450
			// We can only have one lock in the queue, anything more means someone is screwing around
451
			$dbResult = $db->read('SELECT COUNT(*) FROM locks_queue WHERE account_id=' . $db->escapeNumber($session->getAccountID()) . ' AND sector_id=' . $db->escapeNumber($sector) . ' AND timestamp > ' . $db->escapeNumber(Smr\Epoch::time() - LOCK_DURATION));
452
			if ($dbResult->record()->getInt('COUNT(*)') > 1) {
453
				release_lock();
454
				$locksFailed[$sector] = true;
455
				throw new Smr\Exceptions\UserError('Multiple actions cannot be performed at the same time!');
456
			}
457
458
			usleep(25000 * $locksInQueue);
459
			continue;
460
		} else {
461
			return true;
462
		}
463
	}
464
465
	release_lock();
466
	$locksFailed[$sector] = true;
467
	return false;
468
}
469
470
function release_lock(): void {
471
	global $lock;
472
473
	if ($lock) {
474
		$db = Smr\Database::getInstance();
475
		$db->write('DELETE from locks_queue WHERE lock_id=' . $db->escapeNumber($lock) . ' OR timestamp<' . $db->escapeNumber(Smr\Epoch::time() - LOCK_DURATION));
476
	}
477
478
	$lock = false;
479
}
480
481
function doTickerAssigns(Smr\Template $template, SmrPlayer $player, Smr\Database $db): void {
482
	//any ticker news?
483
	if ($player->hasTickers()) {
484
		$ticker = [];
485
		$max = Smr\Epoch::time() - 60;
486
		$dateFormat = $player->getAccount()->getDateTimeFormat();
487
		if ($player->hasTicker('NEWS')) {
488
			//get recent news (5 mins)
489
			$dbResult = $db->read('SELECT time,news_message FROM news WHERE game_id = ' . $db->escapeNumber($player->getGameID()) . ' AND time >= ' . $max . ' ORDER BY time DESC LIMIT 4');
490
			foreach ($dbResult->records() as $dbRecord) {
491
				$ticker[] = [
492
					'Time' => date($dateFormat, $dbRecord->getInt('time')),
493
					'Message' => $dbRecord->getField('news_message'),
494
				];
495
			}
496
		}
497
		if ($player->hasTicker('SCOUT')) {
498
			$dbResult = $db->read('SELECT message_text,send_time FROM message
499
						WHERE account_id=' . $db->escapeNumber($player->getAccountID()) . '
500
						AND game_id=' . $db->escapeNumber($player->getGameID()) . '
501
						AND message_type_id=' . $db->escapeNumber(MSG_SCOUT) . '
502
						AND send_time>=' . $db->escapeNumber($max) . '
503
						AND sender_id NOT IN (SELECT account_id FROM player_has_ticker WHERE type=' . $db->escapeString('BLOCK') . ' AND expires > ' . $db->escapeNumber(Smr\Epoch::time()) . ' AND game_id = ' . $db->escapeNumber($player->getGameID()) . ') AND receiver_delete = \'FALSE\'
504
						ORDER BY send_time DESC
505
						LIMIT 4');
506
			foreach ($dbResult->records() as $dbRecord) {
507
				$ticker[] = [
508
					'Time' => date($dateFormat, $dbRecord->getInt('send_time')),
509
					'Message' => $dbRecord->getField('message_text'),
510
				];
511
			}
512
		}
513
		$template->assign('Ticker', $ticker);
514
	}
515
}
516
517
function doSkeletonAssigns(Smr\Template $template, Smr\Database $db): void {
518
	$session = Smr\Session::getInstance();
519
	$account = $session->getAccount();
520
521
	$template->assign('CSSLink', $account->getCssUrl());
522
	$template->assign('CSSColourLink', $account->getCssColourUrl());
523
524
	$template->assign('FontSize', $account->getFontSize() - 20);
525
	$template->assign('timeDisplay', date($account->getDateTimeFormatSplit(), Smr\Epoch::time()));
526
527
	$container = Page::create('skeleton.php');
528
529
	if ($session->hasGame()) {
530
		$player = $session->getPlayer();
531
		$template->assign('GameName', SmrGame::getGame($session->getGameID())->getName());
532
		$template->assign('GameID', $session->getGameID());
533
534
		$template->assign('PlotCourseLink', Globals::getPlotCourseHREF());
535
536
		$template->assign('TraderLink', Globals::getTraderStatusHREF());
537
538
		$template->assign('PoliticsLink', Globals::getPoliticsHREF());
539
540
		$container['body'] = 'combat_log_list.php';
541
		$template->assign('CombatLogsLink', $container->href());
542
543
		$template->assign('PlanetLink', Globals::getPlanetListHREF($player->getAllianceID()));
544
545
		$container['body'] = 'forces_list.php';
546
		$template->assign('ForcesLink', $container->href());
547
548
		$template->assign('MessagesLink', Globals::getViewMessageBoxesHREF());
549
550
		$container['body'] = 'news_read_current.php';
551
		$template->assign('ReadNewsLink', $container->href());
552
553
		$container['body'] = 'galactic_post_current.php';
554
		$template->assign('GalacticPostLink', $container->href());
555
556
		$container['body'] = 'trader_search.php';
557
		$template->assign('SearchForTraderLink', $container->href());
558
559
		$container['body'] = 'rankings_player_experience.php';
560
		$template->assign('RankingsLink', $container->href());
561
562
		$container['body'] = 'hall_of_fame_new.php';
563
		$container['game_id'] = $player->getGameID();
564
		$template->assign('CurrentHallOfFameLink', $container->href());
565
	}
566
567
	$container = Page::create('skeleton.php', 'hall_of_fame_new.php');
568
	$template->assign('HallOfFameLink', $container->href());
569
570
	$template->assign('AccountID', $account->getAccountID());
571
	$template->assign('PlayGameLink', Page::create('game_leave_processing.php', 'game_play.php')->href());
572
573
	$template->assign('LogoutLink', Page::create('logoff.php')->href());
574
575
	$container = Page::create('game_leave_processing.php', 'admin/admin_tools.php');
576
	$template->assign('AdminToolsLink', $container->href());
577
578
	$container = Page::create('skeleton.php', 'preferences.php');
579
	$template->assign('PreferencesLink', $container->href());
580
581
	$container['body'] = 'album_edit.php';
582
	$template->assign('EditPhotoLink', $container->href());
583
584
	$container['body'] = 'bug_report.php';
585
	$template->assign('ReportABugLink', $container->href());
586
587
	$container['body'] = 'contact.php';
588
	$template->assign('ContactFormLink', $container->href());
589
590
	$container['body'] = 'chat_rules.php';
591
	$template->assign('IRCLink', $container->href());
592
593
	$container['body'] = 'donation.php';
594
	$template->assign('DonateLink', $container->href());
595
596
	if ($session->hasGame()) {
597
		$dbResult = $db->read('SELECT message_type_id,COUNT(*) FROM player_has_unread_messages WHERE ' . $player->getSQL() . ' GROUP BY message_type_id');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $player does not seem to be defined for all execution paths leading up to this point.
Loading history...
598
599
		if ($dbResult->hasRecord()) {
600
			$messages = [];
601
			foreach ($dbResult->records() as $dbRecord) {
602
				$messages[$dbRecord->getInt('message_type_id')] = $dbRecord->getInt('COUNT(*)');
603
			}
604
605
			$container = Page::create('skeleton.php', 'message_view.php');
606
607
			if (isset($messages[MSG_GLOBAL])) {
608
				$container['folder_id'] = MSG_GLOBAL;
609
				$template->assign('MessageGlobalLink', $container->href());
610
				$template->assign('MessageGlobalNum', $messages[MSG_GLOBAL]);
611
			}
612
613
			if (isset($messages[MSG_PLAYER])) {
614
				$container['folder_id'] = MSG_PLAYER;
615
				$template->assign('MessagePersonalLink', $container->href());
616
				$template->assign('MessagePersonalNum', $messages[MSG_PLAYER]);
617
			}
618
619
			if (isset($messages[MSG_SCOUT])) {
620
				$container['folder_id'] = MSG_SCOUT;
621
				$template->assign('MessageScoutLink', $container->href());
622
				$template->assign('MessageScoutNum', $messages[MSG_SCOUT]);
623
			}
624
625
			if (isset($messages[MSG_POLITICAL])) {
626
				$container['folder_id'] = MSG_POLITICAL;
627
				$template->assign('MessagePoliticalLink', $container->href());
628
				$template->assign('MessagePoliticalNum', $messages[MSG_POLITICAL]);
629
			}
630
631
			if (isset($messages[MSG_ALLIANCE])) {
632
				$container['folder_id'] = MSG_ALLIANCE;
633
				$template->assign('MessageAllianceLink', $container->href());
634
				$template->assign('MessageAllianceNum', $messages[MSG_ALLIANCE]);
635
			}
636
637
			if (isset($messages[MSG_ADMIN])) {
638
				$container['folder_id'] = MSG_ADMIN;
639
				$template->assign('MessageAdminLink', $container->href());
640
				$template->assign('MessageAdminNum', $messages[MSG_ADMIN]);
641
			}
642
643
			if (isset($messages[MSG_CASINO])) {
644
				$container['folder_id'] = MSG_CASINO;
645
				$template->assign('MessageCasinoLink', $container->href());
646
				$template->assign('MessageCasinoNum', $messages[MSG_CASINO]);
647
			}
648
649
			if (isset($messages[MSG_PLANET])) {
650
				$container = Page::create('planet_msg_processing.php');
651
				$template->assign('MessagePlanetLink', $container->href());
652
				$template->assign('MessagePlanetNum', $messages[MSG_PLANET]);
653
			}
654
		}
655
656
		$container = Page::create('skeleton.php', 'trader_search_result.php');
657
		$container['player_id'] = $player->getPlayerID();
658
		$template->assign('PlayerNameLink', $container->href());
659
660
		if (is_array(Globals::getHiddenPlayers()) && in_array($player->getAccountID(), Globals::getHiddenPlayers())) {
661
			$template->assign('PlayerInvisible', true);
662
		}
663
664
		// ******* Hardware *******
665
		$container = Page::create('skeleton.php', 'configure_hardware.php');
666
667
		$template->assign('HardwareLink', $container->href());
668
669
		// ******* Forces *******
670
		$template->assign('ForceDropLink', Page::create('skeleton.php', 'forces_drop.php')->href());
671
672
		$ship = $player->getShip();
673
		if ($ship->hasMines()) {
674
			$container = Page::create('forces_drop_processing.php');
675
			$container['owner_id'] = $player->getAccountID();
676
			$container['drop_mines'] = 1;
677
			$container->addVar('body', 'referrer');
678
			$template->assign('DropMineLink', $container->href());
679
		}
680
		if ($ship->hasCDs()) {
681
			$container = Page::create('forces_drop_processing.php');
682
			$container['owner_id'] = $player->getAccountID();
683
			$container['drop_combat_drones'] = 1;
684
			$container->addVar('body', 'referrer');
685
			$template->assign('DropCDLink', $container->href());
686
		}
687
688
		if ($ship->hasSDs()) {
689
			$container = Page::create('forces_drop_processing.php');
690
			$container['owner_id'] = $player->getAccountID();
691
			$container['drop_scout_drones'] = 1;
692
			$container->addVar('body', 'referrer');
693
			$template->assign('DropSDLink', $container->href());
694
		}
695
696
		$template->assign('CargoJettisonLink', Page::create('skeleton.php', 'cargo_dump.php')->href());
697
698
		$template->assign('WeaponReorderLink', Page::create('skeleton.php', 'weapon_reorder.php')->href());
699
700
	}
701
702
	// ------- VOTING --------
703
	$voteSites = [];
704
	foreach (Smr\VoteSite::getAllSites($account->getAccountID()) as $site) {
705
		$voteSites[] = [
706
			'img' => $site->getLinkImg($session->getGameID()),
707
			'url' => $site->getLinkUrl($session->getGameID()),
708
			'sn' => $site->getSN($session->getGameID()),
709
		];
710
	}
711
	$template->assign('VoteSites', $voteSites);
712
713
	// Determine the minimum time until the next vote across all sites
714
	$minVoteWait = Smr\VoteSite::getMinTimeUntilFreeTurns($account->getAccountID());
715
	if ($minVoteWait <= 0) {
716
		$template->assign('TimeToNextVote', 'now');
717
	} else {
718
		$template->assign('TimeToNextVote', 'in ' . format_time($minVoteWait, true));
719
	}
720
721
	// ------- VERSION --------
722
	$dbResult = $db->read('SELECT * FROM version ORDER BY went_live DESC LIMIT 1');
723
	$version = '';
724
	if ($dbResult->hasRecord()) {
725
		$dbRecord = $dbResult->record();
726
		$container = Page::create('skeleton.php', 'changelog_view.php');
727
		$version = create_link($container, 'v' . $dbRecord->getInt('major_version') . '.' . $dbRecord->getInt('minor_version') . '.' . $dbRecord->getInt('patch_level'));
728
	}
729
730
	$template->assign('Version', $version);
731
	$template->assign('CurrentYear', date('Y', Smr\Epoch::time()));
732
}
733
734
/**
735
 * Convert an integer number of seconds into a human-readable time.
736
 * Seconds are omitted to avoid frequent and disruptive ajax updates.
737
 * Use short=true to use 1-letter units (e.g. "1h and 3m").
738
 * If seconds is negative, will append "ago" to the result.
739
 * If seconds is zero, will return only "now".
740
 * If seconds is <60, will prefix "less than" or "<" (HTML-safe).
741
 */
742
function format_time(int $seconds, bool $short = false): string {
743
	if ($seconds == 0) {
744
		return 'now';
745
	}
746
747
	if ($seconds < 0) {
748
		$past = true;
749
		$seconds = abs($seconds);
750
	} else {
751
		$past = false;
752
	}
753
754
	$minutes = ceil($seconds / 60);
755
	$hours = 0;
756
	$days = 0;
757
	$weeks = 0;
758
	if ($minutes >= 60) {
759
		$hours = floor($minutes / 60);
760
		$minutes = $minutes % 60;
761
	}
762
	if ($hours >= 24) {
763
		$days = floor($hours / 24);
764
		$hours = $hours % 24;
765
	}
766
	if ($days >= 7) {
767
		$weeks = floor($days / 7);
768
		$days = $days % 7;
769
	}
770
	$times = [
771
		'week' => $weeks,
772
		'day' => $days,
773
		'hour' => $hours,
774
		'minute' => $minutes,
775
	];
776
	$parts = [];
777
	foreach ($times as $unit => $amount) {
778
		if ($amount > 0) {
779
			if ($short) {
780
				$parts[] = $amount . $unit[0];
781
			} else {
782
				$parts[] = $amount . ' ' . pluralise($unit, $amount);
783
			}
784
		}
785
	}
786
787
	if (count($parts) == 1) {
788
		$result = $parts[0];
789
	} else {
790
		// e.g. 5h, 10m and 30s
791
		$result = implode(', ', array_slice($parts, 0, -1)) . ' and ' . end($parts);
792
	}
793
794
	if ($seconds < 60) {
795
		$result = ($short ? '&lt;' : 'less than ') . $result;
796
	}
797
798
	if ($past) {
799
		$result .= ' ago';
800
	}
801
	return $result;
802
}
803
804
function number_colour_format(float $number, bool $justSign = false): string {
805
	$formatted = '<span';
806
	if ($number > 0) {
807
		$formatted .= ' class="green">+';
808
	} elseif ($number < 0) {
809
		$formatted .= ' class="red">-';
810
	} else {
811
		$formatted .= '>';
812
	}
813
	if ($justSign === false) {
814
		$decimalPlaces = 0;
815
		$pos = strpos((string)$number, '.');
816
		if ($pos !== false) {
817
			$decimalPlaces = strlen(substr((string)$number, $pos + 1));
818
		}
819
		$formatted .= number_format(abs($number), $decimalPlaces);
820
	}
821
	$formatted .= '</span>';
822
	return $formatted;
823
}
824
825
826
/**
827
 * Randomly choose an array key weighted by the array values.
828
 * Probabilities are relative to the total weight. For example:
829
 *
830
 * array(
831
 *    'A' => 1, // 10% chance
832
 *    'B' => 3, // 30% chance
833
 *    'C' => 6, // 60% chance
834
 * );
835
 */
836
function getWeightedRandom(array $choices): string|int {
837
	// Normalize the weights so that their sum is much larger than 1.
838
	$maxWeight = max($choices);
839
	foreach ($choices as $key => $weight) {
840
		$choices[$key] = IRound($weight * 1000 / $maxWeight);
841
	}
842
843
	// Generate a random number that is lower than the sum of the weights.
844
	$rand = rand(1, array_sum($choices));
0 ignored issues
show
Bug introduced by
It seems like array_sum($choices) can also be of type double; however, parameter $max of rand() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

844
	$rand = rand(1, /** @scrutinizer ignore-type */ array_sum($choices));
Loading history...
845
846
	// Subtract weights from the random number until it is negative,
847
	// then return the key associated with that weight.
848
	foreach ($choices as $key => $weight) {
849
		$rand -= $weight;
850
		if ($rand <= 0) {
851
			return $key;
852
		}
853
	}
854
	throw new Exception('Internal error computing weights');
855
}
856