Merge branch 'develop' into feature/settings

This commit is contained in:
slawkens 2023-05-08 13:05:22 +02:00
commit 40c00a1434
76 changed files with 1590 additions and 1509 deletions

View File

@ -1,13 +1,16 @@
name: PHP Linting
on:
pull_request:
branches: [master, develop]
branches: [develop]
push:
branches: [master]
branches: [develop]
jobs:
phplint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: michaelw90/PHP-Lint@master
- uses: actions/checkout@v3
- uses: overtrue/phplint@8.2
with:
path: .
options: --exclude=*.log

View File

@ -1,9 +1,52 @@
# Changelog
## [0.9.0 - x.x.2020]
## [0.9.0-alpha - x.x.2023]
Minimum PHP version for this release is 7.2.5.
### Added
* reworked Admin Panel (@Leesneaks, @gpedro, @slawkens)
* updated to Bootstrap v4
* new Menu
* new Dashboard: statistics, server status
* new Admin Bar showed on top when admin logged in
* new page: Server Data, to reload server data
* new pages: mass account & teleport tools
* editable changelogs
* revised Accounts & Players editors
* option to add/modify menus with plugins
* better, updated TinyMCE editor (v6.x)
* with option to upload images
* list of open source libraries used in project
* brand new charming installation page (by @fernandomatos)
* using Bootstrap
* new pages router: nikic/fast-route, allowing for better customisation
* Guild Wars support (available as plugin)
* support for login and create account only by email (configurable)
* with no need for account name
* Google ReCAPTCHA v3 support (available as plugin)
* automatically load towns names from .OTBM file
* support for Account Number
* suggest account number option
* many new functions, hooks and configurables
### Changed
* Composer is now used for external libraries like: Twig, PHPMailer, fast-route etc.
* mail support is disabled on fresh install, can be manually enabled by user
* don't show PHP errors on prod
* disable add php pages in admin panel for security. Option to disable plugins upload
* visitors counter shows now user browser, and also if its bot
* changes in required and optional PHP extensions
* reworked Pages:
* Bans
* works now for TFS 1.x
* Highscores
* frags works for TFS 1.x
* cached
* moved pages to Twig:
* experience stages
* update player_deaths entries on name change
* change_password email to be more informal
### Fixed
* hundrets of bug fixes, mostly patched from 0.8, so it makes no sense writing them again here

View File

@ -36,7 +36,7 @@ Official website: https://my-aac.org
chmod 660 images/guilds
chmod 660 images/houses
chmod 660 images/gallery
chmod -R 770 system/cache
chmod -R 760 system/cache
Visit http://your_domain/install (http://localhost/install) and follow instructions in the browser.

View File

@ -29,6 +29,11 @@ define('PAGE', $page);
require SYSTEM . 'functions.php';
require SYSTEM . 'init.php';
// verify myaac tables exists in database
if(!$db->hasTable('myaac_account_actions')) {
throw new RuntimeException('Seems that the table <strong>myaac_account_actions</strong> of MyAAC doesn\'t exist in the database. This is a fatal error. You can try to reinstall MyAAC by visiting <a href="' . BASE_URL . 'install">this</a> url.');
}
if(config('env') === 'dev') {
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);

View File

@ -16,6 +16,11 @@ $use_datatable = true;
if ($config['account_country'])
require SYSTEM . 'countries.conf.php';
$nameOrNumberColumn = 'name';
if (USE_ACCOUNT_NUMBER) {
$nameOrNumberColumn = 'number';
}
$hasSecretColumn = $db->hasColumn('accounts', 'secret');
$hasCoinsColumn = $db->hasColumn('accounts', 'coins');
$hasPointsColumn = $db->hasColumn('accounts', 'premium_points');
@ -48,16 +53,16 @@ else if (isset($_REQUEST['search'])) {
if (strlen($search_account) < 3 && !Validator::number($search_account)) {
echo_error('Player name is too short.');
} else {
$query = $db->query('SELECT `id` FROM `accounts` WHERE `name` = ' . $db->quote($search_account));
$query = $db->query('SELECT `id` FROM `accounts` WHERE `' . $nameOrNumberColumn . '` = ' . $db->quote($search_account));
if ($query->rowCount() == 1) {
$query = $query->fetch();
$id = (int)$query['id'];
} else {
$query = $db->query('SELECT `id`, `name` FROM `accounts` WHERE `name` LIKE ' . $db->quote('%' . $search_account . '%'));
$query = $db->query('SELECT `id`, `' . $nameOrNumberColumn . '` FROM `accounts` WHERE `' . $nameOrNumberColumn . '` LIKE ' . $db->quote('%' . $search_account . '%'));
if ($query->rowCount() > 0 && $query->rowCount() <= 10) {
$str_construct = 'Do you mean?<ul class="mb-0">';
foreach ($query as $row)
$str_construct .= '<li><a href="' . $admin_base . '&id=' . $row['id'] . '">' . $row['name'] . '</a></li>';
$str_construct .= '<li><a href="' . $admin_base . '&id=' . $row['id'] . '">' . $row[$nameOrNumberColumn] . '</a></li>';
$str_construct .= '</ul>';
echo_error($str_construct);
} else if ($query->rowCount() > 10)
@ -145,7 +150,7 @@ else if (isset($_REQUEST['search'])) {
$web_lastlogin = strtotime($_POST['web_lastlogin']);
verify_number($web_lastlogin, 'Web Last login', 11);
if (!$error) {
if (!$error && $hooks->trigger(HOOK_ADMIN_ACCOUNTS_SAVE_POST, ['account_id' => $account->getId(), 'account_email' => $account->getEMail()])) {
if (USE_ACCOUNT_NAME) {
$account->setName($name);
}
@ -203,7 +208,7 @@ else if (isset($_REQUEST['search'])) {
}
}
} else if ($id == 0) {
$accounts_db = $db->query('SELECT `id`, `name`' . ($hasTypeColumn ? ',type' : ($hasGroupColumn ? ',group_id' : '')) . ' FROM `accounts` ORDER BY `id` ASC');
$accounts_db = $db->query('SELECT `id`, `' . $nameOrNumberColumn . '`' . ($hasTypeColumn ? ',type' : ($hasGroupColumn ? ',group_id' : '')) . ' FROM `accounts` ORDER BY `id` ASC');
?>
<div class="col-12 col-sm-12 col-lg-10">
<div class="card card-info card-outline">
@ -215,7 +220,7 @@ else if (isset($_REQUEST['search'])) {
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th><?= ($nameOrNumberColumn == 'number' ? 'Number' : 'Name'); ?></th>
<?php if($hasTypeColumn || $hasGroupColumn): ?>
<th>Position</th>
<?php endif; ?>
@ -226,7 +231,7 @@ else if (isset($_REQUEST['search'])) {
<?php foreach ($accounts_db as $account_lst): ?>
<tr>
<th><?php echo $account_lst['id']; ?></th>
<td><?php echo $account_lst['name']; ?></a></td>
<td><?php echo $account_lst[$nameOrNumberColumn]; ?></a></td>
<?php if($hasTypeColumn || $hasGroupColumn): ?>
<td>
<?php if ($hasTypeColumn) {
@ -284,6 +289,11 @@ else if (isset($_REQUEST['search'])) {
<label for="name">Account Name:</label>
<input type="text" class="form-control" id="name" name="name" autocomplete="off" value="<?php echo $account->getName(); ?>"/>
</div>
<?php elseif (USE_ACCOUNT_NUMBER): ?>
<div class="col-12 col-sm-12 col-lg-4">
<label for="name">Account Number:</label>
<input type="text" class="form-control" id="name" name="name" autocomplete="off" value="<?php echo $account->getNumber(); ?>"/>
</div>
<?php endif; ?>
<div class="col-12 col-sm-12 col-lg-5">
<div class="form-check">

View File

@ -16,7 +16,7 @@ if (!hasFlag(FLAG_CONTENT_MAILER) && !superAdmin()) {
}
if (!config('mail_enabled')) {
echo 'Mail support disabled.';
echo 'Mail support disabled in config.';
return;
}

View File

@ -76,18 +76,18 @@ if (!empty($action)) {
$enable_tinymce = $_page['enable_tinymce'] == '1';
$access = $_page['access'];
} else {
if(Pages::update($id, $name, $p_title, $body, $player_id, $php, $enable_tinymce, $access)) {
if(Pages::update($id, $name, $p_title, $body, $player_id, $php, $enable_tinymce, $access, $errors)) {
$action = $name = $p_title = $body = '';
$player_id = 1;
$access = 0;
$php = false;
$enable_tinymce = true;
success("Updated successful.");
success('Updated successful.');
}
}
} else if ($action == 'hide') {
Pages::toggleHidden($id, $errors, $status);
success(($status == 1 ? 'Show' : 'Hide') . " successful.");
success(($status == 1 ? 'Show' : 'Hide') . ' successful.');
}
if (!empty($errors))
@ -152,6 +152,10 @@ class Pages
$errors[] = 'Enable PHP is wrong.';
return false;
}
if ($php == 1 && !getBoolean(config('admin_pages_php_enable'))) {
$errors[] = 'PHP pages disabled on this server. To enable go to config.php and change admin_pages_php_enable to "yes".';
return false;
}
if(!isset($enable_tinymce) || ($enable_tinymce != 0 && $enable_tinymce != 1)) {
$errors[] = 'Enable TinyMCE is wrong.';
return false;
@ -200,7 +204,7 @@ class Pages
return !count($errors);
}
static public function update($id, $name, $title, $body, $player_id, $php, $enable_tinymce, $access)
static public function update($id, $name, $title, $body, $player_id, $php, $enable_tinymce, $access, &$errors)
{
if(!self::verify($name, $title, $body, $player_id, $php, $enable_tinymce, $access, $errors)) {
return false;

View File

@ -663,7 +663,14 @@ else if (isset($_REQUEST['search'])) {
</div>
<div class="col-12 col-sm-12 col-lg-6">
<label for="lastip" class="control-label">Last IP:</label>
<input type="text" class="form-control" id="lastip" name="lastip" autocomplete="off" maxlength="10" value="<?php echo longToIp($player->getLastIP()); ?>" readonly/>
<input type="text" class="form-control" id="lastip" name="lastip" autocomplete="off" maxlength="10" value="<?php
if (strlen($player->getLastIP()) > 11) {
echo inet_ntop($player->getLastIP());
}
else {
echo longToIp($player->getLastIP());
}
?>" readonly/>
</div>
</div>
<?php if ($db->hasColumn('players', 'loss_experience')): ?>

View File

@ -13,9 +13,13 @@ $use_datatable = true;
require_once LIBS . 'plugins.php';
$twig->display('admin.plugins.form.html.twig');
if (!getBoolean(config('admin_plugins_manage_enable'))) {
warning('Plugin installation and management is disabled in config.<br/>If you wish to enable, go to config.php and change <b>admin_plugins_manage_enable</b> to "yes".');
}
else {
$twig->display('admin.plugins.form.html.twig');
if (isset($_REQUEST['uninstall'])) {
if (isset($_REQUEST['uninstall'])) {
$uninstall = $_REQUEST['uninstall'];
if (Plugins::uninstall($uninstall)) {
@ -23,13 +27,27 @@ if (isset($_REQUEST['uninstall'])) {
} else {
error('Error while uninstalling plugin ' . $uninstall . ': ' . Plugins::getError());
}
} else if (isset($_FILES["plugin"]["name"])) {
$file = $_FILES["plugin"];
$filename = $file["name"];
$tmp_name = $file["tmp_name"];
$type = $file["type"];
} else if (isset($_REQUEST['enable'])) {
$enable = $_REQUEST['enable'];
if (Plugins::enable($enable)) {
success('Successfully enabled plugin ' . $enable);
} else {
error('Error while enabling plugin ' . $enable . ': ' . Plugins::getError());
}
} else if (isset($_REQUEST['disable'])) {
$disable = $_REQUEST['disable'];
if (Plugins::disable($disable)) {
success('Successfully disabled plugin ' . $disable);
} else {
error('Error while disabling plugin ' . $disable . ': ' . Plugins::getError());
}
} else if (isset($_FILES['plugin']['name'])) {
$file = $_FILES['plugin'];
$filename = $file['name'];
$tmp_name = $file['tmp_name'];
$type = $file['type'];
$name = explode(".", $filename);
$name = explode('.', $filename);
$accepted_types = array('application/zip', 'application/x-zip-compressed', 'multipart/x-zip', 'application/x-compressed', 'application/octet-stream', 'application/zip-compressed');
if (isset($file['error'])) {
@ -87,24 +105,27 @@ if (isset($_REQUEST['uninstall'])) {
error('Error uploading file - unknown error.');
}
}
}
}
$plugins = array();
foreach (get_plugins() as $plugin) {
foreach (get_plugins(true) as $plugin) {
$string = file_get_contents(BASE . 'plugins/' . $plugin . '.json');
$string = Plugins::removeComments($string);
$plugin_info = json_decode($string, true);
if ($plugin_info == false) {
if (!$plugin_info) {
warning('Cannot load plugin info ' . $plugin . '.json');
} else {
$disabled = (strpos($plugin, 'disabled.') !== false);
$pluginOriginal = ($disabled ? str_replace('disabled.', '', $plugin) : $plugin);
$plugins[] = array(
'name' => isset($plugin_info['name']) ? $plugin_info['name'] : '',
'description' => isset($plugin_info['description']) ? $plugin_info['description'] : '',
'version' => isset($plugin_info['version']) ? $plugin_info['version'] : '',
'author' => isset($plugin_info['author']) ? $plugin_info['author'] : '',
'contact' => isset($plugin_info['contact']) ? $plugin_info['contact'] : '',
'file' => $plugin,
'name' => $plugin_info['name'] ?? '',
'description' => $plugin_info['description'] ?? '',
'version' => $plugin_info['version'] ?? '',
'author' => $plugin_info['author'] ?? '',
'contact' => $plugin_info['contact'] ?? '',
'file' => $pluginOriginal,
'enabled' => !$disabled,
'uninstall' => isset($plugin_info['uninstall'])
);
}

View File

@ -8,6 +8,11 @@
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
use DeviceDetector\DeviceDetector;
use DeviceDetector\Parser\Client\Browser;
use DeviceDetector\Parser\OperatingSystem;
$title = 'Visitors';
$use_datatable = true;
@ -30,6 +35,31 @@ function compare($a, $b)
$tmp = $visitors->getVisitors();
usort($tmp, 'compare');
foreach ($tmp as &$visitor) {
$userAgent = $visitor['user_agent'] ?? '';
if (!strlen($userAgent) || $userAgent == 'unknown') {
$browser = 'Unknown';
}
else {
$dd = new DeviceDetector($userAgent);
$dd->parse();
if ($dd->isBot()) {
$bot = $dd->getBot();
$message = '(Bot) %s, <a href="%s" target="_blank">%s</a>';
$browser = sprintf($message, $bot['category'], $bot['url'], $bot['name']);
}
else {
$osFamily = OperatingSystem::getOsFamily($dd->getOs('name'));
$browserFamily = Browser::getBrowserFamily($dd->getClient('name'));
$browser = $osFamily . ', ' . $browserFamily;
}
}
$visitor['browser'] = $browser;
}
$twig->display('admin.visitors.html.twig', array(
'config_visitors_counter_ttl' => $config['visitors_counter_ttl'],
'visitors' => $tmp

View File

@ -27,7 +27,7 @@ if (version_compare(phpversion(), '7.2.5', '<')) die('PHP version 7.2.5 or highe
const MYAAC = true;
const MYAAC_VERSION = '0.9.0-dev';
const DATABASE_VERSION = 34;
const DATABASE_VERSION = 35;
const TABLE_PREFIX = 'myaac_';
define('START_TIME', microtime(true));
define('MYAAC_OS', stripos(PHP_OS, 'WIN') === 0 ? 'WINDOWS' : (strtoupper(PHP_OS) === 'DARWIN' ? 'MAC' : 'LINUX'));

View File

@ -10,6 +10,7 @@
"composer/semver": "^3.2",
"twig/twig": "^2.0",
"erusev/parsedown": "^1.7",
"nikic/fast-route": "^1.3"
"nikic/fast-route": "^1.3",
"matomo/device-detector": "^6.0"
}
}

View File

@ -210,6 +210,10 @@ $config = array(
'status_interval' => 60,
// admin panel
'admin_plugins_manage_enable' => 'yes', // you can disable possibility to upload and uninstall plugins, for security
// enable support for plain php pages in admin panel, for security
// existing pages still will be working, so you need to delete them manually
'admin_pages_php_enable' => 'no',
'admin_panel_modules' => 'statistics,web_status,server_status,lastlogin,created,points,coins,balance', // default - statistics,web_status,server_status,lastlogin,created,points,coins,balance
// other

View File

@ -1,4 +1,4 @@
SET @myaac_database_version = 33;
SET @myaac_database_version = 35;
CREATE TABLE `myaac_account_actions`
(
@ -203,6 +203,7 @@ CREATE TABLE `myaac_monsters` (
`mana` int(11) NOT NULL DEFAULT 0,
`exp` int(11) NOT NULL,
`health` int(11) NOT NULL,
`look` VARCHAR(255) NOT NULL DEFAULT '',
`speed_lvl` int(11) NOT NULL default 1,
`use_haste` tinyint(1) NOT NULL,
`voices` text NOT NULL,
@ -340,6 +341,7 @@ CREATE TABLE `myaac_visitors`
`ip` VARCHAR(45) NOT NULL,
`lastvisit` INT(11) NOT NULL DEFAULT 0,
`page` VARCHAR(2048) NOT NULL,
`user_agent` VARCHAR(255) NOT NULL DEFAULT '',
UNIQUE (`ip`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;

View File

@ -1,4 +1,4 @@
We have detected that you don't have access to write to the system/cache directory. Under linux you can fix it by using this two command, where first one should be enough (for apache):<br/><br/><span class="console">chown -R www-data.www-data /var/www/*</span><br/><span class="console">chmod -R 660 system/cache</span>
We have detected that you don't have access to write to the system/cache directory. Under linux you can fix it by using this two command, where first one should be enough (for apache):<br/><br/><span class="console">chown -R www-data.www-data /var/www/*</span><br/><span class="console">chmod -R 760 system/cache</span>
<style type="text/css">
.console {

View File

@ -70,7 +70,7 @@ if($step == 'database') {
$key = str_replace('var_', '', $key);
if(in_array($key, array('account', 'password', 'password_confirm', 'email', 'player_name'))) {
if(in_array($key, array('account', 'account_id', 'password', 'password_confirm', 'email', 'player_name'))) {
continue;
}
@ -110,14 +110,12 @@ if($step == 'database') {
}
}
else if($step == 'admin') {
$config_failed = true;
if(file_exists(BASE . 'config.local.php') && isset($config['installed']) && $config['installed'] && isset($_SESSION['saved'])) {
$config_failed = false;
}
if($config_failed) {
if(!file_exists(BASE . 'config.local.php') || !isset($config['installed']) || !$config['installed']) {
$step = 'database';
}
else {
$_SESSION['saved'] = true;
}
}
else if($step == 'finish') {
$email = $_SESSION['var_email'];

View File

@ -55,12 +55,30 @@ if(!$error) {
error($database_error);
}
else {
if(!$db->hasTable('accounts')) {
$tmp = str_replace('$TABLE$', 'accounts', $locale['step_database_error_table']);
error($tmp);
$error = true;
}
if(!$db->hasTable('players')) {
$tmp = str_replace('$TABLE$', 'players', $locale['step_database_error_table']);
error($tmp);
$error = true;
}
if(!$db->hasTable('guilds')) {
$tmp = str_replace('$TABLE$', 'guilds', $locale['step_database_error_table']);
error($tmp);
$error = true;
}
if(!$error) {
$twig->display('install.installer.html.twig', array(
'url' => 'tools/5-database.php',
'message' => $locale['loading_spinner']
));
if(!$error) {
if(!Validator::email($_SESSION['var_mail_admin'])) {
error($locale['step_config_mail_admin_error']);
$error = true;
@ -86,7 +104,7 @@ if(!$error) {
unset($_SESSION['saved']);
$locale['step_database_error_file'] = str_replace('$FILE$', '<b>' . BASE . 'config.local.php</b>', $locale['step_database_error_file']);
warning($locale['step_database_error_file'] . '<br/>
error($locale['step_database_error_file'] . '<br/>
<textarea cols="70" rows="10">' . $content . '</textarea>');
}
}

View File

@ -8,7 +8,7 @@ if(isset($config['installed']) && $config['installed'] && !isset($_SESSION['save
else {
require SYSTEM . 'init.php';
if(!$error) {
if(USE_ACCOUNT_NAME)
if(USE_ACCOUNT_NAME || USE_ACCOUNT_NUMBER)
$account = isset($_SESSION['var_account']) ? $_SESSION['var_account'] : null;
else
$account_id = isset($_SESSION['var_account_id']) ? $_SESSION['var_account_id'] : null;
@ -65,7 +65,6 @@ else {
$new_account->setPassword(encrypt($password));
$new_account->setEMail($email);
$new_account->unblock();
$new_account->save();
$new_account->setCustomField('created', time());

View File

@ -23,24 +23,6 @@ if(!$error) {
}
}
if(!$db->hasTable('accounts')) {
$locale['step_database_error_table'] = str_replace('$TABLE$', 'accounts', $locale['step_database_error_table']);
error($locale['step_database_error_table']);
return;
}
if(!$db->hasTable('players')) {
$locale['step_database_error_table'] = str_replace('$TABLE$', 'players', $locale['step_database_error_table']);
error($locale['step_database_error_table']);
return;
}
if(!$db->hasTable('guilds')) {
$locale['step_database_error_table'] = str_replace('$TABLE$', 'guilds', $locale['step_database_error_table']);
error($locale['step_database_error_table']);
return;
}
if($db->hasTable(TABLE_PREFIX . 'account_actions')) {
$locale['step_database_error_table_exist'] = str_replace('$TABLE$', TABLE_PREFIX . 'account_actions', $locale['step_database_error_table_exist']);
warning($locale['step_database_error_table_exist']);
@ -73,13 +55,8 @@ else {
success($locale['step_database_adding_field'] . ' accounts.key...');
}
if(!$db->hasColumn('accounts', 'blocked')) {
if(query("ALTER TABLE `accounts` ADD `blocked` TINYINT(1) NOT NULL DEFAULT FALSE COMMENT 'internal usage' AFTER `key`;"))
success($locale['step_database_adding_field'] . ' accounts.blocked...');
}
if(!$db->hasColumn('accounts', 'created')) {
if(query("ALTER TABLE `accounts` ADD `created` INT(11) NOT NULL DEFAULT 0 AFTER `" . ($db->hasColumn('accounts', 'group_id') ? 'group_id' : 'blocked') . "`;"))
if(query("ALTER TABLE `accounts` ADD `created` INT(11) NOT NULL DEFAULT 0 AFTER `" . ($db->hasColumn('accounts', 'group_id') ? 'group_id' : 'key') . "`;"))
success($locale['step_database_adding_field'] . ' accounts.created...');
}

View File

@ -7,6 +7,23 @@ server {
# increase max file upload
client_max_body_size 10M;
# this is very important, be sure its in your nginx conf - it prevents access to logs etc.
location ~ /system {
deny all;
return 404;
}
# block .htaccess
location ~ /\.ht {
deny all;
}
# block git files and folders
location ~ /\.git {
return 404;
deny all;
}
location / {
try_files $uri $uri/ /index.php;
}
@ -15,15 +32,6 @@ server {
include snippets/fastcgi-php.conf;
fastcgi_read_timeout 240;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
# for ubuntu 22.04+ it will be php8.1-sock
}
location ~ /\.ht {
deny all;
}
location /system {
deny all;
return 404;
# for ubuntu 22.04+ it will be php8.1-fpm.-sock
}
}

View File

@ -1,11 +1,3 @@
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>
<IfVersion < 2.4>
order allow,deny
deny from all
</IfVersion>
<IfVersion >= 2.4>
Require all denied
</IfVersion>

View File

@ -1,206 +0,0 @@
<?php
namespace MyAAC;
$loader = new \MyAAC\Psr4AutoloaderClass;
// register the autoloader
$loader->register();
// register the base directories for the namespace prefix
$loader->addNamespace('Composer\Semver', LIBS . 'semver');
$loader->addNamespace('Twig', LIBS . 'Twig');
/**
* An example of a general-purpose implementation that includes the optional
* functionality of allowing multiple base directories for a single namespace
* prefix.
*
* Given a foo-bar package of classes in the file system at the following
* paths ...
*
* /path/to/packages/foo-bar/
* src/
* Baz.php # Foo\Bar\Baz
* Qux/
* Quux.php # Foo\Bar\Qux\Quux
* tests/
* BazTest.php # Foo\Bar\BazTest
* Qux/
* QuuxTest.php # Foo\Bar\Qux\QuuxTest
*
* ... add the path to the class files for the \Foo\Bar\ namespace prefix
* as follows:
*
* <?php
* // instantiate the loader
* $loader = new \Example\Psr4AutoloaderClass;
*
* // register the autoloader
* $loader->register();
*
* // register the base directories for the namespace prefix
* $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/src');
* $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/tests');
*
* The following line would cause the autoloader to attempt to load the
* \Foo\Bar\Qux\Quux class from /path/to/packages/foo-bar/src/Qux/Quux.php:
*
* <?php
* new \Foo\Bar\Qux\Quux;
*
* The following line would cause the autoloader to attempt to load the
* \Foo\Bar\Qux\QuuxTest class from /path/to/packages/foo-bar/tests/Qux/QuuxTest.php:
*
* <?php
* new \Foo\Bar\Qux\QuuxTest;
*/
class Psr4AutoloaderClass
{
/**
* An associative array where the key is a namespace prefix and the value
* is an array of base directories for classes in that namespace.
*
* @var array
*/
protected $prefixes = array();
/**
* Register loader with SPL autoloader stack.
*
* @return void
*/
public function register()
{
spl_autoload_register(array($this, 'loadClass'));
}
/**
* Adds a base directory for a namespace prefix.
*
* @param string $prefix The namespace prefix.
* @param string $base_dir A base directory for class files in the
* namespace.
* @param bool $prepend If true, prepend the base directory to the stack
* instead of appending it; this causes it to be searched first rather
* than last.
* @return void
*/
public function addNamespace($prefix, $base_dir, $prepend = false)
{
// normalize namespace prefix
$prefix = trim($prefix, '\\') . '\\';
// normalize the base directory with a trailing separator
$base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
// initialize the namespace prefix array
if (isset($this->prefixes[$prefix]) === false) {
$this->prefixes[$prefix] = array();
}
// retain the base directory for the namespace prefix
if ($prepend) {
array_unshift($this->prefixes[$prefix], $base_dir);
} else {
array_push($this->prefixes[$prefix], $base_dir);
}
}
/**
* Loads the class file for a given class name.
*
* @param string $class The fully-qualified class name.
* @return mixed The mapped file name on success, or boolean false on
* failure.
*/
public function loadClass($class)
{
if (0 === strpos($class, 'Twig_')) {
$file = LIBS . 'Twig/' . str_replace(array('_', "\0"), array('/', ''), $class).'.php';
if((config('env') === 'dev') && !is_file($file)) {
return false;
}
require $file;
return false;
}
// the current namespace prefix
$prefix = $class;
// work backwards through the namespace names of the fully-qualified
// class name to find a mapped file name
while (false !== $pos = strrpos($prefix, '\\')) {
// retain the trailing namespace separator in the prefix
$prefix = substr($class, 0, $pos + 1);
// the rest is the relative class name
$relative_class = substr($class, $pos + 1);
// try to load a mapped file for the prefix and relative class
$mapped_file = $this->loadMappedFile($prefix, $relative_class);
if ($mapped_file) {
return $mapped_file;
}
// remove the trailing namespace separator for the next iteration
// of strrpos()
$prefix = rtrim($prefix, '\\');
}
// never found a mapped file
return false;
}
/**
* Load the mapped file for a namespace prefix and relative class.
*
* @param string $prefix The namespace prefix.
* @param string $relative_class The relative class name.
* @return mixed Boolean false if no mapped file can be loaded, or the
* name of the mapped file that was loaded.
*/
protected function loadMappedFile($prefix, $relative_class)
{
// are there any base directories for this namespace prefix?
if (isset($this->prefixes[$prefix]) === false) {
return false;
}
// look through base directories for this namespace prefix
foreach ($this->prefixes[$prefix] as $base_dir) {
// replace the namespace prefix with the base directory,
// replace namespace separators with directory separators
// in the relative class name, append with .php
$file = $base_dir
. str_replace('\\', '/', $relative_class)
. '.php';
// if the mapped file exists, require it
if ($this->requireFile($file)) {
// yes, we're done
return $file;
}
}
// never found it
return false;
}
/**
* If a file exists, require it from the file system.
*
* @param string $file The file to require.
* @return bool True if the file exists, false if not.
*/
protected function requireFile($file)
{
if (config('env') !== 'dev' || file_exists($file)) {
require $file;
return true;
}
return false;
}
}

View File

@ -1,5 +1,12 @@
<?php
/**
* Exception handler
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2023 MyAAC
* @link https://my-aac.org
*/
require LIBS . 'SensitiveException.php';
/**

View File

@ -7,12 +7,11 @@
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
use PHPMailer\PHPMailer\PHPMailer;
use Twig\Loader\ArrayLoader as Twig_ArrayLoader;
defined('MYAAC') or die('Direct access not allowed!');
function message($message, $type, $return)
{
if(IS_CLI) {
@ -125,14 +124,13 @@ function getHouseLink($name, $generate = true)
function getGuildLink($name, $generate = true)
{
global $db;
global $config;
if(is_numeric($name))
{
$guild = $db->query(
'SELECT `name` FROM `guilds` WHERE `id` = ' . (int)$name);
if($guild->rowCount() > 0)
$name = $guild->fetchColumn();
if(is_numeric($name)) {
$name = getGuildNameById($name);
if ($name === false) {
$name = 'Unknown';
}
}
$settings = Settings::getInstance();
@ -792,16 +790,21 @@ function get_templates()
* Generates list of installed plugins
* @return array $plugins
*/
function get_plugins()
function get_plugins($disabled = false): array
{
$ret = array();
$ret = [];
$path = PLUGINS;
foreach(scandir($path, 0) as $file) {
foreach(scandir($path, SCANDIR_SORT_ASCENDING) as $file) {
$file_ext = pathinfo($file, PATHINFO_EXTENSION);
$file_name = pathinfo($file, PATHINFO_FILENAME);
if ($file === '.' || $file === '..' || $file === 'disabled' || $file === 'example.json' || $file_ext !== 'json' || is_dir($path . $file))
if ($file === '.' || $file === '..' || $file === 'example.json' || $file_ext !== 'json' || is_dir($path . $file)) {
continue;
}
if (!$disabled && strpos($file, 'disabled.') !== false) {
continue;
}
$ret[] = str_replace('.json', '', $file_name);
}
@ -1553,6 +1556,39 @@ function escapeHtml($html) {
return htmlentities($html, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function getGuildNameById($id)
{
global $db;
$guild = $db->query('SELECT `name` FROM `guilds` WHERE `id` = ' . (int)$id);
if($guild->rowCount() > 0) {
return $guild->fetchColumn();
}
return false;
}
function getGuildLogoById($id)
{
global $db;
$logo = 'default.gif';
$query = $db->query('SELECT `logo_name` FROM `guilds` WHERE `id` = ' . (int)$id);
if ($query->rowCount() == 1) {
$query = $query->fetch(PDO::FETCH_ASSOC);
$guildLogo = $query['logo_name'];
if (!empty($guildLogo) && file_exists(GUILD_IMAGES_DIR . $guildLogo)) {
$logo = $guildLogo;
}
}
return BASE_URL . GUILD_IMAGES_DIR . $logo;
}
// validator functions
require_once LIBS . 'validator.php';
require_once SYSTEM . 'compat/base.php';

View File

@ -30,6 +30,7 @@ define('HOOK_CHARACTERS_AFTER_CHARACTERS', ++$i);
define('HOOK_LOGIN', ++$i);
define('HOOK_LOGIN_ATTEMPT', ++$i);
define('HOOK_LOGOUT', ++$i);
define('HOOK_ACCOUNT_CHANGE_PASSWORD_POST', ++$i);
define('HOOK_ACCOUNT_CREATE_BEFORE_FORM', ++$i);
define('HOOK_ACCOUNT_CREATE_BEFORE_BOXES', ++$i);
define('HOOK_ACCOUNT_CREATE_BETWEEN_BOXES_1', ++$i);
@ -65,9 +66,12 @@ define('HOOK_ADMIN_MENU', ++$i);
define('HOOK_ADMIN_LOGIN_AFTER_ACCOUNT', ++$i);
define('HOOK_ADMIN_LOGIN_AFTER_PASSWORD', ++$i);
define('HOOK_ADMIN_LOGIN_AFTER_SIGN_IN', ++$i);
define('HOOK_ADMIN_ACCOUNTS_SAVE_POST', ++$i);
define('HOOK_EMAIL_CONFIRMED', ++$i);
define('HOOK_GUILDS_AFTER_INVITED_CHARACTERS', ++$i);
const HOOK_FIRST = HOOK_STARTUP;
const HOOK_LAST = HOOK_EMAIL_CONFIRMED;
define('HOOK_LAST', $i);
require_once LIBS . 'plugins.php';
class Hook

View File

@ -251,11 +251,13 @@ class CreateCharacter
}
}
if ($db->hasTable('player_items') && $db->hasColumn('player_items', 'pid') && $db->hasColumn('player_items', 'sid') && $db->hasColumn('player_items', 'itemtype')) {
$loaded_items_to_copy = $db->query("SELECT * FROM player_items WHERE player_id = ".$char_to_copy->getId()."");
foreach($loaded_items_to_copy as $save_item) {
$blob = $db->quote($save_item['attributes']);
$db->query("INSERT INTO `player_items` (`player_id` ,`pid` ,`sid` ,`itemtype`, `count`, `attributes`) VALUES ('".$player->getId()."', '".$save_item['pid']."', '".$save_item['sid']."', '".$save_item['itemtype']."', '".$save_item['count']."', $blob);");
}
}
global $twig;
$twig->display('success.html.twig', array(

View File

@ -110,4 +110,21 @@ class Cache
* @return bool
*/
public function enabled() {return false;}
public static function remember($key, $ttl, $callback)
{
$cache = self::getInstance();
if(!$cache->enabled()) {
return $callback();
}
$value = null;
if ($cache->fetch($key, $value)) {
return unserialize($value);
}
$value = $callback();
$cache->set($key, serialize($value),$ttl);
return $value;
}
}

View File

@ -82,6 +82,9 @@ class Creatures {
$armor = $monster->getArmor();
$defensev = $monster->getDefense();
//load look
$look = $monster->getLook();
//load monster flags
$flags = $monster->getFlags();
if(!isset($flags['summonable']))
@ -147,6 +150,7 @@ class Creatures {
'armor' => $armor,
'race' => $race,
'loot' => json_encode($loot),
'look' => json_encode($look),
'summons' => json_encode($summons)
));

View File

@ -10,7 +10,7 @@
*/
defined('MYAAC') or die('Direct access not allowed!');
function is_sub_dir($path = NULL, $parent_folder = SITE_PATH) {
function is_sub_dir($path = NULL, $parent_folder = BASE) {
//Get directory path minus last folder
$dir = dirname($path);
@ -41,35 +41,9 @@ function is_sub_dir($path = NULL, $parent_folder = SITE_PATH) {
use Composer\Semver\Semver;
class Plugins {
private static $warnings = array();
private static $warnings = [];
private static $error = null;
private static $plugin_json = array();
private static $plugins = [];
public static function load()
{
$cache = Cache::getInstance();
if ($cache->enabled()) {
$tmp = '';
if ($cache->fetch('plugins', $tmp)) {
self::$plugins = unserialize($tmp);
return;
}
}
foreach (get_plugins() as $filename) {
$plugin_json = self::getPluginJson($filename);
if (!$plugin_json) {
continue;
}
self::$plugins[$filename] = $plugin_json;
}
if ($cache->enabled()) {
$cache->set('hooks', serialize(self::$plugins), 600);
}
}
private static $plugin_json = [];
public static function getRoutes()
{
@ -82,22 +56,8 @@ class Plugins {
}
$routes = [];
foreach(get_plugins() as $filename) {
$string = file_get_contents(PLUGINS . $filename . '.json');
$string = self::removeComments($string);
$plugin = json_decode($string, true);
self::$plugin_json = $plugin;
if ($plugin == null) {
self::$warnings[] = 'Cannot load ' . $filename . '.json. File might be not a valid json code.';
continue;
}
if(isset($plugin['enabled']) && !getBoolean($plugin['enabled'])) {
self::$warnings[] = 'Skipping ' . $filename . '... The plugin is disabled.';
continue;
}
$warningPreTitle = 'Plugin: ' . $filename . ' - ';
foreach(self::getAllPluginsJson() as $plugin) {
$warningPreTitle = 'Plugin: ' . $plugin['name'] . ' - ';
if (isset($plugin['routes'])) {
foreach ($plugin['routes'] as $_name => $info) {
@ -106,7 +66,8 @@ class Plugins {
if ($method !== '*') {
$methods = is_string($method) ? explode(',', $info['method']) : $method;
foreach ($methods as $method) {
if (!in_array($method, ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'])) {
$method = strtolower($method);
if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'head'])) {
self::$warnings[] = $warningPreTitle . 'Not allowed method ' . $method . '... Disabling this route...';
}
}
@ -187,14 +148,14 @@ class Plugins {
}
$hooks = [];
foreach (self::$plugins as $filename => $plugin_json) {
if (isset($plugin_json['hooks'])) {
foreach ($plugin_json['hooks'] as $_name => $info) {
foreach(self::getAllPluginsJson() as $plugin) {
if (isset($plugin['hooks'])) {
foreach ($plugin['hooks'] as $_name => $info) {
if (defined('HOOK_'. $info['type'])) {
$hook = constant('HOOK_'. $info['type']);
$hooks[] = ['name' => $_name, 'type' => $hook, 'file' => $info['file']];
} else {
self::$warnings[] = 'Plugin: ' . $filename . '. Unknown event type: ' . $info['type'];
self::$warnings[] = 'Plugin: ' . $plugin['name'] . '. Unknown event type: ' . $info['type'];
}
}
}
@ -207,6 +168,41 @@ class Plugins {
return $hooks;
}
public static function getAllPluginsJson($disabled = false)
{
$cache = Cache::getInstance();
if ($cache->enabled()) {
$tmp = '';
if ($cache->fetch('plugins', $tmp)) {
return unserialize($tmp);
}
}
$plugins = [];
foreach (get_plugins($disabled) as $filename) {
$string = file_get_contents(PLUGINS . $filename . '.json');
$plugin = json_decode($string, true);
self::$plugin_json = $plugin;
if ($plugin == null) {
self::$warnings[] = 'Cannot load ' . $filename . '.json. File might be not a valid json code.';
continue;
}
if (isset($plugin['enabled']) && !getBoolean($plugin['enabled'])) {
self::$warnings[] = 'Skipping ' . $filename . '... The plugin is disabled.';
continue;
}
$plugins[] = $plugin;
}
if ($cache->enabled()) {
$cache->set('plugins', serialize($plugins), 600);
}
return $plugins;
}
public static function getPluginSettings($pluginName)
{
$plugin_json = self::getPluginJson($pluginName);
@ -234,7 +230,6 @@ class Plugins {
}
$string = file_get_contents($pathToPlugin);
$string = self::removeComments($string);
$plugin_json = json_decode($string, true);
if ($plugin_json == null) {
self::$warnings[] = 'Cannot load ' . $name . '.json. File might be not a valid json code.';
@ -289,7 +284,6 @@ class Plugins {
}
$string = file_get_contents($file_name);
$string = self::removeComments($string);
$plugin_json = json_decode($string, true);
self::$plugin_json = $plugin_json;
if ($plugin_json == null) {
@ -489,7 +483,35 @@ class Plugins {
return false;
}
public static function uninstall($plugin_name)
public static function enable($pluginFileName): bool
{
return self::enableDisable($pluginFileName, true);
}
public static function disable($pluginFileName): bool
{
return self::enableDisable($pluginFileName, false);
}
private static function enableDisable($pluginFileName, $enable): bool
{
$filenameJson = $pluginFileName . '.json';
$fileExist = is_file(PLUGINS . ($enable ? 'disabled.' : '') . $filenameJson);
if (!$fileExist) {
self::$error = 'Cannot ' . ($enable ? 'enable' : 'disable') . ' plugin: ' . $pluginFileName . '. File does not exist.';
return false;
}
$result = rename(PLUGINS . ($enable ? 'disabled.' : '') . $filenameJson, PLUGINS . ($enable ? '' : 'disabled.') . $filenameJson);
if (!$result) {
self::$error = 'Cannot ' . ($enable ? 'enable' : 'disable') . ' plugin: ' . $pluginFileName . '. Permission problem.';
return false;
}
return true;
}
public static function uninstall($plugin_name): bool
{
$filename = BASE . 'plugins/' . $plugin_name . '.json';
if(!file_exists($filename)) {
@ -497,9 +519,8 @@ class Plugins {
return false;
}
$string = file_get_contents($filename);
$string = self::removeComments($string);
$plugin_info = json_decode($string, true);
if($plugin_info == false) {
if(!$plugin_info) {
self::$error = 'Cannot load plugin info ' . $plugin_name . '.json';
return false;
}
@ -577,20 +598,8 @@ class Plugins {
return self::$error;
}
public static function removeComments($string) {
$string = preg_replace('!/\*.*?\*/!s', '', $string);
$string = preg_replace('/\n\s*\n/', "\n", $string);
// Removes multi-line comments and does not create
// a blank line, also treats white spaces/tabs
$string = preg_replace('!^[ \t]*/\*.*?\*/[ \t]*[\r\n]!s', '', $string);
// Removes single line '//' comments, treats blank characters
$string = preg_replace('![ \t]*//.*[ \t]*[\r\n]!', '', $string);
// Strip blank lines
$string = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $string);
return $string;
public static function getPluginJson() {
return self::$plugin_json;
}
/**

View File

@ -21,7 +21,6 @@
* @property string $password Password.
* @property string $eMail Email address.
* @property int $premiumEnd Timestamp of PACC end.
* @property bool $blocked Blocked flag state.
* @property bool $deleted Deleted flag state.
* @property bool $warned Warned flag state.
* @property bool $banned Ban state.
@ -39,7 +38,7 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
* @var array
* @version 0.1.5
*/
private $data = array('email' => '', 'blocked' => false, 'rlname' => '','location' => '', 'country' => '','web_flags' => 0, 'lastday' => 0, 'premdays' => 0, 'created' => 0);
private $data = array('email' => '', 'rlname' => '','location' => '', 'country' => '','web_flags' => 0, 'lastday' => 0, 'premdays' => 0, 'created' => 0);
public static $cache = array();
@ -231,26 +230,22 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
* @param int $id Account number.
* @throws PDOException On PDO operation error.
*/
public function load($id, $fresh = false, $searchOnlyById = false)
public function load($id, $fresh = false)
{
if(!$fresh && isset(self::$cache[$id])) {
$this->data = self::$cache[$id];
return;
}
$numberColumn = 'id';
$nameOrNumber = '';
if (!$searchOnlyById) {
if (USE_ACCOUNT_NAME) {
$nameOrNumber = '`name`,';
} else if (USE_ACCOUNT_NUMBER) {
$nameOrNumber = '`number`,';
$numberColumn = 'number';
}
}
// SELECT query on database
$this->data = $this->db->query('SELECT `id`, ' . $nameOrNumber . '`password`, `email`, `blocked`, `rlname`, `location`, `country`, `web_flags`, ' . ($this->db->hasColumn('accounts', 'premdays') ? '`premdays`, ' : '') . ($this->db->hasColumn('accounts', 'lastday') ? '`lastday`, ' : ($this->db->hasColumn('accounts', 'premend') ? '`premend`,' : ($this->db->hasColumn('accounts', 'premium_ends_at') ? '`premium_ends_at`,' : ''))) . '`created` FROM `accounts` WHERE `' . $numberColumn . '` = ' . (int) $id)->fetch();
$this->data = $this->db->query('SELECT `id`, ' . $nameOrNumber . '`password`, `email`, `rlname`, `location`, `country`, `web_flags`, ' . ($this->db->hasColumn('accounts', 'premdays') ? '`premdays`, ' : '') . ($this->db->hasColumn('accounts', 'lastday') ? '`lastday`, ' : ($this->db->hasColumn('accounts', 'premend') ? '`premend`,' : ($this->db->hasColumn('accounts', 'premium_ends_at') ? '`premium_ends_at`,' : ''))) . '`created` FROM `accounts` WHERE `id` = ' . (int) $id)->fetch();
self::$cache[$id] = $this->data;
}
@ -268,8 +263,13 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
*/
public function find($name)
{
$nameOrNumberColumn = 'name';
if (USE_ACCOUNT_NUMBER) {
$nameOrNumberColumn = 'number';
}
// finds player's ID
$id = $this->db->query('SELECT `id` FROM `accounts` WHERE `name` = ' . $this->db->quote($name) )->fetch();
$id = $this->db->query('SELECT `id` FROM `accounts` WHERE `' . $nameOrNumberColumn . '` = ' . $this->db->quote($name) )->fetch();
// if anything was found
if( isset($id['id']) )
@ -345,7 +345,7 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
}
// UPDATE query on database
$this->db->exec('UPDATE `accounts` SET ' . ($this->db->hasColumn('accounts', 'name') ? '`name` = ' . $this->db->quote($this->data['name']) . ',' : '') . '`password` = ' . $this->db->quote($this->data['password']) . ', `email` = ' . $this->db->quote($this->data['email']) . ', `blocked` = ' . (int) $this->data['blocked'] . ', `rlname` = ' . $this->db->quote($this->data['rlname']) . ', `location` = ' . $this->db->quote($this->data['location']) . ', `country` = ' . $this->db->quote($this->data['country']) . ', `web_flags` = ' . (int) $this->data['web_flags'] . ', ' . ($this->db->hasColumn('accounts', 'premdays') ? '`premdays` = ' . (int) $this->data['premdays'] . ',' : '') . '`' . $field . '` = ' . (int) $this->data[$field] . ' WHERE `id` = ' . $this->data['id']);
$this->db->exec('UPDATE `accounts` SET ' . ($this->db->hasColumn('accounts', 'name') ? '`name` = ' . $this->db->quote($this->data['name']) . ',' : '') . '`password` = ' . $this->db->quote($this->data['password']) . ', `email` = ' . $this->db->quote($this->data['email']) . ', `rlname` = ' . $this->db->quote($this->data['rlname']) . ', `location` = ' . $this->db->quote($this->data['location']) . ', `country` = ' . $this->db->quote($this->data['country']) . ', `web_flags` = ' . (int) $this->data['web_flags'] . ', ' . ($this->db->hasColumn('accounts', 'premdays') ? '`premdays` = ' . (int) $this->data['premdays'] . ',' : '') . '`' . $field . '` = ' . (int) $this->data[$field] . ' WHERE `id` = ' . $this->data['id']);
}
/**
@ -650,53 +650,6 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
$this->data['email'] = (string) $email;
}
/**
* Checks if account is blocked.
*
* <p>
* Note: Since 0.0.3 version this method throws {@link E_OTS_NotLoaded E_OTS_NotLoaded} exception instead of triggering E_USER_WARNING.
* </p>
*
* @version 0.0.3
* @return bool Blocked state.
* @throws E_OTS_NotLoaded If account is not loaded.
*/
public function isBlocked()
{
if( !isset($this->data['blocked']) )
{
throw new E_OTS_NotLoaded();
}
return $this->data['blocked'];
}
/**
* Unblocks account.
*
* <p>
* This method only updates object state. To save changes in database you need to use {@link OTS_Account::save() save() method} to flush changed to database.
* </p>
*/
public function unblock()
{
$this->data['blocked'] = false;
}
/**
* Blocks account.
*
* <p>
* This method only updates object state. To save changes in databaseed to use {@link OTS_Account::save() save() method} to flush changed to database.
* </p>
*/
public function block()
{
$this->data['blocked'] = true;
}
/**
* Reads custom field.
*
@ -1147,9 +1100,6 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
case 'playersList':
return $this->getPlayersList();
case 'blocked':
return $this->isBlocked();
case 'deleted':
return $this->isDeleted();
@ -1195,17 +1145,6 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
$this->setPremiumEnd($value);
break;
case 'blocked':
if($value)
{
$this->block();
}
else
{
$this->unblock();
}
break;
case 'deleted':
if($value)
{

View File

@ -36,6 +36,7 @@
* @property-read int $armor Armor rate.
* @property-read array $defenses List of defenses.
* @property-read array $attacks List of attacks.
* @property-read array $look List of looks.
*/
class OTS_Monster extends DOMDocument
{
@ -273,6 +274,30 @@ class OTS_Monster extends DOMDocument
return $loot;
}
/**
* Returns look of the monster.
*
* @return array Look with all the attributes of the look.
* @throws DOMException On DOM operation error.
*/
public function getLook()
{
$look = array();
$element = $this->documentElement->getElementsByTagName('look')->item(0);
$look['type'] = $element->getAttribute('type');
$look['typeex'] = $element->getAttribute('typeex');
$look['head'] = $element->getAttribute('head');
$look['body'] = $element->getAttribute('body');
$look['legs'] = $element->getAttribute('legs');
$look['feet'] = $element->getAttribute('feet');
$look['addons'] = $element->getAttribute('addons');
$look['corpse'] = $element->getAttribute('corpse');
return $look;
}
/**
* Returns all monster summons.
*
@ -560,6 +585,9 @@ class OTS_Monster extends DOMDocument
case 'attacks':
return $this->getAttacks();
case 'look':
return $this->getLook();
default:
throw new OutOfBoundsException();
}

View File

@ -174,6 +174,7 @@ class OTS_MonstersList implements Iterator, Countable, ArrayAccess
* @return OTS_Monster Monster.
* @throws DOMException On DOM operation error.
*/
#[\ReturnTypeWillChange]
public function current()
{
return $this->getMonster( key($this->monsters) );
@ -187,7 +188,7 @@ class OTS_MonstersList implements Iterator, Countable, ArrayAccess
/**
* Moves to next iterator monster.
*/
public function next()
public function next(): void
{
next($this->monsters);
}
@ -197,6 +198,7 @@ class OTS_MonstersList implements Iterator, Countable, ArrayAccess
*
* @return string Current position key.
*/
#[\ReturnTypeWillChange]
public function key()
{
return key($this->monsters);
@ -207,7 +209,7 @@ class OTS_MonstersList implements Iterator, Countable, ArrayAccess
*
* @return bool If iterator has anything more.
*/
public function valid()
public function valid(): bool
{
return key($this->monsters) !== null;
}
@ -215,7 +217,7 @@ class OTS_MonstersList implements Iterator, Countable, ArrayAccess
/**
* Resets iterator index.
*/
public function rewind()
public function rewind(): void
{
reset($this->monsters);
}
@ -226,6 +228,7 @@ class OTS_MonstersList implements Iterator, Countable, ArrayAccess
* @param string $offset Array key.
* @return bool True if it's set.
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return isset($this->monsters[$offset]);
@ -239,6 +242,7 @@ class OTS_MonstersList implements Iterator, Countable, ArrayAccess
* @return OTS_Monster Monster instance.
* @throws DOMException On DOM operation error.
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->getMonster($offset);
@ -251,6 +255,7 @@ class OTS_MonstersList implements Iterator, Countable, ArrayAccess
* @param mixed $value Field value.
* @throws E_OTS_ReadOnly Always - this class is read-only.
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
throw new E_OTS_ReadOnly();
@ -262,6 +267,7 @@ class OTS_MonstersList implements Iterator, Countable, ArrayAccess
* @param string|int $offset Array key.
* @throws E_OTS_ReadOnly Always - this class is read-only.
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
throw new E_OTS_ReadOnly();

View File

@ -398,7 +398,7 @@ class OTS_Player extends OTS_Row_DAO
}
// UPDATE query on database
$this->db->query('UPDATE ' . $this->db->tableName('players') . ' SET ' . $this->db->fieldName('name') . ' = ' . $this->db->quote($this->data['name']) . ', ' . $this->db->fieldName('account_id') . ' = ' . $this->data['account_id'] . ', ' . $this->db->fieldName('group_id') . ' = ' . $this->data['group_id'] . ', ' . $this->db->fieldName('sex') . ' = ' . $this->data['sex'] . ', ' . $this->db->fieldName('vocation') . ' = ' . $this->data['vocation'] . ', ' . $this->db->fieldName('experience') . ' = ' . $this->data['experience'] . ', ' . $this->db->fieldName('level') . ' = ' . $this->data['level'] . ', ' . $this->db->fieldName('maglevel') . ' = ' . $this->data['maglevel'] . ', ' . $this->db->fieldName('health') . ' = ' . $this->data['health'] . ', ' . $this->db->fieldName('healthmax') . ' = ' . $this->data['healthmax'] . ', ' . $this->db->fieldName('mana') . ' = ' . $this->data['mana'] . ', ' . $this->db->fieldName('manamax') . ' = ' . $this->data['manamax'] . ', ' . $this->db->fieldName('manaspent') . ' = ' . $this->data['manaspent'] . ', ' . $this->db->fieldName('soul') . ' = ' . $this->data['soul'] . ', ' . $this->db->fieldName('lookbody') . ' = ' . $this->data['lookbody'] . ', ' . $this->db->fieldName('lookfeet') . ' = ' . $this->data['lookfeet'] . ', ' . $this->db->fieldName('lookhead') . ' = ' . $this->data['lookhead'] . ', ' . $this->db->fieldName('looklegs') . ' = ' . $this->data['looklegs'] . ', ' . $this->db->fieldName('looktype') . ' = ' . $this->data['looktype'] . $lookaddons . ', ' . $this->db->fieldName('posx') . ' = ' . $this->data['posx'] . ', ' . $this->db->fieldName('posy') . ' = ' . $this->data['posy'] . ', ' . $this->db->fieldName('posz') . ' = ' . $this->data['posz'] . ', ' . $this->db->fieldName('cap') . ' = ' . $this->data['cap'] . ', ' . $this->db->fieldName('lastlogin') . ' = ' . $this->data['lastlogin'] . ', ' . $this->db->fieldName('lastlogout') . ' = ' . $this->data['lastlogout'] . ', ' . $this->db->fieldName('lastip') . ' = ' . $this->data['lastip'] . ', ' . $this->db->fieldName('save') . ' = ' . (int) $this->data['save'] . ', ' . $this->db->fieldName('conditions') . ' = ' . $this->db->quote($this->data['conditions']) . ', `' . $skull_time . '` = ' . $this->data['skulltime'] . ', `' . $skull_type . '` = ' . (int) $this->data['skull'] . $guild_info . ', ' . $this->db->fieldName('town_id') . ' = ' . $this->data['town_id'] . $loss . $loss_items . ', ' . $this->db->fieldName('balance') . ' = ' . $this->data['balance'] . $blessings . $stamina . $direction . ' WHERE ' . $this->db->fieldName('id') . ' = ' . $this->data['id']);
$this->db->query('UPDATE ' . $this->db->tableName('players') . ' SET ' . $this->db->fieldName('name') . ' = ' . $this->db->quote($this->data['name']) . ', ' . $this->db->fieldName('account_id') . ' = ' . $this->data['account_id'] . ', ' . $this->db->fieldName('group_id') . ' = ' . $this->data['group_id'] . ', ' . $this->db->fieldName('sex') . ' = ' . $this->data['sex'] . ', ' . $this->db->fieldName('vocation') . ' = ' . $this->data['vocation'] . ', ' . $this->db->fieldName('experience') . ' = ' . $this->data['experience'] . ', ' . $this->db->fieldName('level') . ' = ' . $this->data['level'] . ', ' . $this->db->fieldName('maglevel') . ' = ' . $this->data['maglevel'] . ', ' . $this->db->fieldName('health') . ' = ' . $this->data['health'] . ', ' . $this->db->fieldName('healthmax') . ' = ' . $this->data['healthmax'] . ', ' . $this->db->fieldName('mana') . ' = ' . $this->data['mana'] . ', ' . $this->db->fieldName('manamax') . ' = ' . $this->data['manamax'] . ', ' . $this->db->fieldName('manaspent') . ' = ' . $this->data['manaspent'] . ', ' . $this->db->fieldName('soul') . ' = ' . $this->data['soul'] . ', ' . $this->db->fieldName('lookbody') . ' = ' . $this->data['lookbody'] . ', ' . $this->db->fieldName('lookfeet') . ' = ' . $this->data['lookfeet'] . ', ' . $this->db->fieldName('lookhead') . ' = ' . $this->data['lookhead'] . ', ' . $this->db->fieldName('looklegs') . ' = ' . $this->data['looklegs'] . ', ' . $this->db->fieldName('looktype') . ' = ' . $this->data['looktype'] . $lookaddons . ', ' . $this->db->fieldName('posx') . ' = ' . $this->data['posx'] . ', ' . $this->db->fieldName('posy') . ' = ' . $this->data['posy'] . ', ' . $this->db->fieldName('posz') . ' = ' . $this->data['posz'] . ', ' . $this->db->fieldName('cap') . ' = ' . $this->data['cap'] . ', ' . $this->db->fieldName('lastlogin') . ' = ' . $this->data['lastlogin'] . ', ' . $this->db->fieldName('lastlogout') . ' = ' . $this->data['lastlogout'] . ', ' . $this->db->fieldName('lastip') . ' = ' . $this->db->quote($this->data['lastip']) . ', ' . $this->db->fieldName('save') . ' = ' . (int) $this->data['save'] . ', ' . $this->db->fieldName('conditions') . ' = ' . $this->db->quote($this->data['conditions']) . ', `' . $skull_time . '` = ' . $this->data['skulltime'] . ', `' . $skull_type . '` = ' . (int) $this->data['skull'] . $guild_info . ', ' . $this->db->fieldName('town_id') . ' = ' . $this->data['town_id'] . $loss . $loss_items . ', ' . $this->db->fieldName('balance') . ' = ' . $this->data['balance'] . $blessings . $stamina . $direction . ' WHERE ' . $this->db->fieldName('id') . ' = ' . $this->data['id']);
}
// creates new player
else
@ -602,7 +602,7 @@ class OTS_Player extends OTS_Row_DAO
}
$account = new OTS_Account();
$account->load($this->data['account_id'], false, true);
$account->load($this->data['account_id']);
return $account;
}

View File

@ -308,7 +308,7 @@ class OTS_SpellsList implements IteratorAggregate, Countable
* @since 0.1.5
* @return AppendIterator Iterator for all spells.
*/
public function getIterator()
public function getIterator(): Traversable
{
$iterator = new AppendIterator();
$iterator->append( new ArrayIterator($this->runes) );

View File

@ -34,10 +34,12 @@ class Visitors
$this->cleanVisitors();
$ip = $_SERVER['REMOTE_ADDR'];
$userAgentShortened = substr($_SERVER['HTTP_USER_AGENT'] ?? 'unknown', 0, 255);
if($this->visitorExists($ip))
$this->updateVisitor($ip, $_SERVER['REQUEST_URI']);
$this->updateVisitor($ip, $_SERVER['REQUEST_URI'], $userAgentShortened);
else
$this->addVisitor($ip, $_SERVER['REQUEST_URI']);
$this->addVisitor($ip, $_SERVER['REQUEST_URI'], $userAgentShortened);
}
public function __destruct()
@ -75,26 +77,26 @@ class Visitors
$db->exec('DELETE FROM ' . $db->tableName(TABLE_PREFIX . 'visitors') . ' WHERE ' . $db->fieldName('lastvisit') . ' < ' . (time() - $this->sessionTime * 60));
}
private function updateVisitor($ip, $page)
private function updateVisitor($ip, $page, $userAgent)
{
if($this->cacheEnabled) {
$this->data[$ip] = array('page' => $page, 'lastvisit' => time());
$this->data[$ip] = array('page' => $page, 'lastvisit' => time(), 'user_agent' => $userAgent);
return;
}
global $db;
$db->exec('UPDATE ' . $db->tableName(TABLE_PREFIX . 'visitors') . ' SET ' . $db->fieldName('lastvisit') . ' = ' . time() . ', ' . $db->fieldName('page') . ' = ' . $db->quote($page) . ' WHERE ' . $db->fieldName('ip') . ' = ' . $db->quote($ip));
$db->update(TABLE_PREFIX . 'visitors', ['lastvisit' => time(), 'page' => $page, 'user_agent' => $userAgent], ['ip' => $ip]);
}
private function addVisitor($ip, $page)
private function addVisitor($ip, $page, $userAgent)
{
if($this->cacheEnabled) {
$this->data[$ip] = array('page' => $page, 'lastvisit' => time());
$this->data[$ip] = array('page' => $page, 'lastvisit' => time(), 'user_agent' => $userAgent);
return;
}
global $db;
$db->exec('INSERT INTO ' . $db->tableName(TABLE_PREFIX . 'visitors') . ' (' . $db->fieldName('ip') . ' ,' . $db->fieldName('lastvisit') . ', ' . $db->fieldName('page') . ') VALUE (' . $db->quote($ip) . ', ' . time() . ', ' . $db->quote($page) . ')');
$db->insert(TABLE_PREFIX . 'visitors', ['ip' => $ip, 'lastvisit' => time(), 'page' => $page, 'user_agent' => $userAgent]);
}
public function getVisitors()
@ -107,7 +109,7 @@ class Visitors
}
global $db;
return $db->query('SELECT ' . $db->fieldName('ip') . ', ' . $db->fieldName('lastvisit') . ', ' . $db->fieldName('page') . ' FROM ' . $db->tableName(TABLE_PREFIX . 'visitors') . ' ORDER BY ' . $db->fieldName('lastvisit') . ' DESC')->fetchAll();
return $db->query('SELECT ' . $db->fieldName('ip') . ', ' . $db->fieldName('lastvisit') . ', ' . $db->fieldName('page') . ', ' . $db->fieldName('user_agent') . ' FROM ' . $db->tableName(TABLE_PREFIX . 'visitors') . ' ORDER BY ' . $db->fieldName('lastvisit') . ' DESC')->fetchAll();
}
public function getAmountVisitors()

View File

@ -1,4 +1,13 @@
<?php
/**
* Logout from account
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
if(isset($account_logged) && $account_logged->isLoaded()) {
if($hooks->trigger(HOOK_LOGOUT, ['account_id' => $account_logged->getId()])) {

View File

@ -1,4 +1,13 @@
<?php
/**
* Database migrations
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
// database migrations
$tmp = '';

View File

@ -1,13 +1,4 @@
<?php
// add user_agent column into visitors
if(!$db->hasTable(TABLE_PREFIX . 'settings')) {
$db->exec("CREATE TABLE `" . TABLE_PREFIX . "settings`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`plugin_name` VARCHAR(255) NOT NULL DEFAULT '',
`key` VARCHAR(255) NOT NULL DEFAULT '',
`value` TEXT NOT NULL,
PRIMARY KEY (`id`),
KEY `key` (`key`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;");
}
$db->exec('ALTER TABLE `' . TABLE_PREFIX . "visitors` ADD `user_agent` VARCHAR(255) NOT NULL DEFAULT '';");

3
system/migrations/35.php Normal file
View File

@ -0,0 +1,3 @@
<?php
// add look column
$db->exec('ALTER TABLE `' . TABLE_PREFIX . "monsters` ADD `look` VARCHAR(255) NOT NULL DEFAULT '' AFTER `health`;");

1
system/migrations/36.php Normal file
View File

@ -0,0 +1 @@
<?php

View File

@ -26,11 +26,11 @@ if(empty($new_password) && empty($new_password2) && empty($old_password)) {
else
{
if(empty($new_password) || empty($new_password2) || empty($old_password)){
$errors[] = "Please fill in form.";
$errors[] = 'Please fill in form.';
}
$password_strlen = strlen($new_password);
if($new_password != $new_password2) {
$errors[] = "The new passwords do not match!";
$errors[] = 'The new passwords do not match!';
}
if(empty($errors)) {
@ -41,9 +41,12 @@ else
/** @var OTS_Account $account_logged */
$old_password = encrypt((USE_ACCOUNT_SALT ? $account_logged->getCustomField('salt') : '') . $old_password);
if($old_password != $account_logged->getPassword()) {
$errors[] = "Current password is incorrect!";
$errors[] = 'Current password is incorrect!';
}
$hooks->trigger(HOOK_ACCOUNT_CHANGE_PASSWORD_POST);
}
if(!empty($errors)){
//show errors
$twig->display('error_box.html.twig', array('errors' => $errors));
@ -51,12 +54,10 @@ else
//show form
$twig->display('account.change_password.html.twig');
}
else
{
else {
$org_pass = $new_password;
if(USE_ACCOUNT_SALT)
{
if(USE_ACCOUNT_SALT) {
$salt = generateRandomString(10, false, true, true);
$new_password = $salt . $new_password;
$account_logged->setCustomField('salt', $salt);
@ -68,18 +69,19 @@ else
$account_logged->logAction('Account password changed.');
$message = '';
if($config['mail_enabled'] && $config['send_mail_when_change_password'])
{
if($config['mail_enabled'] && $config['send_mail_when_change_password']) {
$mailBody = $twig->render('mail.password_changed.html.twig', array(
'new_password' => $org_pass,
'ip' => get_browser_real_ip(),
));
if(_mail($account_logged->getEMail(), $config['lua']['serverName']." - Changed password", $mailBody))
$message = '<br/><small>Your new password were send on email address <b>'.$account_logged->getEMail().'</b>.</small>';
else
if(_mail($account_logged->getEMail(), $config['lua']['serverName']." - Changed password", $mailBody)) {
$message = '<br/><small>Your new password were send on email address <b>' . $account_logged->getEMail() . '</b>.</small>';
}
else {
$message = '<br/><p class="error">An error occurred while sending email. For Admin: More info can be found in system/logs/mailer-error.log</p>';
}
}
$twig->display('success.html.twig', array(
'title' => 'Password Changed',

View File

@ -26,8 +26,13 @@ if(config('account_create_character_create')) {
}
$account_type = 'number';
if(USE_ACCOUNT_NAME) {
if (config('account_login_by_email')) {
$account_type = 'Email Address';
}
else {
if(USE_ACCOUNT_NAME) {
$account_type = 'name';
}
}
$errors = array();
@ -156,9 +161,12 @@ if($save)
if(empty($errors))
{
$hasBeenCreatedByEMail = false;
$new_account = new OTS_Account();
if (config('account_login_by_email')) {
$new_account->createWithEmail($email);
$hasBeenCreatedByEMail = true;
}
else {
if(USE_ACCOUNT_NAME)
@ -175,7 +183,6 @@ if($save)
$new_account->setPassword(encrypt($password));
$new_account->setEMail($email);
$new_account->unblock();
$new_account->save();
if(USE_ACCOUNT_SALT)
@ -247,14 +254,21 @@ if($save)
$character_created = $createCharacter->doCreate($character_name, $character_sex, $character_vocation, $character_town, $new_account, $errors);
if (!$character_created) {
error('There was an error creating your character. Please create your character later in account management page.');
error(implode(' ', $errors));
}
}
if($config['account_create_auto_login']) {
if(config('account_create_auto_login')) {
if ($hasBeenCreatedByEMail) {
$_POST['account_login'] = $email;
}
else {
$_POST['account_login'] = USE_ACCOUNT_NAME ? $account_name : $account_id;
}
$_POST['password_login'] = $password2;
require SYSTEM . 'login.php';
require PAGES . 'account/login.php';
header('Location: ' . getLink('account/manage'));
}

View File

@ -1,4 +1,15 @@
<?php
/**
* Login
*
* @package MyAAC
* @author Gesior <jerzyskalski@wp.pl>
* @author Slawkens <slawkens@gmail.com>
* @copyright 2023 MyAAC
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
$title = 'Login';
// new login with data from form
if(!$logged && isset($_POST['account_login'], $_POST['password_login']))
@ -37,7 +48,7 @@ if(!$logged && isset($_POST['account_login'], $_POST['password_login']))
}
if (!config('account_login_by_email') || config('account_login_by_email_fallback')) {
if(USE_ACCOUNT_NAME) {
if(USE_ACCOUNT_NAME || USE_ACCOUNT_NUMBER) {
$account_logged->find($login_account);
} else {
$account_logged->load($login_account, true);
@ -48,7 +59,7 @@ if(!$logged && isset($_POST['account_login'], $_POST['password_login']))
&& (!isset($t) || $t['attempts'] < 5)
)
{
setSession('account', $account_logged->getNumber());
setSession('account', $account_logged->getId());
setSession('password', encrypt((USE_ACCOUNT_SALT ? $account_logged->getCustomField('salt') : '') . $login_password));
if($remember_me) {
setSession('remember_me', true);

View File

@ -40,7 +40,7 @@ elseif($action == 'step1' && $action_type == 'email')
{
if($account->getCustomField('email_next') < time())
echo 'Please enter e-mail to account with this character.<BR>
<form action="?subtopic=lostaccount&action=sendcode" method=post>
<form action="' . getLink('account/lost') . '?action=sendcode" method=post>
<input type=hidden name="character">
<table cellspacing=1 cellpadding=4 border=0 width=100%>
<TR><TD BGCOLOR="'.$config['vdarkborder'].'" class="white"><B>Please enter e-mail to account</B></TD></TR>
@ -68,7 +68,7 @@ elseif($action == 'step1' && $action_type == 'email')
else
echo 'Invalid player name format. If you have other characters on account try with other name.';
echo '<BR /><TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
<a href="?subtopic=lostaccount" border="0"><IMG SRC="'.$template_path.'/images/global/buttons/sbutton_back.gif" NAME="Back" ALT="Back" BORDER=0 WIDTH=120 HEIGHT=18></a></div>
<a href="' . getLink('account/lost') . '" border="0"><IMG SRC="'.$template_path.'/images/global/buttons/sbutton_back.gif" NAME="Back" ALT="Back" BORDER=0 WIDTH=120 HEIGHT=18></a></div>
</TD></TR></FORM></TABLE></TABLE>';
}
elseif($action == 'sendcode')
@ -95,8 +95,8 @@ elseif($action == 'sendcode')
<p>Account name: '.$account->getName().'</p>
<br />
To do so, please click this link:
<p><a href="' . BASE_URL . '?subtopic=lostaccount&action=checkcode&code='.$newcode.'&character='.urlencode($nick).'">'.BASE_URL.'/?subtopic=lostaccount&action=checkcode&code='.$newcode.'&character='.urlencode($nick).'</a></p>
<p>or open page: <i>' . BASE_URL . '?subtopic=lostaccount&action=checkcode</i> and in field "code" write <b>'.$newcode.'</b></p>
<p><a href="' . getLink('account/lost') . '?action=checkcode&code='.$newcode.'&character='.urlencode($nick).'">'.BASE_URL.'/?subtopic=lostaccount&action=checkcode&code='.$newcode.'&character='.urlencode($nick).'</a></p>
<p>or open page: <i>' . getLink('account/lost') . '?action=checkcode</i> and in field "code" write <b>'.$newcode.'</b></p>
<br/>
<p>If you did not request a password change, you may ignore this message and your password will remain unchanged.';
@ -131,7 +131,7 @@ elseif($action == 'sendcode')
else
echo 'Invalid player name format. If you have other characters on account try with other name.';
echo '<BR /><TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
<a href="?subtopic=lostaccount&action=step1&action_type=email&nick='.urlencode($nick).'" border="0"><IMG SRC="'.$template_path.'/images/global/buttons/sbutton_back.gif" NAME="Back" ALT="Back" BORDER=0 WIDTH=120 HEIGHT=18></a></div>
<a href="' . getLink('account/lost') . '?action=step1&action_type=email&nick='.urlencode($nick).'" border="0"><IMG SRC="'.$template_path.'/images/global/buttons/sbutton_back.gif" NAME="Back" ALT="Back" BORDER=0 WIDTH=120 HEIGHT=18></a></div>
</TD></TR></FORM></TABLE></TABLE>';
}
elseif($action == 'step1' && $action_type == 'reckey')
@ -150,7 +150,7 @@ elseif($action == 'step1' && $action_type == 'reckey')
if(!empty($account_key))
{
echo 'If you enter right recovery key you will see form to set new e-mail and password to account. To this e-mail will be send your new password and account name.<BR>
<FORM ACTION="?subtopic=lostaccount&action=step2" METHOD=post>
<FORM ACTION="' . getLink('account/lost') . '?action=step2" METHOD=post>
<TABLE CELLSPACING=1 CELLPADDING=4 BORDER=0 WIDTH=100%>
<TR><TD BGCOLOR="'.$config['vdarkborder'].'" class="white"><B>Please enter your recovery key</B></TD></TR>
<TR><TD BGCOLOR="'.$config['darkborder'].'">

View File

@ -86,7 +86,7 @@ $twig->display('account.management.html.twig', array(
'email_request' => $email_request,
'email_new_time' => $email_new_time,
'email_new' => isset($email_new) ? $email_new : '',
'account' => USE_ACCOUNT_NAME ? $account_logged->getName() : $account_logged->getId(),
'account' => (USE_ACCOUNT_NAME ? $account_logged->getName() : (USE_ACCOUNT_NUMBER ? $account_logged->getNumber() : $account_logged->getId())),
'account_email' => $account_email,
'account_created' => $account_created,
'account_status' => $account_status,

View File

@ -201,8 +201,7 @@ if($player->isLoaded() && !$player->isDeleted())
unset($storage);
}
if($config['characters']['equipment']) {
global $db;
if($config['characters']['equipment'] && $db->hasTable('player_items') && $db->hasColumn('player_items', 'pid') && $db->hasColumn('player_items', 'sid') && $db->hasColumn('player_items', 'itemtype')) {
$eq_sql = $db->query('SELECT `pid`, `itemtype` FROM player_items WHERE player_id = '.$player->getId().' AND (`pid` >= 1 and `pid` <= 10)');
$equipment = array();
foreach($eq_sql as $eq)
@ -285,7 +284,7 @@ WHERE killers.death_id = '".$death['id']."' ORDER BY killers.final_hit DESC, kil
$deaths[] = array('time' => $death['date'], 'description' => $description . '.');
}
}
} else {
} else if ($db->hasColumn('player_deaths', 'time') && $db->hasColumn('player_deaths', 'level') && $db->hasColumn('player_deaths', 'killed_by') && $db->hasColumn('player_deaths', 'is_player')) {
$mostdamage = '';
if($db->hasColumn('player_deaths', 'mostdamage_by'))
$mostdamage = ', `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`';

View File

@ -101,14 +101,15 @@ if(isset($_GET['image']))
return;
}
$images =
$db->query('SELECT `id`, `comment`, `image`, `author`, `thumb`' .
$images = Cache::remember('gallery_' . ($canEdit ? '1' : '0'), 60, function () use ($db, $canEdit) {
return $db->query('SELECT `id`, `comment`, `image`, `author`, `thumb`' .
($canEdit ? ', `hidden`, `ordering`' : '') .
' FROM `' . TABLE_PREFIX . 'gallery`' .
(!$canEdit ? ' WHERE `hidden` != 1' : '') .
' ORDER BY `ordering`;');
' ORDER BY `ordering`;')->fetchAll(PDO::FETCH_ASSOC);
});
$last = $images->rowCount();
$last = count($images);
if(!$last)
{
?>

View File

@ -148,6 +148,8 @@ if($db->hasColumn('players', 'guildnick'))
$twig->display('guilds.view.html.twig', array(
'logo' => $guild_logo,
'guild' => $guild,
'guild_id' => $guild->getId(),
'guild_name' => $guild_name,
'description' => $description,
'guild_owner' => $guild_owner->isLoaded() ? $guild_owner : null,

View File

@ -165,6 +165,8 @@ if(isset($_POST['town']) && isset($_POST['state']) && isset($_POST['order']) &&
foreach($players_info->fetchAll() as $player)
$players[$player['houseid']] = array('name' => $player['ownername']);
$hasTilesColumn = $db->hasColumn('houses', 'tiles');
$houses = array();
foreach($houses_info->fetchAll() as $house)
{
@ -185,7 +187,7 @@ if(isset($_POST['town']) && isset($_POST['state']) && isset($_POST['order']) &&
$houseRent = 'Free';
}
$houses[] = array('owner' => $owner, 'name' => $house['name'], 'size' => $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);
}
$housesSearch = true;

View File

@ -4,9 +4,10 @@
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2021 MyAAC
* @copyright 2023 MyAAC
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
if(!isset($content[0]))
$content = '';
@ -55,10 +56,9 @@ if (BASE_DIR !== '') {
define('URI', $uri);
/** @var boolean $load_it */
if(!$load_it) {
// ignore warnings in some functions/plugins
// page is not loaded anyways
// page is not loaded anyway
define('PAGE', '');
return;
@ -115,10 +115,22 @@ $dispatcher = FastRoute\cachedDispatcher(function (FastRoute\RouteCollector $r)
if ($route[0] === '*') {
$route[0] = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'];
}
else {
if (is_string($route[0])) {
$route[0] = explode(',', $route[0]);
}
$toUpperCase = function(string $value): string {
return trim(strtoupper($value));
};
// convert to upper case, fast-route accepts only upper case
$route[0] = array_map($toUpperCase, $route[0]);
}
$aliases = [
[':int', ':string', ':alphanum'],
[':\d+', ':[A-Za-z0-9-_%+\']+}', ':[A-Za-z0-9]+'],
[':\d+', ':[A-Za-z0-9-_%+\' ]+', ':[A-Za-z0-9]+'],
];
// apply aliases
@ -147,6 +159,10 @@ $found = true;
// old support for pages like /?subtopic=accountmanagement
$page = $_REQUEST['p'] ?? ($_REQUEST['subtopic'] ?? '');
if(!empty($page) && preg_match('/^[A-z0-9\-]+$/', $page)) {
if (isset($_REQUEST['p'])) { // some plugins may require this
$_REQUEST['subtopic'] = $_REQUEST['p'];
}
if (config('backward_support')) {
require SYSTEM . 'compat/pages.php';
}
@ -161,7 +177,6 @@ else {
switch ($routeInfo[0]) {
case FastRoute\Dispatcher::NOT_FOUND:
// ... 404 Not Found
//var_dump('not found');
/**
* Fallback to load page from templates/ or system/pages/ directory
*/
@ -282,7 +297,8 @@ function getDatabasePages() {
return $ret;
}
function loadPageFromFileSystem($page, &$found) {
function loadPageFromFileSystem($page, &$found): string
{
$file = SYSTEM . 'pages/' . $page . '.php';
if (!is_file($file)) {
// feature: convert camelCase to snake_case

View File

@ -7,9 +7,10 @@
* @copyright 2021 MyAAC
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
return [
['GET', '', '__redirect__/news'], // redirect empty URL to news
['GET', '', '__redirect__/' . (config('friendly_urls') ? '' : 'index.php/') . 'news'], // redirect empty URL to news
['GET', 'news/archive/{id:int}[/]', 'news/archive.php'],
// block access to some files
@ -26,12 +27,12 @@ return [
[['GET', 'POST'], 'account/character/sex[/]', 'account/change_sex.php'],
[['GET', 'POST'], 'account/character/delete[/]', 'account/delete_character.php'],
[['GET', 'POST'], 'account/character/comment[/{name:[A-Za-z0-9-_%+\']+}]', 'account/change_comment.php'],
['GET', 'account/confirm_email/{hash:[A-Za-z0-9-_]+}[/]', 'account/confirm_email.php'],
['GET', 'account/confirm_email/{hash:alphanum}[/]', 'account/confirm_email.php'],
['GET', 'bans/{page:\d+}[/]', 'bans.php'],
[['GET', 'POST'], 'characters[/{name:string]', 'characters.php'],
['GET', 'bans/{page:int}[/]', 'bans.php'],
[['GET', 'POST'], 'characters[/{name:string}]', 'characters.php'],
['GET', 'changelog[/{page:int}]', 'changelog.php'],
['GET', 'creatures[/{name:string}]', 'creatures.php'],
[['GET', 'POST'], 'creatures[/{name:string}]', 'creatures.php'],
['GET', 'faq[/{action:string}]', 'faq.php'],

View File

@ -0,0 +1,6 @@
{% if new_line is defined and new_line %}
<br/>
{% endif %}
<form action="{% if action is not defined %}{{ getLink('account/manage') }}{% else %}{{ action }}{% endif %}" method="post">
{{ include('buttons.back.html.twig') }}
</form>

View File

@ -89,10 +89,12 @@
<a name="General+Information"></a>
<h2>General Information</h2>
<table width="100%">
{% if not config.account_login_by_email or config.account_login_by_email_fallback %}
<tr style="background-color: {{ config.lightborder }};" >
<td style="width: 90px;">Account {% if constant('USE_ACCOUNT_NAME') %}Name{% else %}Number{% endif %}:</td>
<td>{{ account }}</td>
</tr>
{% endif %}
<tr style="background-color: {{ config.darkborder }};" >
<td style="width: 90px;">Email Address:</td>
<td>{{ account_email ~ email_change }}

View File

@ -6,6 +6,7 @@
<table class="table table-striped table-bordered table-responsive d-md-table" id="tb_plugins">
<thead>
<tr>
<th>Enabled</th>
<th>Name</th>
<th>Version</th>
<th>Author</th>
@ -16,6 +17,17 @@
<tbody>
{% for plugin in plugins %}
<tr>
<td>
{% if plugin.enabled %}
<a href="?p=plugins&disable={{ plugin.file }}" class="btn btn-success" onclick="return confirm('Are you sure you want to disable plugin {{ plugin.name }}?');" title="Disable">
<i class="fas fa-check"></i> Enabled
</a>
{% else %}
<a href="?p=plugins&enable={{ plugin.file }}" class="btn btn-danger" onclick="return confirm('Are you sure you want to enable plugin {{ plugin.name }}?');" title="Enable">
<i class="fas fa-ban"></i> Disabled
</a>
{% endif %}
</td>
<td><b>{{ plugin.name }}</b><br>
<small>{{ plugin.description|raw }}</small>
</td>
@ -26,10 +38,11 @@
<td>{{ plugin.file }}.json</td>
<td>
{% if plugin.uninstall %}
<a href="?p=plugins&uninstall={{ plugin.file }}" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure?');" title="Uninstall">
<a href="?p=plugins&uninstall={{ plugin.file }}" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to uninstall {{ plugin.name }}?');" title="Uninstall">
<i class="fas fa-trash"></i>
</a>
{% endif %}</td>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
@ -39,6 +52,6 @@
<script>
$(function () {
$('#tb_plugins').DataTable()
$('#tb_plugins').DataTable();
})
</script>

View File

@ -9,6 +9,7 @@
<th>IP</th>
<th>Last visit</th>
<th>Page</th>
<th>Browser</th>
</tr>
</thead>
<tbody>
@ -17,6 +18,7 @@
<td>{{ visitor.ip }}</td>
<td>{{ visitor.lastvisit|date("H:i:s") }}</td>
<td><a href="{{ visitor.page }}">{{ visitor.page|slice(0, 50) }}</a></td>
<td>{{ visitor.browser|raw }}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -265,6 +265,8 @@
</div>
<br>
{{ hook(constant('HOOK_GUILDS_AFTER_INVITED_CHARACTERS'), { 'guild': guild, 'isLeader': isLeader }) }}
<div class="TableContainer">
<table class="Table3" cellpadding="0" cellspacing="0">
<tbody>

View File

@ -12,7 +12,7 @@
<span class="CaptionEdgeRightBottom" style="background-image:url({{ template_path }}/images/content/box-frame-edge.gif);"></span>
</div>
</div>
<table class="Table1" cellpadding="0" cellspacing="0" style="background-color: {{ config.lightborder }}">
<table class="Table5" cellpadding="0" cellspacing="0" style="background-color: {{ config.lightborder }}">
<tr>
<td>
<div class="InnerTableContainer">

View File

@ -9,6 +9,7 @@
theme: "silver",
plugins: 'preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount help code emoticons',
toolbar1: 'formatselect | bold italic strikethrough forecolor backcolor | emoticons link | alignleft aligncenter alignright alignjustify | numlist bullist outdent indent | removeformat code',
resize: 'both',
image_advtab: true,
images_upload_url: '{{ constant('BASE_URL') }}admin/tools/upload_image.php',
images_upload_credentials: true,

View File

@ -1,4 +1,13 @@
<?php
/**
* Twig Loader
*
* @package MyAAC
* @author Slawkens <slawkens@gmail.com>
* @copyright 2021 MyAAC
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
use Twig\Environment as Twig_Environment;
use Twig\Extension\DebugExtension as Twig_DebugExtension;
@ -61,17 +70,17 @@ $function = new TwigFunction('generateLink', function ($s, $n, $b = false) {
});
$twig->addFunction($function);
$function = new TwigFunction('getPlayerLink', function ($s, $p) {
$function = new TwigFunction('getPlayerLink', function ($s, $p = true) {
return getPlayerLink($s, $p);
});
$twig->addFunction($function);
$function = new TwigFunction('getMonsterLink', function ($s, $p) {
$function = new TwigFunction('getMonsterLink', function ($s, $p = true) {
return getMonsterLink($s, $p);
});
$twig->addFunction($function);
$function = new TwigFunction('getGuildLink', function ($s, $p) {
$function = new TwigFunction('getGuildLink', function ($s, $p = true) {
return getGuildLink($s, $p);
});
$twig->addFunction($function);
@ -81,14 +90,14 @@ $function = new TwigFunction('truncate', function ($s, $n) {
});
$twig->addFunction($function);
$function = new TwigFunction('hook', function ($hook) {
$function = new TwigFunction('hook', function ($hook, array $params = []) {
global $hooks;
if(is_string($hook)) {
$hook = constant($hook);
}
$hooks->trigger($hook);
$hooks->trigger($hook, $params);
});
$twig->addFunction($function);

View File

@ -146,10 +146,12 @@
<div class="TableContentAndRightShadow" style="background-image:url({{ template_path }}/images/content/table-shadow-rm.gif);">
<div class="TableContentContainer">
<table class="TableContent" width="100%">
{% if not config.account_login_by_email or config.account_login_by_email_fallback %}
<tr style="background-color: {{ config.lightborder }};" >
<td class="LabelV" >Account {% if constant('USE_ACCOUNT_NAME') %}Name{% else %}Number{% endif %}:</td>
<td style="width:90%;" >{{ account }}</td>
</tr>
{% endif %}
<tr style="background-color: {{ config.darkborder }};" >
<td class="LabelV" >Email Address:</td>
<td style="width:90%;" >{{ account_email ~ email_change}}</td>

View File

@ -1489,6 +1489,25 @@ img {
width: 135px;
z-index: 20;
}
.BigButtonText {
position: absolute;
top: 0;
left: 0;
width: 135px;
height: 25px;
margin: 0;
padding: 0;
cursor: pointer;
background: 0 0;
border: none;
text-align: center;
color: #ffd18c;
font-family: Verdana,Arial,Times New Roman,sans-serif;
font-size: 12px;
font-weight: 400;
z-index: 20;
text-shadow: -1px -1px 0 #000,0 -1px 0 #000,1px -1px 0 #000,1px 0 0 #000,1px 1px 0 #000,0 1px 0 #000,-1px 1px 0 #000,-1px 0 0 #000
}
.TopButtonContainer {
position: relative;
right: 4px;

View File

@ -1,8 +1,8 @@
{% spaceless %}
<div class="BigButton" style="background-image:url({{ template_path }}/images/global/buttons/sbutton.gif)">
<div class="BigButton" style="background-image:url({{ template_path }}/images/global/buttons/button_blue.gif)">
<div onMouseOver="MouseOverBigButton(this);" onMouseOut="MouseOutBigButton(this);">
<div class="BigButtonOver" style="background-image:url({{ template_path }}/images/global/buttons/sbutton_over.gif);" ></div>
<input class="ButtonText" type="image" name="{{ button_name }}" alt="{{ button_name }}" src="{{ template_path }}/images/global/buttons/{{ button_image }}.gif" />
<div class="BigButtonOver" style="background-image:url({{ template_path }}/images/global/buttons/{% if button_color is defined and button_color == 'green' %}button_green{% else %}button_blue_over{% endif %}.gif);" ></div>
<input class="BigButtonText" type="submit" value="{{ button_name }}">
</div>
</div>
{% endspaceless %}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

View File

@ -27,7 +27,7 @@ if(isset($config['boxes']))
var loginStatus="<?php echo ($logged ? 'true' : 'false'); ?>";
<?php
if(PAGE !== 'news') {
if(strpos(URI, 'subtopic=') !== false) {
if(isset($_REQUEST['subtopic'])) {
$tmp = $_REQUEST['subtopic'];
if($tmp === 'accountmanagement') {
$tmp = 'accountmanage';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -27,15 +27,16 @@ if(isset($_GET['account']))
error_(Validator::getLastError());
$_account = new OTS_Account();
if(USE_ACCOUNT_NAME)
if(USE_ACCOUNT_NAME || USE_ACCOUNT_NUMBER)
$_account->find($account);
else
$_account->load($account);
$accountNameOrNumber = (USE_ACCOUNT_NAME ? ' name' : 'number');
if($_account->isLoaded())
error_('Account with this name already exist.');
error_("Account with this $accountNameOrNumber already exist.");
success_('Good account' . (USE_ACCOUNT_NAME ? ' name' : '') . ' ( ' . $account . ' ).');
success_("Good account $accountNameOrNumber ($account).");
}
else if(isset($_GET['email']))
{