Merge branch 'develop' into feature/migrations-up-down

This commit is contained in:
slawkens 2024-11-07 16:34:10 +01:00
commit a5075d77dc
19 changed files with 115 additions and 71 deletions

View File

@ -1,6 +1,28 @@
# Changelog # Changelog
## [1.0-RC -23.07.2024] ## [1.0-RC.2 - 25.10.2024]
Still waiting for your reports about bugs found in this release. We are very close to stable release.
### Added
* feat: rate limit settings for blocking accounts login attempts (@gpedro, #266)
* search by email in accounts editor (https://github.com/slawkens/myaac/commit/c2ec46824621468f2a1cb4046805c485ed13fea5)
* New hooks in account manage + create (https://github.com/slawkens/myaac/commit/93641fc68ac9a5f1479329e2bd41380c19534d5d)
### Changed
* chore: drop raw queries + accounts - search by email + accounts - required min size for search by account number (@gpedro, #266)
* Use https for outfit & item images (https://github.com/slawkens/myaac/commit/71c00aa5e01fbdfd88802912e200dd1025976231)
* Do not require players & guilds tables on install (https://github.com/slawkens/myaac/commit/779aa152fa940261c9b161533946f44e288597a2)
* Do not create player if there is no players table in db (https://github.com/slawkens/myaac/commit/201f95caa8b70e88fa651eac8c3c3aa7cd765bd0)
### Fixed
* Highscore frags fixed for TFS 0.3 (@Scrollog, #263)
* Missing groups variable #262. thanks, @Scrollog for reporting (https://github.com/slawkens/myaac/commit/8d8bdb6dac6df21672ac77288fff2f2f8d6eb665)
* Verified email for login.php (@gpedro, #265)
* Warning if core.account_country is disabled (https://github.com/slawkens/myaac/commit/ab73d60c61e14a1cacdb6cfbf7f89f4bf3be0833)
## [1.0-RC.1 - 23.07.2024]
Changes since 1.0-beta: Changes since 1.0-beta:

View File

@ -26,7 +26,7 @@
if (version_compare(phpversion(), '8.1', '<')) die('PHP version 8.1 or higher is required.'); if (version_compare(phpversion(), '8.1', '<')) die('PHP version 8.1 or higher is required.');
const MYAAC = true; const MYAAC = true;
const MYAAC_VERSION = '1.0-RC'; const MYAAC_VERSION = '1.0-RC.2';
const DATABASE_VERSION = 40; const DATABASE_VERSION = 40;
const TABLE_PREFIX = 'myaac_'; const TABLE_PREFIX = 'myaac_';
define('START_TIME', microtime(true)); define('START_TIME', microtime(true));

View File

@ -105,4 +105,8 @@ $config['clients'] = [
1316, 1316,
1320, 1320,
1321, 1321,
1322,
1330,
1332,
1340,
]; ];

View File

@ -40,10 +40,15 @@ else
if(empty($errors)) if(empty($errors))
{ {
if(!admin() && !Validator::newCharacterName($name)) if(!Validator::characterName($name)) {
$errors[] = Validator::getLastError(); $errors[] = Validator::getLastError();
} }
if(!admin() && !Validator::newCharacterName($name)) {
$errors[] = Validator::getLastError();
}
}
if(empty($errors)) { if(empty($errors)) {
$player = new OTS_Player(); $player = new OTS_Player();
$player->load($player_id); $player->load($player_id);

View File

@ -148,6 +148,10 @@ if($save)
} }
} }
/**
* two hooks for compatibility
*/
$hooks->trigger(HOOK_ACCOUNT_CREATE_AFTER_SUBMIT, $params);
if (!$hooks->trigger(HOOK_ACCOUNT_CREATE_POST, $params)) { if (!$hooks->trigger(HOOK_ACCOUNT_CREATE_POST, $params)) {
return; return;
} }
@ -187,6 +191,8 @@ if($save)
$new_account->setEMail($email); $new_account->setEMail($email);
$new_account->save(); $new_account->save();
$hooks->trigger(HOOK_ACCOUNT_CREATE_AFTER_SAVED, ['account' => $new_account]);
if(USE_ACCOUNT_SALT) if(USE_ACCOUNT_SALT)
$new_account->setCustomField('salt', $salt); $new_account->setCustomField('salt', $salt);

View File

@ -42,7 +42,7 @@ if(!empty($login_account) && !empty($login_password))
} }
} }
if($account_logged->isLoaded() && encrypt((USE_ACCOUNT_SALT ? $account_logged->getCustomField('salt') : '') . $login_password) == $account_logged->getPassword() && ($limiter->enabled && !$limiter->exceeded($ip)) if($account_logged->isLoaded() && encrypt((USE_ACCOUNT_SALT ? $account_logged->getCustomField('salt') : '') . $login_password) == $account_logged->getPassword() && (!$limiter->enabled || !$limiter->exceeded($ip))
) )
{ {
if (setting('core.account_mail_verify') && (int)$account_logged->getCustomField('email_verified') !== 1) { if (setting('core.account_mail_verify') && (int)$account_logged->getCustomField('email_verified') !== 1) {

View File

@ -35,11 +35,12 @@ $settingHighscoresVocationBox = setting('core.highscores_vocation_box');
$configVocations = config('vocations'); $configVocations = config('vocations');
$configVocationsAmount = config('vocations_amount'); $configVocationsAmount = config('vocations_amount');
if($settingHighscoresVocationBox && $vocation !== 'all') $vocationId = null;
{ if($settingHighscoresVocationBox && $vocation !== 'all') {
foreach($configVocations as $id => $name) { foreach($configVocations as $id => $name) {
if(strtolower($name) == $vocation) { if(strtolower($name) == $vocation) {
$add_vocs = array($id); $vocationId = $id;
$add_vocs = [$id];
$i = $id + $configVocationsAmount; $i = $id + $configVocationsAmount;
while(isset($configVocations[$i])) { while(isset($configVocations[$i])) {
@ -175,7 +176,7 @@ if (empty($highscores)) {
$query $query
->join('player_skills', 'player_skills.player_id', '=', 'players.id') ->join('player_skills', 'player_skills.player_id', '=', 'players.id')
->where('skillid', $skill) ->where('skillid', $skill)
->addSelect('player_skills.skillid as value'); ->addSelect('player_skills.value as value');
} }
} else if ($skill == SKILL_FRAGS) // frags } else if ($skill == SKILL_FRAGS) // frags
{ {
@ -287,6 +288,7 @@ $twig->display('highscores.html.twig', [
'skillName' => ($skill == SKILL_FRAGS ? 'Frags' : ($skill == SKILL_BALANCE ? 'Balance' : getSkillName($skill))), 'skillName' => ($skill == SKILL_FRAGS ? 'Frags' : ($skill == SKILL_BALANCE ? 'Balance' : getSkillName($skill))),
'levelName' => ($skill != SKILL_FRAGS && $skill != SKILL_BALANCE ? 'Level' : ($skill == SKILL_BALANCE ? 'Balance' : 'Frags')), 'levelName' => ($skill != SKILL_FRAGS && $skill != SKILL_BALANCE ? 'Level' : ($skill == SKILL_BALANCE ? 'Balance' : 'Frags')),
'vocation' => $vocation !== 'all' ? $vocation : null, 'vocation' => $vocation !== 'all' ? $vocation : null,
'vocationId' => $vocationId,
'types' => $types, 'types' => $types,
'linkPreviousPage' => $linkPreviousPage, 'linkPreviousPage' => $linkPreviousPage,
'linkNextPage' => $linkNextPage, 'linkNextPage' => $linkNextPage,

View File

@ -62,7 +62,9 @@ if ($monsterModel && isset($monsterModel->name)) {
$elements = json_decode($monster['elements'], true); $elements = json_decode($monster['elements'], true);
$immunities = json_decode($monster['immunities'], true); $immunities = json_decode($monster['immunities'], true);
$loot = json_decode($monster['loot'], true); $loot = json_decode($monster['loot'], true);
if (!empty($loot)) {
usort($loot, 'sort_by_chance'); usort($loot, 'sort_by_chance');
}
foreach ($loot as &$item) { foreach ($loot as &$item) {
$item['name'] = getItemNameById($item['id']); $item['name'] = getItemNameById($item['id']);

View File

@ -100,7 +100,7 @@ foreach($playersOnline as $player) {
} }
$record = ''; $record = '';
if($players > 0) { if(count($players_data) > 0) {
if( setting('core.online_record')) { if( setting('core.online_record')) {
$result = null; $result = null;
$timestamp = false; $timestamp = false;
@ -114,7 +114,7 @@ if($players > 0) {
} }
} }
if($record) { if($result) {
$record = 'The maximum on this game world was ' . $result['record'] . ' players' . ($timestamp ? ' on ' . date("M d Y, H:i:s", $result['timestamp']) . '.' : '.'); $record = 'The maximum on this game world was ' . $result['record'] . ' players' . ($timestamp ? ' on ' . date("M d Y, H:i:s", $result['timestamp']) . '.' : '.');
} }
} }

View File

@ -23,32 +23,8 @@ class CreateCharacter
*/ */
public function checkName($name, &$errors) public function checkName($name, &$errors)
{ {
$minLength = setting('core.create_character_name_min_length'); if (!\Validator::characterName($name)) {
$maxLength = setting('core.create_character_name_max_length'); $errors['name'] = \Validator::getLastError();
if(empty($name)) {
$errors['name'] = 'Please enter a name for your character!';
return false;
}
if(strlen($name) > $maxLength) {
$errors['name'] = 'Name is too long. Max. length <b>' . $maxLength . '</b> letters.';
return false;
}
if(strlen($name) < $minLength) {
$errors['name'] = 'Name is too short. Min. length <b>' . $minLength . '</b> letters.';
return false;
}
$name_length = strlen($name);
if(strspn($name, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM- '") != $name_length) {
$errors['name'] = 'This name contains invalid letters, words or format. Please use only a-Z, - , \' and space.';
return false;
}
if(!preg_match("/[A-z ']/", $name)) {
$errors['name'] = 'Your name contains illegal characters.';
return false; return false;
} }

View File

@ -10,7 +10,7 @@ class RateLimit
public int $max_attempts; public int $max_attempts;
public int $ttl; public int $ttl;
public $enabled = false; public $enabled = false;
protected array $data; protected array $data = [];
public function __construct(string $key, int $max_attempts, int $ttl) public function __construct(string $key, int $max_attempts, int $ttl)
{ {
@ -76,7 +76,7 @@ class RateLimit
public function save(): void public function save(): void
{ {
global $cache; global $cache;
if (!$this->enabled) { if (!$this->enabled || !$cache->enabled()) {
return; return;
} }
@ -92,7 +92,7 @@ class RateLimit
} }
$data = []; $data = [];
if ($this->enabled && $cache->enabled()) { if ($cache->enabled()) {
$tmp = ''; $tmp = '';
if ($cache->fetch($this->key, $tmp)) { if ($cache->fetch($this->key, $tmp)) {
$data = unserialize($tmp); $data = unserialize($tmp);
@ -110,8 +110,6 @@ class RateLimit
$this->save(); $this->save();
} }
} else {
$data = [];
} }
} }

View File

@ -178,8 +178,7 @@ class Validator
*/ */
public static function characterName($name) public static function characterName($name)
{ {
if(!isset($name[0])) if(empty($name)) {
{
self::$lastError = 'Please enter character name.'; self::$lastError = 'Please enter character name.';
return false; return false;
} }
@ -250,7 +249,7 @@ class Validator
} }
} }
if(substr($name_lower, -1) == "'" || substr($name_lower, -1) == "-") { if(str_ends_with($name_lower, "'") || str_ends_with($name_lower, "-")) {
self::$lastError = 'Your name contains illegal characters.'; self::$lastError = 'Your name contains illegal characters.';
return false; return false;
} }
@ -285,7 +284,7 @@ class Validator
$words_blocked = array_merge(['--', "''","' ", " '", '- ', ' -', "-'", "'-"], setting('core.create_character_name_blocked_words')); $words_blocked = array_merge(['--', "''","' ", " '", '- ', ' -', "-'", "'-"], setting('core.create_character_name_blocked_words'));
foreach($words_blocked as $word) { foreach($words_blocked as $word) {
if(!(strpos($name_lower, $word) === false)) { if(str_contains($name_lower, $word)) {
self::$lastError = 'Your name contains illegal words.'; self::$lastError = 'Your name contains illegal words.';
return false; return false;
} }
@ -335,7 +334,7 @@ class Validator
NPCs::load(); NPCs::load();
if(NPCs::$npcs) { if(NPCs::$npcs) {
foreach (NPCs::$npcs as $npc) { foreach (NPCs::$npcs as $npc) {
if(strpos($name_lower, $npc) !== false) { if(str_contains($name_lower, $npc)) {
self::$lastError = 'Your name cannot contains NPC name.'; self::$lastError = 'Your name cannot contains NPC name.';
return false; return false;
} }

View File

@ -45,6 +45,12 @@ define('HOOK_ACCOUNT_CREATE_AFTER_TOWNS', ++$i);
define('HOOK_ACCOUNT_CREATE_BEFORE_SUBMIT_BUTTON', ++$i); define('HOOK_ACCOUNT_CREATE_BEFORE_SUBMIT_BUTTON', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_FORM', ++$i); define('HOOK_ACCOUNT_CREATE_AFTER_FORM', ++$i);
define('HOOK_ACCOUNT_CREATE_POST', ++$i); define('HOOK_ACCOUNT_CREATE_POST', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_SUBMIT', ++$i);
define('HOOK_ACCOUNT_CREATE_AFTER_SAVED', ++$i);
define('HOOK_ACCOUNT_MANAGE_BEFORE_GENERAL_INFORMATION', ++$i);
define('HOOK_ACCOUNT_MANAGE_BEFORE_PUBLIC_INFORMATION', ++$i);
define('HOOK_ACCOUNT_MANAGE_BEFORE_ACCOUNT_LOGS', ++$i);
define('HOOK_ACCOUNT_MANAGE_BEFORE_CHARACTERS', ++$i);
define('HOOK_ACCOUNT_LOGIN_BEFORE_PAGE', ++$i); define('HOOK_ACCOUNT_LOGIN_BEFORE_PAGE', ++$i);
define('HOOK_ACCOUNT_LOGIN_BEFORE_ACCOUNT', ++$i); define('HOOK_ACCOUNT_LOGIN_BEFORE_ACCOUNT', ++$i);
define('HOOK_ACCOUNT_LOGIN_AFTER_ACCOUNT', ++$i); define('HOOK_ACCOUNT_LOGIN_AFTER_ACCOUNT', ++$i);

View File

@ -142,10 +142,14 @@ function updateStatus() {
} }
} }
$status['uptime'] = $serverStatus->getUptime(); $uptime = $status['uptime'] = $serverStatus->getUptime();
$h = floor($status['uptime'] / 3600); $m = date('m', $uptime);
$m = floor(($status['uptime'] - $h * 3600) / 60); $m = $m > 1 ? "$m months, " : ($m == 1 ? 'month, ' : '');
$status['uptimeReadable'] = $h . 'h ' . $m . 'm'; $d = date('d', $uptime);
$d = $d > 1 ? "$d days, " : ($d == 1 ? 'day, ' : '');
$h = date('H', $uptime);
$min = date('i', $uptime);
$status['uptimeReadable'] = "{$m}{$d}{$h}h {$min}m";
$status['monsters'] = $serverStatus->getMonstersCount(); $status['monsters'] = $serverStatus->getMonstersCount();
$status['motd'] = $serverStatus->getMOTD(); $status['motd'] = $serverStatus->getMOTD();

View File

@ -88,6 +88,7 @@
</div> </div>
<br/><br/> <br/><br/>
{% endif %} {% endif %}
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_GENERAL_INFORMATION') }}
<a name="General+Information"></a> <a name="General+Information"></a>
<h2>General Information</h2> <h2>General Information</h2>
<table width="100%"> <table width="100%">
@ -127,6 +128,7 @@
{% endautoescape %} {% endautoescape %}
</table> </table>
<br/> <br/>
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_PUBLIC_INFORMATION') }}
<a name="Public+Information"></a> <a name="Public+Information"></a>
<h2>Public Information</h2> <h2>Public Information</h2>
<table width="100%"> <table width="100%">
@ -145,6 +147,7 @@
{% include('buttons.base.html.twig') %} {% include('buttons.base.html.twig') %}
</form> </form>
<br/> <br/>
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_ACCOUNT_LOGS') }}
<a name="Account+Logs" ></a> <a name="Account+Logs" ></a>
<h2>Action Log</h2> <h2>Action Log</h2>
<table> <table>
@ -164,6 +167,7 @@
{% endautoescape %} {% endautoescape %}
</table> </table>
<br/> <br/>
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_CHARACTERS') }}
<a name="Characters" ></a> <a name="Characters" ></a>
<h2>Character list: {{ players|length }} characters.</h2> <h2>Character list: {{ players|length }} characters.</h2>
<table> <table>

View File

@ -7,21 +7,21 @@
<tr> <tr>
<td>Filters</td> <td>Filters</td>
<td> <td>
<label for="vocationFilter">Choose a Skill</label> <label for="skillFilter">Choose a Skill</label>
<select onchange="location = this.value;" aria-label="skillFilter" id="skillFilter"> <select onchange="location = this.value;" id="skillFilter">
{% set i = 0 %} {% set i = 0 %}
{% for link, name in types %} {% for link, name in types %}
<option value="{{ getLink('highscores') }}/{{ link }}{% if vocation is not null %}{{ vocation }}{% endif %}" class="size_xs">{{ name }}</option> <option value="{{ getLink('highscores') }}/{{ link }}/{% if vocation is not null %}{{ vocation }}{% endif %}" class="size_xs" {% if list is not null and list == link %}selected{% endif %}>{{ name }}</option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>
<td> <td>
<label for="vocationFilter">Choose a vocation</label> <label for="vocationFilter">Choose a vocation</label>
<select onchange="location = this.value;" aria-label="vocationFilter" id="vocationFilter"> <select onchange="location = this.value;" id="vocationFilter">
<option value="{{ getLink('highscores') }}/{{ list }}" class="size_xs">[ALL]</option> <option value="{{ getLink('highscores') }}/{{ list }}" class="size_xs">[ALL]</option>
{% set i = 0 %} {% set i = 0 %}
{% for i in 1..config.vocations_amount %} {% for i in 1..config.vocations_amount %}
<option value="{{ getLink('highscores') }}/{{ list }}/{{ config.vocations[i]|lower }}" class="size_xs">{{ config.vocations[i]}}</option> <option value="{{ getLink('highscores') }}/{{ list }}/{{ config.vocations[i]|lower }}" class="size_xs" {% if vocationId is not null and vocationId == i %}selected{% endif %}>{{ config.vocations[i]}}</option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>

View File

@ -23,6 +23,9 @@
Currently {{ players|length }} players are online.<br/> Currently {{ players|length }} players are online.<br/>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if setting('core.online_record') %}
{{ record }}
{% endif %}
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -111,6 +111,7 @@
</div> </div>
<br/><br/> <br/><br/>
{% endif %} {% endif %}
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_GENERAL_INFORMATION') }}
<a name="General+Information" ></a> <a name="General+Information" ></a>
<div class="TopButtonContainer"> <div class="TopButtonContainer">
<div class="TopButton"> <div class="TopButton">
@ -221,6 +222,7 @@
{% endset %} {% endset %}
{% include 'tables.headline.html.twig' %} {% include 'tables.headline.html.twig' %}
<br/> <br/>
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_PUBLIC_INFORMATION') }}
<a name="Public+Information"></a> <a name="Public+Information"></a>
<div class="TopButtonContainer"> <div class="TopButtonContainer">
<div class="TopButton"> <div class="TopButton">
@ -280,6 +282,7 @@
{% endset %} {% endset %}
{% include 'tables.headline.html.twig' %} {% include 'tables.headline.html.twig' %}
<br/> <br/>
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_ACCOUNT_LOGS') }}
<a name="Account+Logs" ></a> <a name="Account+Logs" ></a>
<div class="TopButtonContainer"> <div class="TopButtonContainer">
<div class="TopButton"> <div class="TopButton">
@ -333,6 +336,7 @@
{% endset %} {% endset %}
{% include 'tables.headline.html.twig' %} {% include 'tables.headline.html.twig' %}
<br/> <br/>
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_CHARACTERS') }}
<a name="Characters" ></a> <a name="Characters" ></a>
<div class="TopButtonContainer"> <div class="TopButtonContainer">
<div class="TopButton" > <div class="TopButton" >

View File

@ -23,32 +23,36 @@ if(isset($_GET['account']))
{ {
$account = $_GET['account']; $account = $_GET['account'];
if(USE_ACCOUNT_NAME) { if(USE_ACCOUNT_NAME) {
if(!Validator::accountName($account)) if(!Validator::accountName($account)) {
error_(Validator::getLastError()); error_(Validator::getLastError());
} }
else if(!Validator::accountId($account)) }
else if(!Validator::accountId($account)) {
error_(Validator::getLastError()); error_(Validator::getLastError());
}
$_account = new OTS_Account(); $_account = new OTS_Account();
if(USE_ACCOUNT_NAME || USE_ACCOUNT_NUMBER) if(USE_ACCOUNT_NAME || USE_ACCOUNT_NUMBER) {
$_account->find($account); $_account->find($account);
else } else {
$_account->load($account); $_account->load($account);
}
$accountNameOrNumber = (USE_ACCOUNT_NAME ? ' name' : 'number'); $accountNameOrNumber = (USE_ACCOUNT_NAME ? ' name' : 'number');
if($_account->isLoaded()) if($_account->isLoaded()) {
error_("Account with this $accountNameOrNumber already exist."); error_("Account with this $accountNameOrNumber already exist.");
}
success_("Good account $accountNameOrNumber ($account)."); success_("Good account $accountNameOrNumber ($account).");
} }
else if(isset($_GET['email'])) else if(isset($_GET['email']))
{ {
$email = $_GET['email']; $email = $_GET['email'];
if(!Validator::email($email)) if(!Validator::email($email)) {
error_(Validator::getLastError()); error_(Validator::getLastError());
}
if(setting('core.account_mail_unique')) if(setting('core.account_mail_unique')) {
{
if(Account::where('email', '=', $email)->exists()) if(Account::where('email', '=', $email)->exists())
error_('Account with this e-mail already exist.'); error_('Account with this e-mail already exist.');
} }
@ -62,11 +66,13 @@ else if(isset($_GET['name']))
$name = strtolower(stripslashes($name)); $name = strtolower(stripslashes($name));
} }
if(!Validator::characterName($name)) if(!Validator::characterName($name)) {
error_(Validator::getLastError()); error_(Validator::getLastError());
}
if(!admin() && !Validator::newCharacterName($name)) if(!admin() && !Validator::newCharacterName($name)) {
error_(Validator::getLastError()); error_(Validator::getLastError());
}
$createCharacter = new CreateCharacter(); $createCharacter = new CreateCharacter();
if (!$createCharacter->checkName($name, $errors)) { if (!$createCharacter->checkName($name, $errors)) {
@ -83,16 +89,19 @@ else if(isset($_GET['password']) && isset($_GET['password_confirm'])) {
error_('Please enter the password for your new account.'); error_('Please enter the password for your new account.');
} }
if(!Validator::password($password)) if(!Validator::password($password)) {
error_(Validator::getLastError()); error_(Validator::getLastError());
}
if($password != $password_confirm) if($password != $password_confirm) {
error_('Passwords are not the same.'); error_('Passwords are not the same.');
}
success_(1); success_(1);
} }
else else {
error_('Error: no input specified.'); error_('Error: no input specified.');
}
/** /**
* Output message & exit. * Output message & exit.