Merge branch 'develop' into feature/twig-hooks-filters

This commit is contained in:
slawkens
2025-03-09 21:24:47 +01:00
59 changed files with 2732 additions and 2283 deletions

View File

@@ -49,7 +49,7 @@ function warning($message, $return = false) {
return message($message, 'warning', $return);
}
function note($message, $return = false) {
return info($message, $return);
return message($message, 'note', $return);
}
function info($message, $return = false) {
return message($message, 'info', $return);
@@ -121,7 +121,7 @@ function getPlayerLink($name, $generate = true, bool $colored = false): string
function getMonsterLink($name, $generate = true): string
{
$url = BASE_URL . (setting('core.friendly_urls') ? '' : 'index.php/') . 'monsters/' . urlencode($name);
$url = BASE_URL . (setting('core.friendly_urls') ? '' : 'index.php/') . 'monsters?name=' . urlencode($name);
if(!$generate) return $url;
return generateLink($url, $name);
@@ -129,16 +129,14 @@ function getMonsterLink($name, $generate = true): string
function getHouseLink($name, $generate = true): string
{
if(is_numeric($name))
{
if(is_numeric($name)) {
$house = House::find(intval($name), ['name']);
if ($house) {
$name = $house->name;
}
}
$url = BASE_URL . (setting('core.friendly_urls') ? '' : 'index.php/') . 'houses/' . urlencode($name);
$url = BASE_URL . (setting('core.friendly_urls') ? '' : 'index.php/') . 'houses?name=' . urlencode($name);
if(!$generate) return $url;
return generateLink($url, $name);
@@ -990,31 +988,29 @@ function load_config_lua($filename)
continue;
}
$tmp_exp = explode('=', $line, 2);
if(strpos($line, 'dofile') !== false)
{
if(str_contains($line, 'dofile')) {
$delimiter = '"';
if(strpos($line, $delimiter) === false)
if(!str_contains($line, $delimiter)) {
$delimiter = "'";
}
$tmp = explode($delimiter, $line);
$result = array_merge($result, load_config_lua($config['server_path'] . $tmp[1]));
}
else if(count($tmp_exp) >= 2)
{
else if(count($tmp_exp) >= 2) {
$key = trim($tmp_exp[0]);
if(0 !== strpos($key, '--'))
{
if(!str_starts_with($key, '--')) {
$value = trim($tmp_exp[1]);
if(strpos($value, '--') !== false) {// found some deep comment
if(str_contains($value, '--')) {// found some deep comment
$value = preg_replace('/--.*$/i', '', $value);
}
if(is_numeric($value))
$result[$key] = (float) $value;
elseif(in_array(@$value[0], array("'", '"')) && in_array(@$value[strlen($value) - 1], array("'", '"')))
$result[$key] = (string) substr(substr($value, 1), 0, -1);
$result[$key] = substr(substr($value, 1), 0, -1);
elseif(in_array($value, array('true', 'false')))
$result[$key] = ($value === 'true') ? true : false;
$result[$key] = $value === 'true';
elseif(@$value[0] === '{') {
// arrays are not supported yet
// just ignore the error
@@ -1022,7 +1018,7 @@ function load_config_lua($filename)
}
else
{
foreach($result as $tmp_key => $tmp_value) // load values definied by other keys, like: dailyFragsToBlackSkull = dailyFragsToRedSkull
foreach($result as $tmp_key => $tmp_value) // load values defined by other keys, like: dailyFragsToBlackSkull = dailyFragsToRedSkull
$value = str_replace($tmp_key, $tmp_value, $value);
$ret = @eval("return $value;");
if((string) $ret == '' && trim($value) !== '""') // = parser error
@@ -1036,8 +1032,7 @@ function load_config_lua($filename)
}
}
$result = array_merge($result, isset($config['lua']) ? $config['lua'] : array());
return $result;
return array_merge($result, $config['lua'] ?? []);
}
function str_replace_first($search,$replace, $subject) {
@@ -1063,17 +1058,36 @@ function get_browser_real_ip() {
return '0';
}
function setSession($key, $data) {
$_SESSION[setting('core.session_prefix') . $key] = $data;
function setSession($key, $value = null): void {
if (!is_array($key)) {
$key = [$key => $value];
}
foreach ($key as $arrayKey => $arrayValue) {
if (is_null($arrayValue)) {
unsetSession($arrayKey);
}
else {
$_SESSION[setting('core.session_prefix') . $arrayKey] = $arrayValue;
}
}
}
function getSession($key) {
$key = setting('core.session_prefix') . $key;
return isset($_SESSION[$key]) ? $_SESSION[$key] : false;
return $_SESSION[setting('core.session_prefix') . $key] ?? null;
}
function unsetSession($key) {
function unsetSession($key): void {
unset($_SESSION[setting('core.session_prefix') . $key]);
}
function session($key): mixed {
if (is_array($key)) {
setSession($key);
return null;
}
return getSession($key);
}
function csrf(bool $return = false): string {
return CsrfToken::create($return);
}
@@ -1096,20 +1110,16 @@ function csrfProtect(): void
}
}
function getTopPlayers($limit = 5) {
function getTopPlayers($limit = 5, $skill = 'level') {
global $db;
$cache = Cache::getInstance();
if($cache->enabled()) {
$tmp = '';
if($cache->fetch('top_' . $limit . '_level', $tmp)) {
$players = unserialize($tmp);
}
if ($skill === 'level') {
$skill = 'experience';
}
if (!isset($players)) {
return Cache::remember("top_{$limit}_{$skill}", 2 * 60, function () use ($db, $limit, $skill) {
$columns = [
'id', 'name', 'level', 'vocation', 'experience',
'id', 'name', 'level', 'vocation', 'experience', 'balance',
'looktype', 'lookhead', 'lookbody', 'looklegs', 'lookfeet'
];
@@ -1117,36 +1127,27 @@ function getTopPlayers($limit = 5) {
$columns[] = 'lookaddons';
}
if ($db->hasColumn('players', 'online')) {
$columns[] = 'online';
}
$players = Player::query()
return Player::query()
->select($columns)
->withOnlineStatus()
->notDeleted()
->where('group_id', '<', setting('core.highscores_groups_hidden'))
->whereNotIn('id', setting('core.highscores_ids_hidden'))
->where('account_id', '!=', 1)
->orderByDesc('experience')
->orderByDesc($skill)
->limit($limit)
->get()
->map(function ($e, $i) {
$row = $e->toArray();
$row['online'] = $e->online_status;
$row['rank'] = $i + 1;
$row['outfit_url'] = $e->outfit_url;
unset($row['online_table']);
return $row;
})->toArray();
if($cache->enabled()) {
$cache->set('top_' . $limit . '_level', serialize($players), 120);
}
}
return $players;
});
}
function deleteDirectory($dir, $ignore = array(), $contentOnly = false) {
@@ -1694,4 +1695,7 @@ function getAccountIdentityColumn(): string
require_once SYSTEM . 'compat/base.php';
// custom functions
require SYSTEM . 'functions_custom.php';
$customFunctions = SYSTEM . 'functions_custom.php';
if (is_file($customFunctions)) {
require $customFunctions;
}

View File

@@ -50,6 +50,7 @@ $cache = Cache::getInstance();
global $hooks;
$hooks = new Hooks();
$hooks->load();
$hooks->trigger(HOOK_INIT);
// twig
require_once SYSTEM . 'twig.php';
@@ -96,8 +97,8 @@ if($config_lua_reload) {
// cache config
if($cache->enabled()) {
$cache->set('config_lua', serialize($config['lua']), 120);
$cache->set('server_path', $config['server_path']);
$cache->set('config_lua', serialize($config['lua']), 2 * 60);
$cache->set('server_path', $config['server_path'], 10 * 60);
}
}
unset($tmp);
@@ -139,7 +140,7 @@ require_once SYSTEM . 'database.php';
// verify myaac tables exists in database
if(!defined('MYAAC_INSTALL') && !$db->hasTable('myaac_account_actions')) {
throw new RuntimeException('Seems that the table myaac_account_actions of MyAAC doesn\'t exist in the database. This is a fatal error. You can try to reinstall MyAAC by visiting ' . BASE_URL . 'install');
throw new RuntimeException('Seems that the table myaac_account_actions of MyAAC doesn\'t exist in the database. This is a fatal error. You can try to reinstall MyAAC by visiting ' . (IS_CLI ? 'http://your-ip.com/' : BASE_URL) . 'install');
}
// execute migrations

View File

@@ -12,6 +12,8 @@
* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU Lesser General Public License, Version 3
*/
use MyAAC\Models\AccountAction;
/**
* OTServ account abstraction.
*
@@ -1010,26 +1012,16 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
public function logAction($action)
{
$ip = get_browser_real_ip();
if(strpos($ip, ":") === false) {
$ipv6 = '0';
}
else {
$ipv6 = $ip;
$ip = '';
}
return $this->db->exec('INSERT INTO `' . TABLE_PREFIX . 'account_actions` (`account_id`, `ip`, `ipv6`, `date`, `action`) VALUES (' . $this->db->quote($this->getId()).', ' . ($ip == '' ? '0' : $this->db->quote(ip2long($ip))) . ', (' . ($ipv6 == '0' ? $this->db->quote('') : $this->db->quote(inet_pton($ipv6))) . '), UNIX_TIMESTAMP(NOW()), ' . $this->db->quote($action).')');
AccountAction::create([
'account_id' => $this->getId(),
'ip' => get_browser_real_ip(),
'date' => time(),
'action' => $action,
]);
}
public function getActionsLog($limit1, $limit2)
{
$actions = array();
foreach($this->db->query('SELECT `ip`, `ipv6`, `date`, `action` FROM `' . TABLE_PREFIX . 'account_actions` WHERE `account_id` = ' . $this->data['id'] . ' ORDER by `date` DESC LIMIT ' . $limit1 . ', ' . $limit2 . '')->fetchAll() as $a)
$actions[] = array('ip' => $a['ip'], 'ipv6' => $a['ipv6'], 'date' => $a['date'], 'action' => $a['action']);
return $actions;
public function getActionsLog($limit) {
return AccountAction::where('account_id', $this->data['id'])->orderByDesc('date')->limit($limit)->get()->toArray();
}
/**
* Returns players iterator.

File diff suppressed because it is too large Load Diff

View File

@@ -14,12 +14,12 @@ $account_logged = new OTS_Account();
// stay-logged with sessions
$current_session = getSession('account');
if($current_session !== false)
if($current_session)
{
$account_logged->load($current_session);
if($account_logged->isLoaded() && $account_logged->getPassword() == getSession('password')
//&& (!isset($_SESSION['admin']) || admin())
&& (getSession('remember_me') !== false || getSession('last_visit') > time() - 15 * 60)) { // login for 15 minutes if "remember me" is not used
&& (getSession('remember_me') || getSession('last_visit') > time() - 15 * 60)) { // login for 15 minutes if "remember me" is not used
$logged = true;
}
else {

20
system/migrations/43.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
/**
* @var OTS_DB_MySQL $db
*/
// 2025-01-22
// change columns to VARCHAR
$up = function () use ($db) {
$db->query("UPDATE guilds set description = '' WHERE description is NULL;"); // prevent truncate error when column is NULL
$db->modifyColumn('guilds', 'description', "VARCHAR(5000) NOT NULL DEFAULT ''");
$db->query("UPDATE players set comment = '' WHERE comment is NULL;");
$db->modifyColumn('players', 'comment', "VARCHAR(5000) NOT NULL DEFAULT ''");
};
$down = function () use ($db) {
$db->modifyColumn('guilds', 'description', "TEXT NOT NULL");
$db->modifyColumn('players', 'comment', "TEXT NOT NULL");
};

27
system/migrations/44.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
/**
* @var OTS_DB_MySQL $db
*/
// 2025-02-27
// remove ipv6, change to ip (for both ipv4 + ipv6) as VARCHAR(45)
$up = function () use ($db) {
$db->query("ALTER TABLE `myaac_account_actions` DROP KEY `account_id`;");
$db->query("ALTER TABLE `myaac_account_actions` ADD COLUMN `id` INT(11) NOT NULL AUTO_INCREMENT FIRST, ADD PRIMARY KEY (`id`);");
$db->modifyColumn(TABLE_PREFIX . 'account_actions', 'ip', "VARCHAR(45) NOT NULL DEFAULT ''");
$db->query("UPDATE `" . TABLE_PREFIX . "account_actions` SET `ip` = INET_NTOA(`ip`) WHERE `ip` != '0';");
$db->query("UPDATE `" . TABLE_PREFIX . "account_actions` SET `ip` = INET6_NTOA(`ipv6`) WHERE `ip` = '0';");
$db->dropColumn(TABLE_PREFIX . 'account_actions', 'ipv6');
};
$down = function () use ($db) {
$db->query("ALTER TABLE `" . TABLE_PREFIX . "account_actions` DROP `id`;");
$db->query("ALTER TABLE `" . TABLE_PREFIX . "account_actions` ADD KEY (`account_id`);");
$db->addColumn(TABLE_PREFIX . 'account_actions', 'ipv6', "BINARY(16) NOT NULL DEFAULT 0x00000000000000000000000000000000 AFTER ip");
$db->query("UPDATE `" . TABLE_PREFIX . "account_actions` SET `ipv6` = INET6_ATON(ip) WHERE NOT IS_IPV4(`ip`);");
$db->query("UPDATE `" . TABLE_PREFIX . "account_actions` SET `ip` = INET_ATON(`ip`) WHERE IS_IPV4(`ip`);");
$db->query("UPDATE `" . TABLE_PREFIX . "account_actions` SET `ip` = 0 WHERE `ipv6` != 0x00000000000000000000000000000000;");
$db->modifyColumn(TABLE_PREFIX . 'account_actions', 'ip', "INT(11) UNSIGNED NOT NULL DEFAULT 0;");
};

View File

@@ -85,12 +85,8 @@ if($email_new_time > 1)
}
}
$actions = array();
foreach($account_logged->getActionsLog(0, 1000) as $action) {
$actions[] = array('action' => $action['action'], 'date' => $action['date'], 'ip' => $action['ip'] != 0 ? long2ip($action['ip']) : inet_ntop($action['ipv6']));
}
$actions = $account_logged->getActionsLog(1000);
$players = array();
/** @var OTS_Players_List $account_players */
$account_players = $account_logged->getPlayersList();
$account_players->orderBy('id');

View File

@@ -21,9 +21,9 @@ $settingHighscoresCountryBox = setting('core.highscores_country_box');
if(config('account_country') && $settingHighscoresCountryBox)
require SYSTEM . 'countries.conf.php';
$list = $_GET['list'] ?? 'experience';
$list = urldecode($_GET['list'] ?? 'experience');
$page = $_GET['page'] ?? 1;
$vocation = $_GET['vocation'] ?? 'all';
$vocation = urldecode($_GET['vocation'] ?? 'all');
if(!is_numeric($page) || $page < 1 || $page > PHP_INT_MAX) {
$page = 1;
@@ -207,6 +207,7 @@ if (empty($highscores)) {
$tmp = $row->toArray();
$tmp['online'] = $row->online_status;
$tmp['vocation'] = $row->vocation_name;
$tmp['outfit_url'] = $row->outfit_url; // @phpstan-ignore-line
unset($tmp['online_table']);
return $tmp;
@@ -233,14 +234,10 @@ foreach($highscores as $id => &$player)
$player['experience'] = number_format($player['experience']);
}
if(!$settingHighscoresVocation) {
unset($player['vocation']);
}
$player['link'] = getPlayerLink($player['name'], false);
$player['flag'] = getFlagImage($player['country']);
if($settingHighscoresOutfit) {
$player['outfit'] = '<img style="position:absolute;margin-top:' . (in_array($player['looktype'], setting('core.outfit_images_wrong_looktypes')) ? '-15px;margin-left:5px' : '-45px;margin-left:-25px') . ';" src="' . setting('core.outfit_images_url') . '?id=' . $player['looktype'] . ($outfit_addons ? '&addons=' . $player['lookaddons'] : '') . '&head=' . $player['lookhead'] . '&body=' . $player['lookbody'] . '&legs=' . $player['looklegs'] . '&feet=' . $player['lookfeet'] . '" alt="" />';
$player['outfit'] = '<img style="position:absolute;margin-top:' . (in_array($player['looktype'], setting('core.outfit_images_wrong_looktypes')) ? '-15px;margin-left:5px' : '-45px;margin-left:-25px') . ';" src="' . $player['outfit_url'] . '" alt="" />';
}
$player['rank'] = $offset + $i;
}

View File

@@ -30,11 +30,11 @@ $state = '';
$order = '';
$type = '';
if(isset($_GET['page']) && $_GET['page'] == 'view' && isset($_REQUEST['house']))
if(isset($_REQUEST['name']))
{
$beds = array("", "one", "two", "three", "fourth", "fifth");
$houseName = $_REQUEST['house'];
$houseId = (Validator::number($_REQUEST['house']) ? $_REQUEST['house'] : -1);
$houseName = urldecode($_REQUEST['name']);
$houseId = (Validator::number($_REQUEST['name']) ? $_REQUEST['name'] : -1);
$selectHouse = $db->query('SELECT * FROM ' . $db->tableName('houses') . ' WHERE ' . $db->fieldName('name') . ' LIKE ' . $db->quote($houseName) . ' OR `id` = ' . $db->quote($houseId));
$house = array();
@@ -187,7 +187,7 @@ if(isset($_POST['town']) && isset($_POST['state']) && isset($_POST['order']) &&
$houseRent = 'Free';
}
$houses[] = array('owner' => $owner, 'name' => $house['name'], 'size' => ($hasTilesColumn ? $house['tiles'] : $house['size']), 'rent' => $house['rent'], 'rentedBy' => $houseRent);
$houses[] = array('owner' => $owner, 'name' => $house['name'], 'size' => ($hasTilesColumn ? $house['tiles'] : $house['size']), 'rent' => $house['rent'], 'rentedBy' => $houseRent, 'link' => getHouseLink($house['name'], false));
}
$housesSearch = true;

View File

@@ -88,7 +88,7 @@ if(isset($_GET['archive']))
foreach($news_DB as $news)
{
$newses[] = array(
'link' => getLink('news') . '/' . $news['id'],
'link' => getLink('news/archive') . '/' . $news['id'],
'icon_id' => $categories[$news['category']]['icon_id'],
'title' => stripslashes($news['title']),
'date' => $news['date']

View File

@@ -39,10 +39,10 @@ return [
[['GET', 'POST'], 'guilds/{guild:string}', 'guilds/show.php'],
['GET', 'highscores/{list:alphanum}/{vocation:alphanum}/{page:int}', 'highscores.php'],
['GET', 'highscores/{list:alphanum}/{page:int}', 'highscores.php'],
['GET', 'highscores/{list:alphanum}/{vocation:alphanum}', 'highscores.php'],
['GET', 'highscores/{list:alphanum}', 'highscores.php'],
['GET', 'highscores/{list:string}/{vocation:string}/{page:int}', 'highscores.php'],
['GET', 'highscores/{list:string}/{page:int}', 'highscores.php'],
['GET', 'highscores/{list:string}/{vocation:string}', 'highscores.php'],
['GET', 'highscores/{list:string}', 'highscores.php'],
/*
'/^polls\/[0-9]+\/?$/' => array('subtopic' => 'polls', 'id' => '$1'),
'/^spells\/[A-Za-z0-9-_%]+\/[A-Za-z0-9-_]+\/?$/' => array('subtopic' => 'spells', 'vocation' => '$1', 'order' => '$2'),

View File

@@ -348,7 +348,7 @@ return [
],
'database_password' => [
'name' => 'Database Password',
'type' => 'text',
'type' => 'password',
'default' => '',
'show_if' => [
'database_overwrite', '=', 'true'
@@ -509,7 +509,7 @@ Sent by MyAAC,<br/>
'smtp_port' => [
'name' => 'SMTP Host',
'type' => 'number',
'desc' => '25 (default) / 465 (ssl, GMail) / 587 (tls, Microsoft Outlook)',
'desc' => '25 (default) / 587 (tls - GMail, Microsoft Outlook)',
'default' => 25,
'show_if' => [
'mail_enabled', '=', 'true'
@@ -536,7 +536,8 @@ Sent by MyAAC,<br/>
'smtp_pass' => [
'name' => 'SMTP Password',
'type' => 'password',
'desc' => 'Here your email password to authenticate with SMTP',
'desc' => 'Here your email password to authenticate with SMTP.' . PHP_EOL
. 'For GMail use generated App password - https://myaccount.google.com/apppasswords.',
'default' => '',
'show_if' => [
'mail_enabled', '=', 'true'
@@ -546,7 +547,8 @@ Sent by MyAAC,<br/>
'name' => 'SMTP Security',
'type' => 'options',
'options' => ['None', 'SSL', 'TLS'],
'desc' => 'What kind of encryption to use on the SMTP connection',
'desc' => 'What kind of encryption to use on the SMTP connection.' . PHP_EOL
. '(Gmail, Outlook - tls).',
'default' => 0,
'show_if' => [
'mail_enabled', '=', 'true'
@@ -1401,7 +1403,7 @@ Sent by MyAAC,<br/>
'name' => 'Outfit Images URL',
'type' => 'text',
'desc' => 'Set to animoutfit.php for animated outfit',
'default' => 'https://outfit-images.ots.me/outfit.php',
'default' => 'https://outfit-images.ots.me/latest/outfit.php',
],
'outfit_images_wrong_looktypes' => [
'name' => 'Outfit Images Wrong Looktypes',

View File

@@ -105,9 +105,6 @@ class CreateCharacter
* @param array $errors
* @return bool
* @throws \E_OTS_NotLoaded
* @throws \Twig_Error_Loader
* @throws \Twig_Error_Runtime
* @throws \Twig_Error_Syntax
*/
public function doCreate($name, $sex, $vocation, $town, $account, &$errors)
{

View File

@@ -9,6 +9,6 @@ class AccountAction extends Model {
public $timestamps = false;
protected $fillable = ['account_id', 'ip', 'ipv6', 'date', 'action'];
protected $fillable = ['account_id', 'ip', 'date', 'action'];
}

View File

@@ -8,6 +8,13 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
* @property int $level
* @property int $vocation
* @property int $online
* @property int $looktype
* @property int $lookhead
* @property int $lookbody
* @property int $looklegs
* @property int $lookfeet
* @property int $lookaddons
* @property string $outfit_url
* @property hasOne $onlineTable
*/
class Player extends Model {
@@ -42,8 +49,8 @@ class Player extends Model {
public function getVocationNameAttribute()
{
$vocation = $this->vocation;
if (isset($this->promotion)) {
$vocation *= $this->promotion;
if (isset($this->promotion) && $this->promotion > 0) {
$vocation += ($this->promotion * setting('core.vocations_amount'));
}
return config('vocations')[$vocation] ?? 'Unknown';
@@ -73,11 +80,21 @@ class Player extends Model {
$query->where($column, 0);
}
public function scopeWithOnlineStatus($query) {
public function scopeWithOnlineStatus($query)
{
global $db;
$query->when($db->hasTable('players_online'), function ($query) {
$query->with('onlineTable');
});
if ($db->hasColumn('players', 'online')) {
$query->addSelect('online');
}
else {
$query->when($db->hasTable('players_online'), function ($query) {
$query->with('onlineTable');
});
}
}
public function getOutfitUrlAttribute() {
return setting('core.outfit_images_url') . '?id=' . $this->looktype . (!empty($this->lookaddons) ? '&addons=' . $this->lookaddons : '') . '&head=' . $this->lookhead . '&body=' . $this->lookbody . '&legs=' . $this->looklegs . '&feet=' . $this->lookfeet;
}
public function getOnlineStatusAttribute()

View File

@@ -72,24 +72,32 @@ class Settings implements \ArrayAccess
}
}
$this->errors = [];
ModelsSettings::where('name', $pluginName)->delete();
foreach ($values as $key => $value) {
$errorMessage = '';
if (isset($settings['settings'][$key]['callbacks']['beforeSave']) && !$settings['settings'][$key]['callbacks']['beforeSave']($key, $value, $errorMessage)) {
$this->errors[] = $errorMessage;
continue;
}
global $db;
try {
$db->beginTransaction();
$this->errors = [];
ModelsSettings::where('name', $pluginName)->delete();
foreach ($values as $key => $value) {
$errorMessage = '';
if (isset($settings['settings'][$key]['callbacks']['beforeSave']) && !$settings['settings'][$key]['callbacks']['beforeSave']($key, $value, $errorMessage)) {
$this->errors[] = $errorMessage;
continue;
}
try {
ModelsSettings::create([
'name' => $pluginName,
'key' => $key,
'value' => $value
]);
} catch (\PDOException $error) {
$this->errors[] = 'Error while saving setting (' . $pluginName . ' - ' . $key . '): ' . $error->getMessage();
}
$db->commit();
} catch (\Exception $error) {
$db->rollBack();
$this->errors[] = 'Error while saving settings (' . $pluginName . ')<br/>' . $error->getMessage();
return false;
}
$this->clearCache();
@@ -247,7 +255,15 @@ class Settings implements \ArrayAccess
$min = $max = $step = '';
}
if ($setting['type'] === 'password') {
echo '<div class="input-group" id="show-hide-' . $key . '">';
}
echo '<input class="form-control" type="' . $setting['type'] . '" name="settings[' . $key . ']" value="' . ($settingsDb[$key] ?? ($setting['default'] ?? '')) . '" id="' . $key . '"' . $min . $max . $step . '/>';
if ($setting['type'] === 'password') {
echo '<div class="input-group-append input-group-text"><a href=""><i class="fas fa-eye-slash" ></i></a></div></div>';
}
}
else if($setting['type'] === 'textarea') {

View File

@@ -5,6 +5,7 @@ const SKILL_BALANCE = -2;
$i = 0;
define('HOOK_INIT', ++$i);
define('HOOK_STARTUP', ++$i);
define('HOOK_BEFORE_PAGE', ++$i);
define('HOOK_AFTER_PAGE', ++$i);
@@ -101,7 +102,7 @@ define('HOOK_FILTER_TWIG_DISPLAY', ++$i);
define('HOOK_FILTER_TWIG_RENDER', ++$i);
define('HOOK_FILTER_THEME_FOOTER', ++$i);
const HOOK_FIRST = HOOK_STARTUP;
const HOOK_FIRST = HOOK_INIT;
define('HOOK_LAST', $i);
function is_sub_dir($path = NULL, $parent_folder = BASE): bool|string

View File

@@ -41,7 +41,7 @@ if(setting('core.template_allow_change'))
}
else {
$template_session = getSession('template');
if ($template_session !== false) {
if ($template_session) {
if (!preg_match("/[^A-z0-9_\-]/", $template_session)) {
$template_name = $template_session;
}
@@ -95,7 +95,7 @@ else {
unset($file);
if ($cache->enabled()) {
$cache->set('template_ini_' . $template_name, serialize($template_ini));
$cache->set('template_ini_' . $template_name, serialize($template_ini), 10 * 60);
}
}
}
@@ -133,6 +133,7 @@ if($forumSetting != '')
$template['link_forum'] = "<a href='" . $forumSetting . "' target='_blank'>";
}
$twig->addGlobal('template_name', $template_name);
$twig->addGlobal('template_path', $template_path);
if($twig_loader) {
$twig_loader->prependPath(BASE . $template_path);
@@ -152,17 +153,40 @@ function get_template_menus(): array
return $result->toArray();
});
$menus = array();
$configMenuCategories = config('menu_categories');
$configMenuDefaultColor = config('menu_default_links_color') ?? config('menu_default_color');
$menus = [];
foreach($result as $menu) {
$link_full = strpos(trim($menu['link']), 'http') === 0 ? $menu['link'] : getLink($menu['link']);
$menus[$menu['category']][] = array('name' => $menu['name'], 'link' => $menu['link'], 'link_full' => $link_full, 'blank' => $menu['blank'] == 1, 'target_blank' => ($menu['blank'] == 1 ? ' target="blank"' : ''), 'color' => $menu['color']);
if (empty($menu['link'])) {
$menu['link'] = 'news';
}
$link_full = (str_starts_with(trim($menu['link']), 'http') ? $menu['link'] : getLink($menu['link']));
$target_blank = ($menu['blank'] == 1 ? ' target="blank"' : '');
$color = (empty($menu['color']) ? ($configMenuCategories[$menu['category']]['default_links_color'] ?? ($configMenuDefaultColor ?? '')) : $menu['color']);
$color = str_replace('#', '', $color);
if (in_array('#' . $color, [$configMenuCategories[$menu['category']]['default_links_color'] ?? '', $configMenuDefaultColor])) {
$color = '';
}
$style_color = (empty($color) ? '' : 'style="color: #' . $color . ' !important"');
$menus[$menu['category']][] = [
'name' => $menu['name'],
'link' => $menu['link'], 'link_full' => $link_full,
'blank' => $menu['blank'] == 1, 'target_blank' => $target_blank,
'color' => $color, 'style_color' => $style_color,
];
}
$new_menus = array();
$new_menus = [];
/**
* @var array $configMenuCategories
*/
$configMenuCategories = config('menu_categories');
if($configMenuCategories === null) {
return [];
}

View File

@@ -24,21 +24,28 @@ Please enter your account {{ account|lower }} and your password.<br/><a href="{{
<td>
<div class="InnerTableContainer">
<table style="width:100%;" >
{{ hook('HOOK_ACCOUNT_LOGIN_BEFORE_PASSWORD') }}
{{ hook('HOOK_ACCOUNT_LOGIN_BEFORE_ACCOUNT') }}
<tr>
<td class="LabelV" >
<span{% if error is not null %} class="red"{% endif %}>{{ account_login_by }}:</span>
</td>
<td style="width:100%;" ><input type="text" name="account_login" size="30" maxlength="{{ setting('core.account_login_by_email') ? '255' : '30' }}" autofocus/></td>
</tr>
{{ hook('HOOK_ACCOUNT_LOGIN_AFTER_ACCOUNT') }}
{{ hook('HOOK_ACCOUNT_LOGIN_BEFORE_PASSWORD') }}
<tr>
<td class="LabelV" >
<span{% if error is not null %} class="red"{% endif %}>Password:</span>
</td>
<td><input type="password" name="password_login" size="30" maxlength="29" ></td>
</tr>
{{ hook('HOOK_ACCOUNT_LOGIN_AFTER_PASSWORD') }}
<tr>
<td class="LabelV" ></td>
<td><input type="checkbox" id="remember_me" name="remember_me" value="true" />

View File

@@ -110,7 +110,7 @@ html { margin-top: 32px !important; }
<form method="post" action="{{ constant('ADMIN_URL') }}?p=dashboard">
{{ csrf() }}
<input type="hidden" name="clear_cache" value="1" />
<a class="ab-item" href="#" onclick="confirm('Are you sure that you want to clear cache?') && $(this).closest('form').submit()" title="Clear Cache">Clear Cache</a>
<a class="ab-item" href="javascript:void(0);" onclick="confirm('Are you sure that you want to clear cache?') && $(this).closest('form').submit()" title="Clear Cache">Clear Cache</a>
</form>
</li>
</ul>

View File

@@ -0,0 +1,31 @@
<div align="center" class="text-center">
<p class="note">You are editing: {{ template }}<br/><br/>
Hint: You can drag menu items.<br/>
Hint: Add links to external sites using: <b>http://</b> or <b>https://</b> prefix.<br/>
Not all templates support blank and colorful links.
</p>
<div class="row text-center">
<div class="col-md-2 col-sm-1"></div>
<div class="col-md-8 col-sm-10">
<div class="row justify-content-center">
{% if config('menus') is not null %}
<form method="post" action="?p=menus&reset_menus" onsubmit="return confirm('Do you really want to reset menus?');">
{{ csrf() }}
<input type="hidden" name="template" value="{{ template }}"/>
<button type="submit" class="btn btn-danger">Reset Menus to default</button>
</form>
<br/>
{% endif %}
{% if canResetColors %}
<form method="post" action="?p=menus&reset_colors" onsubmit="return confirm('Do you really want to reset colors?');">
{{ csrf() }}
<input type="hidden" name="template" value="{{ template }}"/>
<button type="submit" class="btn btn-warning" style="margin-left: 20px">Reset Colors to default</button>
</form>
<br/>
{% endif %}
</div>
</div>
<div class="col-md-2 col-sm-1"></div>
</div>
</div>

View File

@@ -1,31 +1,41 @@
<link type="text/css" rel="stylesheet" href="{{ constant('BASE_URL') }}tools/ext/jquery-ui/themes/base/jquery-ui.min.css">
<script>
var last_id = [];
let last_id = [];
let colors = [];
{% for cat, menu in menus %}
last_id[{{ cat }}] = {{ last_id[cat] }};
{% endfor %}
$(function () {
$(".sortable").sortable();
$(".sortable").disableSelection();
$(".remove-button").click(function () {
{% set menuDefaultLinksColor = config('menu_default_links_color') %}
{% for cat, options in config('menu_categories') %}
colors[{{ cat }}] = '{{ options['default_links_color'] ?? (menuDefaultLinksColor ?? config('menu_default_color')) }}';
{% endfor %}
$(function () {
const $sortable = $(".sortable");
$sortable.sortable();
$sortable.disableSelection();
$(".remove-button").on('click', function () {
var id = $(this).attr("id");
$('#list-' + id.replace('remove-button-', '')).remove();
});
$(".add-button").click(function () {
$(".add-button").on('click', function () {
var cat = $(this).attr("id").replace('add-button-', '');
var id = last_id[cat];
last_id[cat]++;
$('#sortable-' + cat).append('<li class="ui-state-default" id="list-' + cat + '-' + id + '"><label>Name:</label> <input type="text" name="menu[' + cat + '][]" value=""/> <label>Link:</label> <input type="text" name="menu_link[' + cat + '][]" value=""/><input type="hidden" name="menu_blank[' + cat + '][]" value="0" /> <label><input class="blank-checkbox" type="checkbox"/><span title="Open in New Window">New Window</span></label> <input class="color-picker" type="text" name="menu_color[' + cat + '][]" value="{{ menu_default_color }}" /><a class="remove-button" id="remove-button-' + cat + '-' + id + '"><i class="fas fa-trash"></i></a></li>'); //add input bo
$('#remove-button-' + cat + '-' + id).click(function () {
const color = colors[cat];
$('#sortable-' + cat).append('<li class="ui-state-default" id="list-' + cat + '-' + id + '"><label>Name:</label> <input type="text" name="menu[' + cat + '][]" value=""/> <label>Link:</label> <input type="text" name="menu_link[' + cat + '][]" value=""/><input type="hidden" name="menu_blank[' + cat + '][]" value="0" /> <label><input class="blank-checkbox" type="checkbox"/><span title="Open in New Window">New Window</span></label> <input class="color-picker" type="text" name="menu_color[' + cat + '][]" value="#' + color + '" /> <a class="remove-button" id="remove-button-' + cat + '-' + id + '"><i class="fas fa-trash"></i></a></li>'); //add input bo
$('#remove-button-' + cat + '-' + id).on('click', function () {
$('#list-' + $(this).attr("id").replace('remove-button-', '')).remove();
});
initialiceSpectrum();
initializeSpectrum();
});
$("#menus-form").submit(function (e) {
$("#menus-form").on('submit', function (e) {
$('.blank-checkbox:not(:checked)').each(function (i, obj) {
$(obj).parent().prev().val("off");
});
@@ -52,11 +62,11 @@
<script type="text/javascript" src="{{ constant('BASE_URL') }}tools/js/spectrum.js"></script>
<link type="text/css" rel="stylesheet" href="{{ constant('BASE_URL') }}tools/css/spectrum.css"/>
<script type="text/javascript">
$(document).ready(function () {
initialiceSpectrum();
$(function () {
initializeSpectrum();
});
function initialiceSpectrum() {
function initializeSpectrum() {
$(".color-picker").spectrum({
preferredFormat: "hex",
showInput: true,

View File

@@ -80,7 +80,26 @@
}
});
$('#settings').submit(function(e) {
const noChangesText = "No changes has been made";
$('form')
.each(function(){
$(this).data('serialized', $(this).serialize())
})
.on('change input', function(){
const disable = $(this).serialize() === $(this).data('serialized');
$(this)
.find('input:submit, button:submit')
.prop('disabled', disable)
.prop('title', disable ? noChangesText : '')
;
})
.find('input:submit, button:submit')
.prop('disabled', true)
.prop('title', noChangesText)
;
$('#settings').on('submit', function(e) {
e.preventDefault();
$.ajax({
@@ -94,6 +113,13 @@
duration: 3000,
escapeMarkup: false,
}).showToast();
let $settings = $('#settings');
$settings.data('serialized', $settings.serialize());
$settings
.find('input:submit, button:submit')
.prop('disabled', true)
.prop('title', noChangesText);
},
error : function(response) {
Toastify({
@@ -109,3 +135,27 @@
});
});
</script>
<script>
{% for key, value in settings %}
{% if value.type == 'password' %}
$(function () {
$('#show-hide-{{ key}} a').on('click', function(event) {
event.preventDefault();
const $showHideIcon = $('#show-hide-{{ key}} i');
const $showHideInput = $('#show-hide-{{ key }} input');
if($showHideInput.attr('type') === 'text'){
$showHideInput.attr('type', 'password');
$showHideIcon.addClass('fa-eye-slash');
$showHideIcon.removeClass('fa-eye');
}else if($showHideInput.attr("type") === 'password'){
$showHideInput.attr('type', 'text');
$showHideIcon.removeClass('fa-eye-slash');
$showHideIcon.addClass('fa-eye');
}
});
});
{% endif %}
{% endfor %}
</script>

View File

@@ -79,57 +79,59 @@
</tr>
{% set showedRank, i = false, 0 %}
{% for rank in guild_members if rank.members|length > 0 %}
{% set rankStyle, i = getStyle(i), i + 1 %}
{% for rank in guild_members %}
{% if rank.members|length > 0 %}
{% set rankStyle, i = getStyle(i), i + 1 %}
{% for player in rank.members %}
<tr bgcolor="{{ rankStyle }}">
<td>
{% if not showedRank %}{{ rank.rank_name }}{% endif %}
{% set showedRank = true %}
</td>
{% for player in rank.members %}
<tr bgcolor="{{ rankStyle }}">
<td>
{% if not showedRank %}{{ rank.rank_name }}{% endif %}
{% set showedRank = true %}
</td>
<td>
{% set playerName = player.getName() %}
<form action="{{ getLink('guilds') }}?action=change_nick&name={{ playerName }}&guild={{ guild_name }}" method="post">
{{ csrf() }}
{{ getPlayerLink(playerName, true)|raw }}
<td>
{% set playerName = player.getName() %}
<form action="{{ getLink('guilds') }}?action=change_nick&name={{ playerName }}&guild={{ guild_name }}" method="post">
{{ csrf() }}
{{ getPlayerLink(playerName, true)|raw }}
{% set showGuildNick = false %}
{% if player.getGuildNick() is not empty %}
{% set showGuildNick = true %}
{% set guildNickRaw = player.getGuildNick()|raw %}
{% endif %}
{% set showGuildNick = false %}
{% if player.getGuildNick() is not empty %}
{% set showGuildNick = true %}
{% set guildNickRaw = player.getGuildNick()|raw %}
{% endif %}
{% if logged %}
{% if player.getId() in players_from_account_ids %}
(<input type="text" name="nick" value="{{ guildNickRaw }}"><input type="submit" value="Change">)
{% if logged %}
{% if player.getId() in players_from_account_ids %}
(<input type="text" name="nick" value="{{ guildNickRaw }}"><input type="submit" value="Change">)
{% else %}
{% if showGuildNick %} ({{ guildNickRaw }}){% endif %}
{% endif %}
{% if level_in_guild > rank.rank_level or isLeader %}
{% if guildOwnerName != playerName %}
<span style="font-size: 10px; float: right">
{<a href="{{ getLink('guilds') }}?action=kick_player&guild={{ guild_name|url_encode }}&name={{ playerName|url_encode }}">KICK</a>}
</span>
{% endif %}
{% endif %}
{% else %}
{% if showGuildNick %} ({{ guildNickRaw }}){% endif %}
{% endif %}
</form>
</td>
{% if level_in_guild > rank.rank_level or isLeader %}
{% if guildOwnerName != playerName %}
<span style="font-size: 10px; float: right">
{<a href="{{ getLink('guilds') }}?action=kick_player&guild={{ guild_name|url_encode }}&name={{ playerName|url_encode }}">KICK</a>}
</span>
{% endif %}
{% endif %}
{% else %}
{% if showGuildNick %} ({{ guildNickRaw }}){% endif %}
{% endif %}
</form>
</td>
<td>{{ player.getVocationName() }}</td>
<td>{{ player.getLevel() }}</td>
<td>
<span style="color: {% if player.isOnline() %} green;"><b>Online{% else %} red;"><b>Offline{% endif %}</b></span>
</td>
</tr>
{% endfor %}
<td>{{ player.getVocationName() }}</td>
<td>{{ player.getLevel() }}</td>
<td>
<span style="color: {% if player.isOnline() %} green;"><b>Online{% else %} red;"><b>Offline{% endif %}</b></span>
</td>
</tr>
{% endfor %}
{% set showedRank = false %}
{% set showedRank = false %}
{% endif %}
{% else %}
<tr bgcolor="{{ getStyle(i) }}">
<td colspan="5">No guild members found.</td>
@@ -162,7 +164,7 @@
</tr>
{% set i = 0 %}
{% for invited_player in invited_list if invited_list|length > 0 %}
{% for invited_player in invited_list %}
{% if invited_player.isLoaded() %}
<tr bgcolor="{{ getStyle(i) }}">
{% set i = i + 1 %}

View File

@@ -9,19 +9,17 @@
<td>
<label for="skillFilter">Choose a Skill</label>
<select onchange="location = this.value;" id="skillFilter">
{% set i = 0 %}
{% for link, name in types %}
<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>
<option value="{{ getLink('highscores') }}/{{ link|urlencode }}{% if vocation is not null %}/{{ vocation|lower|urlencode }}{% endif %}" class="size_xs" {% if list is not null and list == link %}selected{% endif %}>{{ name }}</option>
{% endfor %}
</select>
</td>
<td>
<label for="vocationFilter">Choose a vocation</label>
<select onchange="location = this.value;" id="vocationFilter">
<option value="{{ getLink('highscores') }}/{{ list }}" class="size_xs">[ALL]</option>
{% set i = 0 %}
<option value="{{ getLink('highscores') }}/{{ list|urlencode }}" class="size_xs">[ALL]</option>
{% for i in 0..config.vocations_amount %}
<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>
<option value="{{ getLink('highscores') }}/{{ list|urlencode }}/{{ config.vocations[i]|lower|urlencode }}" class="size_xs" {% if vocationId is not null and vocationId == i %}selected{% endif %}>{{ config.vocations[i]}}</option>
{% endfor %}
</select>
</td>
@@ -105,7 +103,7 @@
<tr bgcolor="{{ config.lightborder }}">
<td>
{% for link, name in types %}
<a href="{{ getLink('highscores') }}/{{ link }}{% if vocation is not null %}/{{ vocation }}{% endif %}" class="size_xs">{{ name }}</a><br/>
<a href="{{ getLink('highscores') }}/{{ link|urlencode }}{% if vocation is not null %}/{{ vocation|urlencode }}{% endif %}" class="size_xs">{{ name }}</a><br/>
{% endfor %}
</td>
</tr>
@@ -118,9 +116,9 @@
</tr>
<tr bgcolor="{{ config.lightborder }}">
<td>
<a href="{{ getLink('highscores') }}/{{ list }}" class="size_xs">[ALL]</a><br/>
<a href="{{ getLink('highscores') }}/{{ list|urlencode }}" class="size_xs">[ALL]</a><br/>
{% for i in 0..config.vocations_amount %}
<a href="{{ getLink('highscores') }}/{{ list }}/{{ config.vocations[i]|lower }}" class="size_xs">{{ config.vocations[i]}}</a><br/>
<a href="{{ getLink('highscores') }}/{{ list|urlencode }}/{{ config.vocations[i]|lower|urlencode }}" class="size_xs">{{ config.vocations[i]}}</a><br/>
{% endfor %}
</td>
</tr>

View File

@@ -56,10 +56,9 @@ the search criteria and start a new search.
</td>
<td>
<form action="{{ getLink('houses/view') }}" method="post">
<input type="hidden" name="house" value="{{ house.name }}">
<a href="{{ house.link }}">
{{ include('buttons.view.html.twig') }}
</form>
</a>
</td>
</tr>
{% endfor %}
@@ -85,17 +84,19 @@ the search criteria and start a new search.
<tr bgcolor="{{ config.darkborder }}">
<td valign="top" rowspan="2">
{% set checked = false %}
{% for id, name in config.towns if id > 0 %}
{% if ((townId is empty and name is not empty) or id == townId) and not checked %}
{% set variable = "checked" %}
{% set checked = true %}
{% else %}
{% set variable = "" %}
{% endif %}
{% for id, name in config.towns %}
{% if id > 0 %}
{% if ((townId is empty and name is not empty) or id == townId) and not checked %}
{% set variable = "checked" %}
{% set checked = true %}
{% else %}
{% set variable = "" %}
{% endif %}
<input type="radio" name="town" id="town_{{ id }}" value="{{ id }}"{% if variable is defined %} {{ variable }}{% endif %}>
<label for="town_{{ id }}"> {{ name }}</label>
<br>
<input type="radio" name="town" id="town_{{ id }}" value="{{ id }}"{% if variable is defined %} {{ variable }}{% endif %}>
<label for="town_{{ id }}"> {{ name }}</label>
<br>
{% endif %}
{% endfor %}
</td>

View File

@@ -139,6 +139,11 @@ $function = new TwigFunction('csrfToken', function () {
});
$twig->addFunction($function);
$function = new TwigFunction('session', function ($key) {
return session($key);
});
$twig->addFunction($function);
$filter = new TwigFilter('urlencode', function ($s) {
return urlencode($s);
});