Compare commits

..

73 Commits

Author SHA1 Message Date
slawkens
17d6a32708 Ignore phpstan error, which is not correct 2026-04-24 21:54:47 +02:00
slawkens
ee1a157abc Merge branch 'develop' into blacktek-toml 2026-04-24 21:49:24 +02:00
Gabriel Pedro
04d630d765 feat: debugbar backtrace (#360)
* feat: debugbar backtrace

* chore(copilot): cleanup stmt reference

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: ignoring debug namespace

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-24 21:47:07 +02:00
slawkens
149de620ff Merge branch 'develop' into blacktek-toml 2026-04-24 21:45:02 +02:00
slawkens
7851e337eb Merge branch 'main' into develop 2026-04-24 21:44:38 +02:00
slawkens
1da5139128 Merge branch 'develop' into blacktek-toml 2026-04-24 21:44:03 +02:00
slawkens
ba1ee4bdb7 Nothing important [skip ci] 2026-04-24 21:22:27 +02:00
slawkens
e61c40d6b3 Merge branch 'main' into develop 2026-04-24 20:37:11 +02:00
slawkens
54bdea85a3 Fix phpstan 2026-04-24 20:36:56 +02:00
slawkens
48284e426f Merge branch 'main' into develop 2026-04-24 16:40:08 +02:00
slawkens
ac9a328206 Highscores: Prevent mass queries amount caused by getPlayerLink 2026-04-24 16:39:55 +02:00
slawkens
0635108d4c Merge branch 'main' into develop 2026-04-24 16:33:07 +02:00
slawkens
47ec36a176 Merge branch 'main' into develop 2026-04-24 16:33:01 +02:00
slawkens
e4a947cabb tibiacom: Reduce queries count from highscores box 2026-04-24 16:32:53 +02:00
slawkens
609cf152af Plugins: Fix uninstall when hook is without HOOK_ prefix 2026-04-24 16:24:06 +02:00
slawkens
6b89dd133d Fix phpstan 2026-04-19 17:29:25 +02:00
slawkens
6dc16397a1 [WIP] Lua + TOML stages loading
Lua extension required for .lua (canary)
2026-04-19 17:22:32 +02:00
slawkens
5dd97ad8fd Add more configs for ots-info site [skip ci] 2026-04-19 16:29:20 +02:00
slawkens
a6a3c76b8d gameplay & groups are also not needed [skip ci] 2026-04-19 16:17:18 +02:00
slawkens
1eba4cc509 [WIP] Loading of config .toml's
Deprecate load_config_lua, use MyAAC\Server\Lua\Loader::load instead
2026-04-19 16:05:22 +02:00
slawkens
6e603ad558 Merge branch 'develop' into blacktek-toml 2026-04-17 17:31:13 +02:00
slawkens
c16f95f8d2 Merge branch 'main' into develop 2026-04-17 17:30:53 +02:00
slawkens
ec7079dd57 Bye JetBrains, it was not my decision :( 2026-04-14 19:58:12 +02:00
slawkens
9e0e2601d2 Add indexes to myaac_account_actions table 2026-04-12 14:13:13 +02:00
slawkens
e7198aeb23 Update CHANGELOG-2.x.md 2026-04-12 13:42:25 +02:00
slawkens
1fa3630f86 Update CHANGELOG-2.x.md 2026-04-12 13:33:16 +02:00
slawkens
e274b83504 Fix: Reset settings by plugin name, not the settings key name 2026-04-12 13:20:48 +02:00
slawkens
a467a540b1 Add constant DEFAULT_PRIORITY 2026-04-12 13:14:10 +02:00
slawkens
08507e2940 Plugins: type hints 2026-04-12 13:10:44 +02:00
slawkens
f1aa128408 Feat: plugins autoload init-priority option 2026-04-12 13:10:15 +02:00
slawkens
7104c2258f Settings: Add Reset button 2026-04-12 10:43:09 +02:00
slawkens
f51211d47a Merge branch 'main' into develop 2026-04-12 09:56:10 +02:00
slawkens
fa93187f80 Add $fillable to Account model 2026-04-12 09:56:01 +02:00
slawkens
2c62a97160 Make myaac_config table columns bigger 2026-04-12 08:53:02 +02:00
slawkens
7bc8a66cc1 BugTracker has been moved to plugins, remove the model 2026-04-11 18:13:25 +02:00
slawkens
f6c2e6e460 Merge branch 'main' into develop 2026-04-11 17:58:02 +02:00
slawkens
4145d9eb3c Fix: Clear hooks on plugin uninstall
Fixes error with gesior-shop-system clear-cache.php being called, despite it's removed
2026-04-11 17:49:22 +02:00
slawkens
a27b8a4fa5 Merge branch 'main' of https://github.com/slawkens/myaac 2026-04-11 17:42:45 +02:00
dependabot[bot]
4570ba3801 Bump lodash from 4.17.23 to 4.18.1 (#358)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-11 17:23:17 +02:00
slawkens
578c0548ee Start v1.8.10-dev 2026-04-11 15:23:46 +02:00
slawkens
aa63e1c986 Fix client boosted + online count (?) 2026-04-10 23:08:33 +02:00
slawkens
0413de85b5 Release v1.8.9 2026-04-06 12:23:42 +02:00
Slawomir Boczek
dd97a749b4 Better name validation, like in the original game website (#356)
* Better name validation, like in the original game website

* Don't automatically ucfirst and strtolower the cases of the word
    * This allows for names like: Lord of Ring, Man of the Earth etc.
* Don't allow special characters like: -, [], '
* Don't allow one letter words
* Require at least one vowel per word
* Add notice about admin logged in

* Add trim, for future

Currently its stripped anyway in the init.php, but AI don't know it :P

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Implement AI recommended changes

* Update tools/validate.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Trim $name

* Update Validator.php

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-06 10:41:29 +02:00
slawkens
050181357a Merge branch 'main' into develop 2026-03-15 13:04:37 +01:00
slawkens
4ae2fdd0df Add page load time to admin panel 2026-03-15 13:02:22 +01:00
slawkens
2c18f9c767 Backward support for MyAAC\Items class 2026-03-08 21:08:29 +01:00
slawkens
39614a22c4 Update Items.php 2026-03-07 21:16:17 +01:00
slawkens
02ce6b364b Update Items.php 2026-03-07 21:09:57 +01:00
slawkens
e10ebed2f4 Fix fromid toid parsing 2026-03-07 21:00:14 +01:00
slawkens
a6e658b1a2 60x faster items.xml loading
Using simplexml, which is enabled by default on php
2026-03-07 20:54:47 +01:00
slawkens
2a80bc1b46 Nothing important, var name 2026-03-02 21:05:19 +01:00
slawkens
fbb5034458 [WIP] Refactoring - Uniform error message + TOML exception catch 2026-03-01 21:45:48 +01:00
slawkens
4ca79a1e85 Use static methods instead, looks better 2026-02-27 15:25:06 +01:00
slawkens
f975bd2f2a [WIP] Mounts TOML + XML parsers 2026-02-26 19:48:16 +01:00
slawkens
8a5823a69c [WIP] Outfits TOML + XML
Deprecate old Outfits & Mounts functions
2026-02-26 18:39:08 +01:00
slawkens
2bf5f5a1db Update GiveAdminCommand.php 2026-02-26 16:46:13 +01:00
slawkens
f738448f2e [WIP] Refactoring 2026-02-26 16:45:58 +01:00
slawkens
ccd70d2ee3 Merge branch 'main' into develop 2026-02-24 21:21:36 +01:00
slawkens
5fcde4708a Install: don't suggest deleting of install folder
It is not needed
Instead just remove ip.txt, this will lock the installation
2026-02-24 21:04:20 +01:00
slawkens
f15b0122c6 Nothing important, code style & grammar 2026-02-24 20:35:20 +01:00
slawkens
1da36c7f68 Merge branch 'main' into develop 2026-02-24 20:14:28 +01:00
slawkens
4eb7f48fd7 Update discord link 2026-02-24 20:13:42 +01:00
slawkens
058e80cfc6 Update Monsters.php 2026-02-15 02:02:07 +01:00
slawkens
980d5cb5d4 Update Items.php 2026-02-15 02:02:05 +01:00
slawkens
9146a0e53c Parse last item 2026-02-15 02:01:57 +01:00
slawkens
ad1d069b1f Copilot fixes 2026-02-15 01:57:26 +01:00
dependabot[bot]
c82e537dc7 Bump qs from 6.14.1 to 6.14.2 (#353)
Bumps [qs](https://github.com/ljharb/qs) from 6.14.1 to 6.14.2.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.1...v6.14.2)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-15 01:23:24 +01:00
slawkens
4bc3923996 glob is not needed here 2026-02-15 01:20:11 +01:00
slawkens
8809467fc6 Delete debug output 2026-02-15 01:19:58 +01:00
slawkens
97ee430a91 Forgot about composer 2026-02-15 01:08:27 +01:00
slawkens
fc9d6b2b91 [WIP] Initial Blacktek support - groups, vocations, items
items.toml loading through custom parser - no .toml reader that I tested can read it
2026-02-15 00:54:18 +01:00
slawkens
4c3f877091 Update CHANGELOG-2.x.md 2026-02-12 18:21:19 +01:00
slawkens
8b10f85bc1 Start v1.8.9-dev 2026-02-04 18:37:03 +01:00
116 changed files with 2565 additions and 2501 deletions

View File

@@ -1,5 +1,18 @@
# Changelog # Changelog
## [1.8.9 - 06.04.2026]
### Added
* Settings: Possibility to add custom HTML for the head and body tags like Google Analytics code etc. (https://github.com/slawkens/myaac/commit/108e83806df5686a06826931ed5e243c19cbe130)
* Add command: give-admin (https://github.com/slawkens/myaac/commit/9fa9ec746c4b344387a21f21886c2251319806fc)
* Usage: php aac give:admin slawkens@gmail.com
Parameter: account email, name or id
* It's admin for the website, not the GM for the game! For that, go into the admin panel and change the group manually
* Add page load time to an Admin Panel footer (https://github.com/slawkens/myaac/commit/4ae2fdd0dfcd56697612395c14aecc2dfd33b1c3)
### Changed
* Better character name validation, like in the original game website (#356)
* Install: don't suggest deleting of install folder - it's not required (https://github.com/slawkens/myaac/commit/5fcde4708a39255cf68edc8c43f2ac6597e2601d)
## [1.8.8 - 31.01.2026] ## [1.8.8 - 31.01.2026]
### Added ### Added
* Change Comment: Add missing hooks - patched from 0.8 (https://github.com/slawkens/myaac/commit/a60a23b84f61d41d1503073b52e01e3120f6d92a) * Change Comment: Add missing hooks - patched from 0.8 (https://github.com/slawkens/myaac/commit/a60a23b84f61d41d1503073b52e01e3120f6d92a)

View File

@@ -1,9 +1,12 @@
## [2.0-dev - x.x.2025] ## [2.0-dev - x.x.2025]
### Added ### Added
* Add an "access" option to Menus (#340) * Menus: Add an "access" option to Menus (#340)
* Possibility to hide menus for unauthorized users * Possibility to hide menus for unauthorized users
* Add the possibility to fetch skills in the getTopPlayers function (#347) * Settings: Add Reset button (https://github.com/slawkens/myaac/commit/7104c2258fd724a55239821b46a616dab845b22a, https://github.com/slawkens/myaac/commit/e274b8350451a20c24e652ea05ed1964ebb86b54)
* New Setting: block create account spam by ip (https://github.com/slawkens/myaac/commit/54265f42e987522803288477952d6e5c4daeeb24)
* Functions: Add the possibility to fetch skills, balance and frags in the getTopPlayers function (#347)
* Plugins: autoload init-priority option (https://github.com/slawkens/myaac/commit/f1aa12840875960849fa0c99a2bbe0ad2949bbec)
### Changed ### Changed
* Better handling of vocations: (#345) * Better handling of vocations: (#345)
@@ -11,6 +14,7 @@
* Support for Monk vocation * Support for Monk vocation
* Better gallery, loads images from images/gallery folder * Better gallery, loads images from images/gallery folder
* Reworked account action logs to use a single IP column as varchar(45) for both ipv4 and ipv6 (#289) * Reworked account action logs to use a single IP column as varchar(45) for both ipv4 and ipv6 (#289)
* Make myaac_config table columns bigger (https://github.com/slawkens/myaac/commit/2c62a97160a3ffe9976ee5bd1d770a0abc576742)
* Admin Panel: save menu collapse state (https://github.com/slawkens/myaac/commit/55da00520df7463a1d1ca41931df1598e9f2ffeb) * Admin Panel: save menu collapse state (https://github.com/slawkens/myaac/commit/55da00520df7463a1d1ca41931df1598e9f2ffeb)
### Internal ### Internal

View File

@@ -7,7 +7,7 @@ Official website: https://my-aac.org
[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/slawkens/myaac/cypress.yml)](https://github.com/slawkens/myaac/actions) [![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/slawkens/myaac/cypress.yml)](https://github.com/slawkens/myaac/actions)
[![License: GPL-3.0](https://img.shields.io/github/license/slawkens/myaac)](https://opensource.org/licenses/gpl-license) [![License: GPL-3.0](https://img.shields.io/github/license/slawkens/myaac)](https://opensource.org/licenses/gpl-license)
[![Downloads Count](https://img.shields.io/github/downloads/slawkens/myaac/total)](https://github.com/slawkens/myaac/releases) [![Downloads Count](https://img.shields.io/github/downloads/slawkens/myaac/total)](https://github.com/slawkens/myaac/releases)
[![OpenTibia Discord](https://img.shields.io/discord/288399552581468162)](https://discord.gg/2J39Wus) [![MyAAC Discord](https://img.shields.io/discord/1468205461319848049)](https://discord.gg/aVagGPJt3g)
[![Closed Issues](https://img.shields.io/github/issues-closed-raw/slawkens/myaac)](https://github.com/slawkens/myaac/issues?q=is%3Aissue+is%3Aclosed) [![Closed Issues](https://img.shields.io/github/issues-closed-raw/slawkens/myaac)](https://github.com/slawkens/myaac/issues?q=is%3Aissue+is%3Aclosed)
| Version | Status | Branch | Requirements | | Version | Status | Branch | Requirements |
@@ -86,12 +86,6 @@ Look: [Contributing](https://docs.my-aac.org/misc/contributing) in our wiki.
If you have a great idea or want to contribute to the project - visit our website at https://www.my-aac.org If you have a great idea or want to contribute to the project - visit our website at https://www.my-aac.org
## Project supported by JetBrains
Many thanks to Jetbrains for kindly providing a license for me to work on this and other open-source projects.
[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/slawkens)
### License ### License
This program and all associated files are released under the GNU Public License. This program and all associated files are released under the GNU Public License.

View File

@@ -10,6 +10,7 @@
use MyAAC\Forum; use MyAAC\Forum;
use MyAAC\Models\Player; use MyAAC\Models\Player;
use MyAAC\Server\Outfits;
use MyAAC\Server\XML\Vocations; use MyAAC\Server\XML\Vocations;
defined('MYAAC') or die('Direct access not allowed!'); defined('MYAAC') or die('Direct access not allowed!');
@@ -659,14 +660,14 @@ else if (isset($_REQUEST['search'])) {
<div class="col-12 col-sm-12 col-lg-6"> <div class="col-12 col-sm-12 col-lg-6">
<label for="look_type" class="control-label">Type:</label> <label for="look_type" class="control-label">Type:</label>
<?php <?php
$outfitlist = null; $outfits = Outfits::get();
$outfitlist = Outfits_loadfromXML(); if ($outfits) { ?>
if ($outfitlist) { ?>
<select name="look_type" id="look_type" class="form-control custom-select"> <select name="look_type" id="look_type" class="form-control custom-select">
<?php <?php
foreach ($outfitlist as $_id => $outfit) { foreach ($outfits as $outfit) {
if ($outfit['enabled'] == 'yes') ; if ($outfit['enabled']) {
echo '<option value=' . $outfit['id'] . ($outfit['id'] == $player->getLookType() ? ' selected' : '') . '>' . $outfit['name'] . ' - ' . ($outfit['type'] == 1 ? 'Male' : 'Female') . '</option>'; echo '<option value=' . $outfit['id'] . ($outfit['id'] == $player->getLookType() ? ' selected' : '') . '>' . $outfit['name'] . ' - ' . ($outfit['sex'] == SEX_MALE ? 'Male' : 'Female') . '</option>';
}
} }
?> ?>
</select> </select>

View File

@@ -46,6 +46,15 @@ if (!is_array($settingsFile)) {
return; return;
} }
if (isset($_POST['reset']) && $_POST['reset'] == '1') {
$settings = Settings::getInstance();
$settings->deleteFromDatabase($settingsFile['key']);
$settings->clearCache();
success('Settings for this plugin has been reset.');
}
$settingsKeyName = ($plugin == 'core' ? $plugin : $settingsFile['key']); $settingsKeyName = ($plugin == 'core' ? $plugin : $settingsFile['key']);
$title = ($plugin == 'core' ? 'Settings' : 'Plugin Settings - ' . $settingsFile['name']); $title = ($plugin == 'core' ? 'Settings' : 'Plugin Settings - ' . $settingsFile['name']);
@@ -57,4 +66,5 @@ $twig->display('admin.settings.html.twig', [
'settings' => $settingsFile['settings'], 'settings' => $settingsFile['settings'],
'script' => $settingsParsed['script'], 'script' => $settingsParsed['script'],
'settingsKeyName' => $settingsKeyName, 'settingsKeyName' => $settingsKeyName,
'pluginName' => $plugin,
]); ]);

View File

@@ -172,7 +172,8 @@
<div class="float-sm-right d-none d-sm-inline"> <div class="float-sm-right d-none d-sm-inline">
<span class="p-2 right badge badge-<?php echo((isset($status['online']) and $status['online']) ? 'success' : 'danger'); ?>"><?php echo $config['lua']['serverName'] ?></span> <span class="p-2 right badge badge-<?php echo((isset($status['online']) and $status['online']) ? 'success' : 'danger'); ?>"><?php echo $config['lua']['serverName'] ?></span>
</div> </div>
<?php echo base64_decode('UG93ZXJlZCBieSA8YSBocmVmPSJodHRwOi8vbXktYWFjLm9yZyIgdGFyZ2V0PSJfYmxhbmsiPk15QUFDLjwvYT4='); ?> <?= base64_decode('UG93ZXJlZCBieSA8YSBocmVmPSJodHRwOi8vbXktYWFjLm9yZyIgdGFyZ2V0PSJfYmxhbmsiPk15QUFDLjwvYT4='); ?>
<?= 'Load time: ' . round(microtime(true) - START_TIME, 4) . ' seconds.'; ?>
</footer> </footer>
<div id="sidebar-overlay"></div> <div id="sidebar-overlay"></div>
</div> </div>

View File

@@ -27,7 +27,7 @@ if (version_compare(phpversion(), '8.1', '<')) die('PHP version 8.1 or higher is
const MYAAC = true; const MYAAC = true;
const MYAAC_VERSION = '2.0-dev'; const MYAAC_VERSION = '2.0-dev';
const DATABASE_VERSION = 51; const DATABASE_VERSION = 52;
const TABLE_PREFIX = 'myaac_'; const TABLE_PREFIX = 'myaac_';
define('START_TIME', microtime(true)); define('START_TIME', microtime(true));
define('MYAAC_OS', stripos(PHP_OS, 'WIN') === 0 ? 'WINDOWS' : (strtoupper(PHP_OS) === 'DARWIN' ? 'MAC' : 'LINUX')); define('MYAAC_OS', stripos(PHP_OS, 'WIN') === 0 ? 'WINDOWS' : (strtoupper(PHP_OS) === 'DARWIN' ? 'MAC' : 'LINUX'));
@@ -104,6 +104,8 @@ const OTSERV_FIRST = OTSERV;
const OTSERV_LAST = OTSERV_06; const OTSERV_LAST = OTSERV_06;
const TFS_02 = 3; const TFS_02 = 3;
const TFS_03 = 4; const TFS_03 = 4;
const BLACKTEK_2 = 5;
const BLACKTEK = 6;
const TFS_FIRST = TFS_02; const TFS_FIRST = TFS_02;
const TFS_LAST = TFS_03; const TFS_LAST = TFS_03;

View File

@@ -5,6 +5,7 @@
"ext-pdo_mysql": "*", "ext-pdo_mysql": "*",
"ext-json": "*", "ext-json": "*",
"ext-xml": "*", "ext-xml": "*",
"ext-simplexml": "*",
"ext-dom": "*", "ext-dom": "*",
"phpmailer/phpmailer": "^6.1", "phpmailer/phpmailer": "^6.1",
"composer/semver": "^3.2", "composer/semver": "^3.2",
@@ -20,7 +21,7 @@
"filp/whoops": "^2.15", "filp/whoops": "^2.15",
"maximebf/debugbar": "1.*", "maximebf/debugbar": "1.*",
"guzzlehttp/guzzle": "7.9.3", "guzzlehttp/guzzle": "7.9.3",
"spomky-labs/otphp": "^11.3" "devium/toml": "^1.0"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^1.10" "phpstan/phpstan": "^1.10"

846
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,7 @@
<?php <?php
use MyAAC\Server\Lua\Loader;
defined('MYAAC') or die('Direct access not allowed!'); defined('MYAAC') or die('Direct access not allowed!');
if(!isset($_SESSION['var_server_path'])) { if(!isset($_SESSION['var_server_path'])) {
@@ -11,23 +14,35 @@ $config['server_path'] = $_SESSION['var_server_path'];
if($config['server_path'][strlen($config['server_path']) - 1] != '/') if($config['server_path'][strlen($config['server_path']) - 1] != '/')
$config['server_path'] .= '/'; $config['server_path'] .= '/';
if((!isset($error) || !$error) && !file_exists($config['server_path'] . 'config.lua')) { $configLuaExists = file_exists($config['server_path'] . 'config.lua');
$configTomlExists = file_exists($config['server_path'] . 'config/server.toml');
if((!isset($error) || !$error)
&& !$configLuaExists
&& !$configTomlExists) {
error($locale['step_database_error_config']); error($locale['step_database_error_config']);
$error = true; $error = true;
} }
if(!isset($error) || !$error) { if (!isset($error) || !$error) {
$config['lua'] = load_config_lua($config['server_path'] . 'config.lua'); if($configLuaExists) {
if(isset($config['lua']['sqlType'])) // tfs 0.3 $config['lua'] = Loader::load($config['server_path'] . 'config.lua');
$config['database_type'] = $config['lua']['sqlType']; if (isset($config['lua']['sqlType'])) // tfs 0.3
else if(isset($config['lua']['mysqlHost'])) // tfs 0.2/1.0 $config['database_type'] = $config['lua']['sqlType'];
$config['database_type'] = 'mysql'; elseif (isset($config['lua']['mysqlHost'])) // tfs 0.2/1.0
else if(isset($config['lua']['database_type'])) // otserv $config['database_type'] = 'mysql';
$config['database_type'] = $config['lua']['database_type']; elseif (isset($config['lua']['database_type'])) // otserv
else if(isset($config['lua']['sql_type'])) // otserv $config['database_type'] = $config['lua']['database_type'];
$config['database_type'] = $config['lua']['sql_type']; elseif (isset($config['lua']['sql_type'])) // otserv
else { $config['database_type'] = $config['lua']['sql_type'];
$config['database_type'] = ''; else {
$config['database_type'] = '';
}
}
elseif ($configTomlExists) {
$tomlConfig = new MyAAC\Server\TOML\Config();
$tomlConfig->load();
$config['server'] = $tomlConfig->get();
$config['database_type'] = (isset($config['server']['database']['mysql']) ? 'mysql' : '');
} }
$config['database_type'] = strtolower($config['database_type']); $config['database_type'] = strtolower($config['database_type']);

View File

@@ -5,16 +5,9 @@ CREATE TABLE IF NOT EXISTS `myaac_account_actions`
`ip` varchar(45) NOT NULL DEFAULT '', `ip` varchar(45) NOT NULL DEFAULT '',
`date` int NOT NULL DEFAULT 0, `date` int NOT NULL DEFAULT 0,
`action` varchar(255) NOT NULL DEFAULT '', `action` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`) PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4; INDEX `myaac_account_actions_account_id` (`account_id`),
INDEX `myaac_account_actions_ip` (`ip`)
CREATE TABLE IF NOT EXISTS `myaac_account_email_codes`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`account_id` int NOT NULL,
`code` varchar(6) NOT NULL,
`created_at` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;
CREATE TABLE IF NOT EXISTS `myaac_account_emails_verify` CREATE TABLE IF NOT EXISTS `myaac_account_emails_verify`
@@ -52,8 +45,8 @@ CREATE TABLE IF NOT EXISTS `myaac_changelog`
CREATE TABLE IF NOT EXISTS `myaac_config` CREATE TABLE IF NOT EXISTS `myaac_config`
( (
`id` int NOT NULL AUTO_INCREMENT, `id` int NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL, `name` varchar(255) NOT NULL,
`value` varchar(1000) NOT NULL, `value` varchar(10000) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE (`name`) UNIQUE (`name`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;

View File

@@ -1,5 +1,6 @@
<?php <?php
use MyAAC\Server\Config;
use Twig\Environment as Twig_Environment; use Twig\Environment as Twig_Environment;
use Twig\Loader\FilesystemLoader as Twig_FilesystemLoader; use Twig\Loader\FilesystemLoader as Twig_FilesystemLoader;
@@ -30,7 +31,7 @@ if(file_exists(CACHE . 'install.txt')) {
$install_status = unserialize(file_get_contents(CACHE . 'install.txt')); $install_status = unserialize(file_get_contents(CACHE . 'install.txt'));
if(!isset($_REQUEST['step'])) { if(!isset($_REQUEST['step'])) {
$step = isset($install_status['step']) ? $install_status['step'] : ''; $step = $install_status['step'] ?? '';
} }
} }
@@ -53,7 +54,7 @@ if($step == 'finish' && (!isset($config['installed']) || !$config['installed']))
// step verify // step verify
$steps = array(1 => 'welcome', 2 => 'license', 3 => 'requirements', 4 => 'config', 5 => 'database', 6 => 'admin', 7 => 'finish'); $steps = array(1 => 'welcome', 2 => 'license', 3 => 'requirements', 4 => 'config', 5 => 'database', 6 => 'admin', 7 => 'finish');
if(!in_array($step, $steps)) // check if step is valid if(!in_array($step, $steps)) // check if a step is valid
throw new RuntimeException('ERROR: Unknown step.'); throw new RuntimeException('ERROR: Unknown step.');
$install_status['step'] = $step; $install_status['step'] = $step;
@@ -61,7 +62,7 @@ $errors = array();
if($step == 'database') { if($step == 'database') {
foreach($_SESSION as $key => $value) { foreach($_SESSION as $key => $value) {
if(strpos($key, 'var_') === false) { if(!str_contains($key, 'var_')) {
continue; continue;
} }
@@ -83,7 +84,7 @@ if($step == 'database') {
$config['server_path'] .= '/'; $config['server_path'] .= '/';
} }
if(!file_exists($config['server_path'] . 'config.lua')) { if(!Config::exists()) {
$errors[] = $locale['step_database_error_config']; $errors[] = $locale['step_database_error_config'];
break; break;
} }
@@ -182,7 +183,7 @@ $error = false;
clearstatcache(); clearstatcache();
if(is_writable(CACHE) && (MYAAC_OS != 'WINDOWS' || win_is_writable(CACHE))) { if(is_writable(CACHE) && (MYAAC_OS != 'WINDOWS' || win_is_writable(CACHE))) {
if(!file_exists(BASE . 'install/ip.txt')) { if(!file_exists(BASE . 'install/ip.txt')) {
$content = warning('AAC installation is disabled. To enable it make file <b>ip.txt</b> in install/ directory and put there your IP.<br/> $content = warning('AAC installation is disabled. To enable it make a file <b>ip.txt</b> in install/ directory and put there your IP.<br/>
Your IP is:<br /><b>' . get_browser_real_ip() . '</b>', true); Your IP is:<br /><b>' . get_browser_real_ip() . '</b>', true);
} }
else { else {
@@ -198,7 +199,7 @@ if(is_writable(CACHE) && (MYAAC_OS != 'WINDOWS' || win_is_writable(CACHE))) {
if(!$allow) if(!$allow)
{ {
$content = warning('In file <b>install/ip.txt</b> must be your IP!<br/> $content = warning('In file <b>install/ip.txt</b> must be your IP!<br/>
In file is:<br /><b>' . nl2br($file_content) . '</b><br/> In the file is:<br /><b>' . nl2br($file_content) . '</b><br/>
Your IP is:<br /><b>' . get_browser_real_ip() . '</b>', true); Your IP is:<br /><b>' . get_browser_real_ip() . '</b>', true);
} }
else { else {

View File

@@ -98,16 +98,6 @@ if(!$db->hasColumn('accounts', 'web_flags')) {
success($locale['step_database_adding_field'] . ' accounts.web_flags...'); success($locale['step_database_adding_field'] . ' accounts.web_flags...');
} }
if(!$db->hasColumn('accounts', '2fa_type')) {
if(query("ALTER TABLE `accounts` ADD `2fa_type` tinyint NOT NULL DEFAULT 0 AFTER `web_flags`;"))
success($locale['step_database_adding_field'] . ' accounts.2fa_type...');
}
if(!$db->hasColumn('accounts', '2fa_secret')) {
if(query("ALTER TABLE `accounts` ADD `2fa_secret` varchar(16) NOT NULL DEFAULT '' AFTER `2fa_type`;"))
success($locale['step_database_adding_field'] . ' accounts.2fa_secret...');
}
if(!$db->hasColumn('accounts', 'email_verified')) { if(!$db->hasColumn('accounts', 'email_verified')) {
if(query("ALTER TABLE `accounts` ADD `email_verified` TINYINT(1) NOT NULL DEFAULT 0 AFTER `web_flags`;")) if(query("ALTER TABLE `accounts` ADD `email_verified` TINYINT(1) NOT NULL DEFAULT 0 AFTER `web_flags`;"))
success($locale['step_database_adding_field'] . ' accounts.email_verified...'); success($locale['step_database_adding_field'] . ' accounts.email_verified...');

View File

@@ -30,6 +30,8 @@ $up();
DataLoader::setLocale($locale); DataLoader::setLocale($locale);
DataLoader::load(); DataLoader::load();
clearCache();
// add menus entries // add menus entries
require_once SYSTEM . 'migrations/17.php'; require_once SYSTEM . 'migrations/17.php';
$up(); $up();
@@ -67,6 +69,10 @@ if(file_exists(CACHE . 'install.txt')) {
unlink(CACHE . 'install.txt'); unlink(CACHE . 'install.txt');
} }
if(file_exists(BASE . 'install/ip.txt')) {
unlink(BASE . 'install/ip.txt');
}
$locale['step_finish_desc'] = str_replace('$ADMIN_PANEL$', generateLink(str_replace('tools/', '',ADMIN_URL), $locale['step_finish_admin_panel'], true), $locale['step_finish_desc']); $locale['step_finish_desc'] = str_replace('$ADMIN_PANEL$', generateLink(str_replace('tools/', '',ADMIN_URL), $locale['step_finish_admin_panel'], true), $locale['step_finish_desc']);
$locale['step_finish_desc'] = str_replace('$HOMEPAGE$', generateLink(str_replace('tools/', '', BASE_URL), $locale['step_finish_homepage'], true), $locale['step_finish_desc']); $locale['step_finish_desc'] = str_replace('$HOMEPAGE$', generateLink(str_replace('tools/', '', BASE_URL), $locale['step_finish_homepage'], true), $locale['step_finish_desc']);
$locale['step_finish_desc'] = str_replace('$LINK$', generateLink('https://my-aac.org', 'https://my-aac.org', true), $locale['step_finish_desc']); $locale['step_finish_desc'] = str_replace('$LINK$', generateLink('https://my-aac.org', 'https://my-aac.org', true), $locale['step_finish_desc']);

108
login.php
View File

@@ -5,7 +5,6 @@ use MyAAC\Models\PlayerOnline;
use MyAAC\Models\Account; use MyAAC\Models\Account;
use MyAAC\Models\Player; use MyAAC\Models\Player;
use MyAAC\RateLimit; use MyAAC\RateLimit;
use MyAAC\TwoFactorAuth\TwoFactorAuth;
require_once 'common.php'; require_once 'common.php';
require_once SYSTEM . 'functions.php'; require_once SYSTEM . 'functions.php';
@@ -13,7 +12,7 @@ require_once SYSTEM . 'init.php';
require_once SYSTEM . 'status.php'; require_once SYSTEM . 'status.php';
# error function # error function
function sendError($message, $code = 3) { function sendError($message, $code = 3){
$ret = []; $ret = [];
$ret['errorCode'] = $code; $ret['errorCode'] = $code;
$ret['errorMessage'] = $message; $ret['errorMessage'] = $message;
@@ -94,9 +93,9 @@ switch ($action) {
$creatureBoost = $db->query("SELECT * FROM " . $db->tableName('boosted_creature'))->fetchAll(); $creatureBoost = $db->query("SELECT * FROM " . $db->tableName('boosted_creature'))->fetchAll();
$bossBoost = $db->query("SELECT * FROM " . $db->tableName('boosted_boss'))->fetchAll(); $bossBoost = $db->query("SELECT * FROM " . $db->tableName('boosted_boss'))->fetchAll();
die(json_encode([ die(json_encode([
'boostedcreature' => true, //'boostedcreature' => true,
'bossraceid' => intval($bossBoost[0]['raceid']),
'creatureraceid' => intval($creatureBoost[0]['raceid']), 'creatureraceid' => intval($creatureBoost[0]['raceid']),
'bossraceid' => intval($bossBoost[0]['raceid'])
])); ]));
} }
@@ -109,18 +108,17 @@ switch ($action) {
case 'login': case 'login':
$ip = configLua('ip'); $port = $config['lua']['gameProtocolPort'];
$port = configLua('gameProtocolPort');
// default world info // default world info
$world = [ $world = [
'id' => 0, 'id' => 0,
'name' => $config['lua']['serverName'], 'name' => $config['lua']['serverName'],
'externaladdress' => $ip, 'externaladdress' => $config['lua']['ip'],
'externalport' => $port, 'externalport' => $port,
'externaladdressprotected' => $ip, 'externaladdressprotected' => $config['lua']['ip'],
'externalportprotected' => $port, 'externalportprotected' => $port,
'externaladdressunprotected' => $ip, 'externaladdressunprotected' => $config['lua']['ip'],
'externalportunprotected' => $port, 'externalportunprotected' => $port,
'previewstate' => 0, 'previewstate' => 0,
'location' => 'BRA', // BRA, EUR, USA 'location' => 'BRA', // BRA, EUR, USA
@@ -135,12 +133,13 @@ switch ($action) {
$inputEmail = $request->email ?? false; $inputEmail = $request->email ?? false;
$inputAccountName = $request->accountname ?? false; $inputAccountName = $request->accountname ?? false;
$inputToken = $request->token ?? false;
$account = Account::query(); $account = Account::query();
if ($inputEmail) { // login by email if ($inputEmail != false) { // login by email
$account->where('email', $inputEmail); $account->where('email', $inputEmail);
} }
else if($inputAccountName) { // login by account name else if($inputAccountName != false) { // login by account name
$account->where('name', $inputAccountName); $account->where('name', $inputAccountName);
} }
@@ -152,14 +151,13 @@ switch ($action) {
$limiter->load(); $limiter->load();
$ban_msg = 'A wrong account, password or secret has been entered ' . setting('core.account_login_attempts_limit') . ' times in a row. You are unable to log into your account for the next ' . setting('core.account_login_ban_time') . ' minutes. Please wait.'; $ban_msg = 'A wrong account, password or secret has been entered ' . setting('core.account_login_attempts_limit') . ' times in a row. You are unable to log into your account for the next ' . setting('core.account_login_ban_time') . ' minutes. Please wait.';
if (!$account) { if (!$account) {
$limiter->increment($ip); $limiter->increment($ip);
if ($limiter->exceeded($ip)) { if ($limiter->exceeded($ip)) {
sendError($ban_msg); sendError($ban_msg);
} }
sendError(($inputEmail ? 'Email' : 'Account name') . ' or password is not correct.'); sendError(($inputEmail != false ? 'Email' : 'Account name') . ' or password is not correct.');
} }
$current_password = encrypt((USE_ACCOUNT_SALT ? $account->salt : '') . $request->password); $current_password = encrypt((USE_ACCOUNT_SALT ? $account->salt : '') . $request->password);
@@ -169,30 +167,32 @@ switch ($action) {
sendError($ban_msg); sendError($ban_msg);
} }
sendError(($inputEmail ? 'Email' : 'Account name') . ' or password is not correct.'); sendError(($inputEmail != false ? 'Email' : 'Account name') . ' or password is not correct.');
} }
$twoFactorAuth = TwoFactorAuth::getInstance($account->id); $accountHasSecret = false;
if (fieldExist('secret', 'accounts')) {
$accountSecret = $account->secret;
if ($accountSecret != null && $accountSecret != '') {
$accountHasSecret = true;
if ($inputToken === false) {
$limiter->increment($ip);
if ($limiter->exceeded($ip)) {
sendError($ban_msg);
}
sendError('Submit a valid two-factor authentication token.', 6);
} else {
require_once LIBS . 'rfc6238.php';
if (TokenAuth6238::verify($accountSecret, $inputToken) !== true) {
$limiter->increment($ip);
if ($limiter->exceeded($ip)) {
sendError($ban_msg);
}
$code = ''; sendError('Two-factor authentication failed, token is wrong.', 6);
if ($twoFactorAuth->isActive()) { }
if ($twoFactorAuth->getAuthType() === TwoFactorAuth::TYPE_EMAIL) { }
$code = $request->emailcode ?? false;
} }
else if ($twoFactorAuth->getAuthType() === TwoFactorAuth::TYPE_APP) {
$code = $request->token ?? false;
}
}
$error = '';
$errorCode = 6;
if (!$twoFactorAuth->processClientLogin($code, $error, $errorCode)) {
$limiter->increment($ip);
if ($limiter->exceeded($ip)) {
sendError($ban_msg);
}
sendError($error, $errorCode);
} }
$limiter->reset($ip); $limiter->reset($ip);
@@ -220,6 +220,46 @@ switch ($action) {
} }
} }
/*
* not needed anymore?
if (fieldExist('premdays', 'accounts') && fieldExist('lastday', 'accounts')) {
$save = false;
$timeNow = time();
$premDays = $account->premdays;
$lastDay = $account->lastday;
$lastLogin = $lastDay;
if ($premDays != 0 && $premDays != PHP_INT_MAX) {
if ($lastDay == 0) {
$lastDay = $timeNow;
$save = true;
} else {
$days = (int)(($timeNow - $lastDay) / 86400);
if ($days > 0) {
if ($days >= $premDays) {
$premDays = 0;
$lastDay = 0;
} else {
$premDays -= $days;
$reminder = ($timeNow - $lastDay) % 86400;
$lastDay = $timeNow - $reminder;
}
$save = true;
}
}
} else if ($lastDay != 0) {
$lastDay = 0;
$save = true;
}
if ($save) {
$account->premdays = $premDays;
$account->lastday = $lastDay;
$account->save();
}
}
*/
$worlds = [$world]; $worlds = [$world];
$playdata = compact('worlds', 'characters'); $playdata = compact('worlds', 'characters');
@@ -228,7 +268,7 @@ switch ($action) {
if (!fieldExist('istutorial', 'players')) { if (!fieldExist('istutorial', 'players')) {
$sessionKey .= "\n"; $sessionKey .= "\n";
} }
$sessionKey .= ($twoFactorAuth->isActive() && strlen($account->{'2fa_secret'}) > 5) ? $account->{'2fa_secret'} : ''; $sessionKey .= ($accountHasSecret && strlen($accountSecret) > 5) ? $inputToken : '';
// this is workaround to distinguish between TFS 1.x and otservbr // this is workaround to distinguish between TFS 1.x and otservbr
// TFS 1.x requires the number in session key // TFS 1.x requires the number in session key

12
package-lock.json generated
View File

@@ -1431,9 +1431,9 @@
} }
}, },
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.23", "version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -1743,9 +1743,9 @@
} }
}, },
"node_modules/qs": { "node_modules/qs": {
"version": "6.14.1", "version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"dev": true, "dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {

View File

@@ -4,6 +4,7 @@ require __DIR__ . '/system/libs/pot/OTS.php';
$ots = POT::getInstance(); $ots = POT::getInstance();
require __DIR__ . '/system/libs/pot/InvitesDriver.php'; require __DIR__ . '/system/libs/pot/InvitesDriver.php';
require __DIR__ . '/system/libs/rfc6238.php';
require __DIR__ . '/common.php'; require __DIR__ . '/common.php';
const ACTION = ''; const ACTION = '';

View File

@@ -27,12 +27,10 @@ parameters:
- '#Variable \$player might not be defined#' - '#Variable \$player might not be defined#'
- '#Variable \$guild might not be defined#' - '#Variable \$guild might not be defined#'
- '#Variable \$[a-zA-Z0-9\\_]+ might not be defined#' - '#Variable \$[a-zA-Z0-9\\_]+ might not be defined#'
- '#Class Lua constructor invoked with 0 parameters, 1 required#'
# Eloquent models # Eloquent models
- '#Call to an undefined method [a-zA-Z0-9\\_]+::[a-zA-Z0-9\\_]+\(\)#' - '#Call to an undefined method [a-zA-Z0-9\\_]+::[a-zA-Z0-9\\_]+\(\)#'
- '#Call to an undefined static method [a-zA-Z0-9\\_]+::[a-zA-Z0-9\\_]+\(\)#' - '#Call to an undefined static method [a-zA-Z0-9\\_]+::[a-zA-Z0-9\\_]+\(\)#'
# system/pages/highscores.php
- '#Access to an undefined property Illuminate\\Database\\Eloquent\\Model::\$online_status#'
- '#Access to an undefined property Illuminate\\Database\\Eloquent\\Model::\$vocation_name#'
- -
message: '#Variable \$tmp in empty\(\) always exists and is always falsy#' message: '#Variable \$tmp in empty\(\) always exists and is always falsy#'
path: templates\kathrine\javascript.php path: templates\kathrine\javascript.php

View File

@@ -9,6 +9,8 @@
*/ */
defined('MYAAC') or die('Direct access not allowed!'); defined('MYAAC') or die('Direct access not allowed!');
use MyAAC\Server\Lua\Loader;
class Validator extends \MyAAC\Validator {} class Validator extends \MyAAC\Validator {}
function check_name($name, &$errors = '') { function check_name($name, &$errors = '') {
@@ -74,3 +76,77 @@ function fieldExist($field, $table)
global $db; global $db;
return $db->hasColumn($table, $field); return $db->hasColumn($table, $field);
} }
function Outfits_loadfromXML(): ?array
{
global $config;
$file_path = $config['data_path'] . 'XML/outfits.xml';
if (!file_exists($file_path)) { return null; }
$xml = new DOMDocument;
$xml->load($file_path);
$outfits = null;
foreach ($xml->getElementsByTagName('outfit') as $outfit) {
$outfits[] = Outfit_parseNode($outfit);
}
return $outfits;
}
function Outfit_parseNode($node): array
{
$looktype = (int)$node->getAttribute('looktype');
$type = (int)$node->getAttribute('type');
$lookname = $node->getAttribute('name');
$premium = $node->getAttribute('premium');
$unlocked = $node->getAttribute('unlocked');
$enabled = $node->getAttribute('enabled');
return array('id' => $looktype, 'type' => $type, 'name' => $lookname, 'premium' => $premium, 'unlocked' => $unlocked, 'enabled' => $enabled);
}
function Mounts_loadfromXML(): ?array
{
global $config;
$file_path = $config['data_path'] . 'XML/mounts.xml';
if (!file_exists($file_path)) { return null; }
$xml = new DOMDocument;
$xml->load($file_path);
$mounts = null;
foreach ($xml->getElementsByTagName('mount') as $mount) {
$mounts[] = Mount_parseNode($mount);
}
return $mounts;
}
function Mount_parseNode($node): array
{
$id = (int)$node->getAttribute('id');
$clientid = (int)$node->getAttribute('clientid');
$name = $node->getAttribute('name');
$speed = (int)$node->getAttribute('speed');
$premium = $node->getAttribute('premium');
$type = $node->getAttribute('type');
return array('id' => $id, 'clientid' => $clientid, 'name' => $name, 'speed' => $speed, 'premium' => $premium, 'type' => $type);
}
function load_config_lua(string $file): array
{
$result = Loader::load($file);
if ($result === false) {
log_append('error.log', '[load_config_file] Fatal error: Cannot load config.lua (' . $file . ').');
throw new \RuntimeException('ERROR: Cannot find ' . $file . ' file.');
}
return $result;
}
function configLua($key) {
global $config;
if (is_array($key)) {
return $config['lua'][$key[0]] = $key[1];
}
return @$config['lua'][$key];
}

View File

@@ -18,6 +18,19 @@ if (!isset($config['database_overwrite'])) {
if(!$config['database_overwrite'] && !isset($config['database_user'][0], $config['database_password'][0], $config['database_name'][0])) if(!$config['database_overwrite'] && !isset($config['database_user'][0], $config['database_password'][0], $config['database_name'][0]))
{ {
if (isset($config['server']['database']['mysql'])) { // BlackTek
$config['otserv_version'] = BLACKTEK;
$config['database_type'] = 'mysql';
$config['database_host'] = $config['server']['database']['mysql']['host'];
$config['database_port'] = $config['server']['database']['mysql']['port'];
$config['database_user'] = $config['server']['database']['mysql']['user'];
$config['database_password'] = $config['server']['database']['mysql']['pass'];
$config['database_name'] = $config['server']['database']['mysql']['database'];
if(!isset($config['database_socket'][0]) && !empty(trim($config['server']['database']['mysql']['socket']))) {
$config['database_socket'] = trim($config['server']['database']['mysql']['socket']);
}
$config['database_encryption'] = 'sha1';
}
if(isset($config['lua']['sqlType'])) {// tfs 0.3 if(isset($config['lua']['sqlType'])) {// tfs 0.3
if(isset($config['lua']['mysqlHost'])) {// tfs 0.2 if(isset($config['lua']['mysqlHost'])) {// tfs 0.2
$config['otserv_version'] = TFS_02; $config['otserv_version'] = TFS_02;
@@ -94,7 +107,6 @@ if(!isset($config['database_socket'])) {
$config['database_socket'] = ''; $config['database_socket'] = '';
} }
try { try {
$ots->connect(array( $ots->connect(array(
'host' => $config['database_host'], 'host' => $config['database_host'],

View File

@@ -11,7 +11,6 @@ defined('MYAAC') or die('Direct access not allowed!');
use MyAAC\Cache\Cache; use MyAAC\Cache\Cache;
use MyAAC\CsrfToken; use MyAAC\CsrfToken;
use MyAAC\Items;
use MyAAC\Models\Config; use MyAAC\Models\Config;
use MyAAC\Models\Guild; use MyAAC\Models\Guild;
use MyAAC\Models\House; use MyAAC\Models\House;
@@ -21,6 +20,7 @@ use MyAAC\Models\PlayerDeath;
use MyAAC\Models\PlayerKillers; use MyAAC\Models\PlayerKillers;
use MyAAC\News; use MyAAC\News;
use MyAAC\Plugins; use MyAAC\Plugins;
use MyAAC\Server\Items;
use MyAAC\Settings; use MyAAC\Settings;
use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\PHPMailer;
@@ -987,85 +987,6 @@ function log_append($file, $str, array $params = [])
fclose($f); fclose($f);
} }
function load_config_lua($filename)
{
global $config;
$config_file = $filename;
if(!@file_exists($config_file))
{
log_append('error.log', '[load_config_file] Fatal error: Cannot load config.lua (' . $filename . ').');
throw new RuntimeException('ERROR: Cannot find ' . $filename . ' file.');
}
$result = array();
$config_string = str_replace(array("\r\n", "\r"), "\n", file_get_contents($filename));
$lines = explode("\n", $config_string);
if(count($lines) > 0) {
foreach($lines as $ln => $line)
{
$line = trim($line);
if(isset($line[0]) && ($line[0] === '{' || $line[0] === '}')) {
// arrays are not supported yet
// just ignore the error
continue;
}
$tmp_exp = explode('=', $line, 2);
if(str_contains($line, 'dofile')) {
$delimiter = '"';
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) {
$key = trim($tmp_exp[0]);
if(!str_starts_with($key, '--')) {
$value = trim($tmp_exp[1]);
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] = substr(substr($value, 1), 0, -1);
elseif(in_array($value, array('true', 'false')))
$result[$key] = $value === 'true';
elseif(@$value[0] === '{') {
// arrays are not supported yet
// just ignore the error
continue;
}
else
{
foreach($result as $tmp_key => $tmp_value) { // load values defined by other keys, like: dailyFragsToBlackSkull = dailyFragsToRedSkull
$value = str_replace($tmp_key, $tmp_value, $value);
}
try {
$ret = eval("return $value;");
}
catch (Throwable $e) {
throw new RuntimeException('ERROR: Loading config.lua file. Line: ' . ($ln + 1) . ' - Unable to parse value "' . $value . '" - ' . $e->getMessage());
}
if((string) $ret == '' && trim($value) !== '""') {
throw new RuntimeException('ERROR: Loading config.lua file. Line ' . ($ln + 1) . ' is not valid [key: ' . $key . ']');
}
$result[$key] = $ret;
}
}
}
}
}
return array_merge($result, $config['lua'] ?? []);
}
function str_replace_first($search,$replace, $subject) { function str_replace_first($search,$replace, $subject) {
$pos = strpos($subject, $search); $pos = strpos($subject, $search);
if ($pos !== false) { if ($pos !== false) {
@@ -1325,15 +1246,6 @@ function config($key) {
return @$config[$key]; return @$config[$key];
} }
function configLua($key) {
global $config;
if (is_array($key)) {
return $config['lua'][$key[0]] = $key[1];
}
return @$config['lua'][$key];
}
function setting($key) function setting($key)
{ {
$settings = Settings::getInstance(); $settings = Settings::getInstance();
@@ -1640,58 +1552,6 @@ function verify_number($number, $name, $max_length)
echo_error($name . ' cannot be longer than ' . $max_length . ' digits.'); echo_error($name . ' cannot be longer than ' . $max_length . ' digits.');
} }
function Outfits_loadfromXML()
{
global $config;
$file_path = $config['data_path'] . 'XML/outfits.xml';
if (!file_exists($file_path)) { return null; }
$xml = new DOMDocument;
$xml->load($file_path);
$outfits = null;
foreach ($xml->getElementsByTagName('outfit') as $outfit) {
$outfits[] = Outfit_parseNode($outfit);
}
return $outfits;
}
function Outfit_parseNode($node) {
$looktype = (int)$node->getAttribute('looktype');
$type = (int)$node->getAttribute('type');
$lookname = $node->getAttribute('name');
$premium = $node->getAttribute('premium');
$unlocked = $node->getAttribute('unlocked');
$enabled = $node->getAttribute('enabled');
return array('id' => $looktype, 'type' => $type, 'name' => $lookname, 'premium' => $premium, 'unlocked' => $unlocked, 'enabled' => $enabled);
}
function Mounts_loadfromXML()
{
global $config;
$file_path = $config['data_path'] . 'XML/mounts.xml';
if (!file_exists($file_path)) { return null; }
$xml = new DOMDocument;
$xml->load($file_path);
$mounts = null;
foreach ($xml->getElementsByTagName('mount') as $mount) {
$mounts[] = Mount_parseNode($mount);
}
return $mounts;
}
function Mount_parseNode($node) {
$id = (int)$node->getAttribute('id');
$clientid = (int)$node->getAttribute('clientid');
$name = $node->getAttribute('name');
$speed = (int)$node->getAttribute('speed');
$premium = $node->getAttribute('premium');
$type = $node->getAttribute('type');
return array('id' => $id, 'clientid' => $clientid, 'name' => $name, 'speed' => $speed, 'premium' => $premium, 'type' => $type);
}
function left($str, $length) { function left($str, $length) {
return substr($str, 0, $length); return substr($str, 0, $length);
} }

View File

@@ -14,7 +14,8 @@ use MyAAC\CsrfToken;
use MyAAC\Hooks; use MyAAC\Hooks;
use MyAAC\Plugins; use MyAAC\Plugins;
use MyAAC\Models\Town; use MyAAC\Models\Town;
use MyAAC\Server\XML\Vocations; use MyAAC\Server\Config;
use MyAAC\Server\Vocations;
use MyAAC\Settings; use MyAAC\Settings;
defined('MYAAC') or die('Direct access not allowed!'); defined('MYAAC') or die('Direct access not allowed!');
@@ -90,28 +91,20 @@ foreach($_REQUEST as $var => $value) {
} }
// load otserv config file // load otserv config file
$config_lua_reload = true;
if($cache->enabled()) { if($cache->enabled()) {
$tmp = null; $tmp = null;
if($cache->fetch('server_path', $tmp) && $tmp == $config['server_path']) { if(!$cache->fetch('server_path', $tmp) || $tmp != config('server_path')) {
$tmp = null; $cache->delete('config_server');
if($cache->fetch('config_lua', $tmp) && $tmp) {
$config['lua'] = unserialize($tmp);
$config_lua_reload = false;
}
} }
} }
if($config_lua_reload) { if (empty($config['server'])) {
$config['lua'] = load_config_lua($config['server_path'] . 'config.lua'); $config['server'] = $config['lua'] = Config::get();
// cache config
if($cache->enabled()) { if($cache->enabled()) {
$cache->set('config_lua', serialize($config['lua']), 2 * 60); $cache->set('server_path', config('server_path'), 10 * 60);
$cache->set('server_path', $config['server_path'], 10 * 60);
} }
} }
unset($tmp);
if(isset($config['lua']['servername'])) if(isset($config['lua']['servername']))
$config['lua']['serverName'] = $config['lua']['servername']; $config['lua']['serverName'] = $config['lua']['servername'];

View File

@@ -372,8 +372,8 @@ class POT
global $debugBar; global $debugBar;
if (isset($debugBar)) { if (isset($debugBar)) {
$this->db = new DebugBar\DataCollector\PDO\TraceablePDO(new OTS_DB_MySQL($params)); $this->db = new \MyAAC\Debug\TraceablePDOWithBacktrace(new OTS_DB_MySQL($params));
$debugBar->addCollector(new DebugBar\DataCollector\PDO\PDOCollector($this->db)); $debugBar->addCollector(new \MyAAC\Debug\PDOCollectorWithBacktrace($this->db));
} }
else { else {
$this->db = new OTS_DB_MySQL($params); $this->db = new OTS_DB_MySQL($params);

View File

@@ -736,11 +736,17 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
*/ */
public function setCustomField($field, $value) public function setCustomField($field, $value)
{ {
if( !isset($this->data['id']) ) { if( !isset($this->data['id']) )
{
throw new E_OTS_NotLoaded(); throw new E_OTS_NotLoaded();
} }
AccountModel::where('id', $this->data['id'])->update([$field => $value]); // quotes value for SQL query
if(!( is_int($value) || is_float($value) ))
{
$value = $this->db->quote($value);
}
$this->db->exec('UPDATE ' . $this->db->tableName('accounts') . ' SET ' . $this->db->fieldName($field) . ' = ' . $value . ' WHERE ' . $this->db->fieldName('id') . ' = ' . $this->data['id']);
} }
/** /**

View File

@@ -8,7 +8,7 @@
* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU Lesser General Public License, Version 3 * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU Lesser General Public License, Version 3
*/ */
use MyAAC\Cache\Cache; use MyAAC\Server\Groups;
/** /**
* List of groups. * List of groups.
@@ -47,73 +47,8 @@ class OTS_Groups_List implements IteratorAggregate, Countable
return; return;
} }
if(!isset($file[0])) foreach(Groups::get() as $id => $info) {
{ $this->groups[$id] = new OTS_Group($info);
global $config;
$file = $config['data_path'] . 'XML/groups.xml';
}
if(!@file_exists($file)) {
error('Error: Cannot load groups.xml. More info in system/logs/error.log file.');
log_append('error.log', '[OTS_Groups_List.php] Fatal error: Cannot load groups.xml (' . $file . '). It doesnt exist.');
return;
}
$cache = Cache::getInstance();
$data = array();
if($cache->enabled())
{
$tmp = '';
if($cache->fetch('groups', $tmp))
$data = unserialize($tmp);
else
{
$groups = new DOMDocument();
if(!@$groups->load($file)) {
error('Error: Cannot load groups.xml. More info in system/logs/error.log file.');
log_append('error.log', '[OTS_Groups_List.php] Fatal error: Cannot load groups.xml (' . $file . '). Error: ' . print_r(error_get_last(), true));
return;
}
// loads groups
foreach( $groups->getElementsByTagName('group') as $group)
{
$data[$group->getAttribute('id')] = array(
'id' => $group->getAttribute('id'),
'name' => $group->getAttribute('name'),
'access' => $group->getAttribute('access')
);
}
$cache->set('groups', serialize($data), 120);
}
foreach($data as $id => $info)
$this->groups[ $id ] = new OTS_Group($info);
}
else
{
// loads DOM document
$groups = new DOMDocument();
if(!@$groups->load($file)) {
error('Error: Cannot load groups.xml. More info in system/logs/error.log file.');
log_append('error.log', '[OTS_Groups_List.php] Fatal error: Cannot load groups.xml (' . $file . '). Error: ' . print_r(error_get_last(), true));
return;
}
// loads groups
foreach($groups->getElementsByTagName('group') as $group)
{
$data[$group->getAttribute('id')] = array(
'id' => $group->getAttribute('id'),
'name' => $group->getAttribute('name'),
'access' => $group->getAttribute('access')
);
$this->groups[ $group->getAttribute('id') ] = new OTS_Group($data[$group->getAttribute('id')]);
//echo $this->getGroup(1)->getName();
}
} }
} }

284
system/libs/rfc6238.php Normal file
View File

@@ -0,0 +1,284 @@
<?php
/** https://github.com/Voronenko/PHPOTP/blob/08cda9cb9c30b7242cf0b3a9100a6244a2874927/code/base32static.php
* Encode in Base32 based on RFC 4648.
* Requires 20% more space than base64
* Great for case-insensitive filesystems like Windows and URL's (except for = char which can be excluded using the pad option for urls)
*
* @package default
* @author Bryan Ruiz
**/
class Base32Static {
private static $map = array(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
'=' // padding character
);
private static $flippedMap = array(
'A'=>'0', 'B'=>'1', 'C'=>'2', 'D'=>'3', 'E'=>'4', 'F'=>'5', 'G'=>'6', 'H'=>'7',
'I'=>'8', 'J'=>'9', 'K'=>'10', 'L'=>'11', 'M'=>'12', 'N'=>'13', 'O'=>'14', 'P'=>'15',
'Q'=>'16', 'R'=>'17', 'S'=>'18', 'T'=>'19', 'U'=>'20', 'V'=>'21', 'W'=>'22', 'X'=>'23',
'Y'=>'24', 'Z'=>'25', '2'=>'26', '3'=>'27', '4'=>'28', '5'=>'29', '6'=>'30', '7'=>'31'
);
/**
* Use padding false when encoding for urls
*
* @return base32 encoded string
* @author Bryan Ruiz
**/
public static function encode($input, $padding = true) {
if(empty($input)) return "";
$input = str_split($input);
$binaryString = "";
for($i = 0; $i < count($input); $i++) {
$binaryString .= str_pad(base_convert(ord($input[$i]), 10, 2), 8, '0', STR_PAD_LEFT);
}
$fiveBitBinaryArray = str_split($binaryString, 5);
$base32 = "";
$i=0;
while($i < count($fiveBitBinaryArray)) {
$base32 .= self::$map[base_convert(str_pad($fiveBitBinaryArray[$i], 5,'0'), 2, 10)];
$i++;
}
if($padding && ($x = strlen($binaryString) % 40) != 0) {
if($x == 8) $base32 .= str_repeat(self::$map[32], 6);
else if($x == 16) $base32 .= str_repeat(self::$map[32], 4);
else if($x == 24) $base32 .= str_repeat(self::$map[32], 3);
else if($x == 32) $base32 .= self::$map[32];
}
return $base32;
}
public static function decode($input) {
if(empty($input)) return;
$paddingCharCount = substr_count($input, self::$map[32]);
$allowedValues = array(6,4,3,1,0);
if(!in_array($paddingCharCount, $allowedValues)) return false;
for($i=0; $i<4; $i++){
if($paddingCharCount == $allowedValues[$i] &&
substr($input, -($allowedValues[$i])) != str_repeat(self::$map[32], $allowedValues[$i])) return false;
}
$input = str_replace('=','', $input);
$input = str_split($input);
$binaryString = "";
for($i=0; $i < count($input); $i = $i+8) {
$x = "";
if(!in_array($input[$i], self::$map)) return false;
for($j=0; $j < 8; $j++) {
$x .= str_pad(base_convert(@self::$flippedMap[@$input[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
}
$eightBits = str_split($x, 8);
for($z = 0; $z < count($eightBits); $z++) {
$binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:"";
}
}
return $binaryString;
}
}
// http://www.faqs.org/rfcs/rfc6238.html
// https://github.com/Voronenko/PHPOTP/blob/08cda9cb9c30b7242cf0b3a9100a6244a2874927/code/rfc6238.php
// Local changes: http -> https, consistent indentation, 200x200 -> 300x300 QR image size, PHP end tag
class TokenAuth6238 {
/**
* verify
*
* @param string $secretkey Secret clue (base 32).
* @return bool True if success, false if failure
*/
public static function verify($secretkey, $code, $rangein30s = 3) {
$key = base32static::decode($secretkey);
$unixtimestamp = time()/30;
for($i=-($rangein30s); $i<=$rangein30s; $i++) {
$checktime = (int)($unixtimestamp+$i);
$thiskey = self::oath_hotp($key, $checktime);
if ((int)$code == self::oath_truncate($thiskey,6)) {
return true;
}
}
return false;
}
public static function getTokenCode($secretkey,$rangein30s = 3) {
$result = "";
$key = base32static::decode($secretkey);
$unixtimestamp = time()/30;
for($i=-($rangein30s); $i<=$rangein30s; $i++) {
$checktime = (int)($unixtimestamp+$i);
$thiskey = self::oath_hotp($key, $checktime);
$result = $result." # ".self::oath_truncate($thiskey,6);
}
return $result;
}
public static function getTokenCodeDebug($secretkey,$rangein30s = 3) {
$result = "";
print "<br/>SecretKey: $secretkey <br/>";
$key = base32static::decode($secretkey);
print "Key(base 32 decode): $key <br/>";
$unixtimestamp = time()/30;
print "UnixTimeStamp (time()/30): $unixtimestamp <br/>";
for($i=-($rangein30s); $i<=$rangein30s; $i++) {
$checktime = (int)($unixtimestamp+$i);
print "Calculating oath_hotp from (int)(unixtimestamp +- 30sec offset): $checktime basing on secret key<br/>";
$thiskey = self::oath_hotp($key, $checktime, true);
print "======================================================<br/>";
print "CheckTime: $checktime oath_hotp:".$thiskey."<br/>";
$result = $result." # ".self::oath_truncate($thiskey,6,true);
}
return $result;
}
public static function getBarCodeUrl($username, $domain, $secretkey, $issuer) {
$url = "https://chart.apis.google.com/chart";
$url = $url."?chs=300x300&chld=M|0&cht=qr&chl=otpauth://totp/";
$url = $url.$username . "@" . $domain . "%3Fsecret%3D" . $secretkey . '%26issuer%3D' . rawurlencode($issuer);
return $url;
}
public static function generateRandomClue($length = 16) {
$b32 = "234567QWERTYUIOPASDFGHJKLZXCVBNM";
$s = "";
for ($i = 0; $i < $length; $i++)
$s .= $b32[rand(0,31)];
return $s;
}
private static function hotp_tobytestream($key) {
$result = array();
$last = strlen($key);
for ($i = 0; $i < $last; $i = $i + 2) {
$x = $key[$i] + $key[$i + 1];
$x = strtoupper($x);
$x = hexdec($x);
$result = $result.chr($x);
}
return $result;
}
private static function oath_hotp ($key, $counter, $debug=false) {
$result = "";
$orgcounter = $counter;
$cur_counter = array(0,0,0,0,0,0,0,0);
if ($debug) {
print "Packing counter $counter (".dechex($counter).")into binary string - pay attention to hex representation of key and binary representation<br/>";
}
for($i=7;$i>=0;$i--) { // C for unsigned char, * for repeating to the end of the input data
$cur_counter[$i] = pack ('C*', $counter);
if ($debug) {
print $cur_counter[$i]."(".dechex(ord($cur_counter[$i])).")"." from $counter <br/>";
}
$counter = $counter >> 8;
}
if ($debug) {
foreach ($cur_counter as $char) {
print ord($char) . " ";
}
print "<br/>";
}
$binary = implode($cur_counter);
// Pad to 8 characters
str_pad($binary, 8, chr(0), STR_PAD_LEFT);
if ($debug) {
print "Prior to HMAC calculation pad with zero on the left until 8 characters.<br/>";
print "Calculate sha1 HMAC(Hash-based Message Authentication Code http://en.wikipedia.org/wiki/HMAC).<br/>";
print "hash_hmac ('sha1', $binary, $key)<br/>";
}
$result = hash_hmac ('sha1', $binary, $key);
if ($debug) {
print "Result: $result <br/>";
}
return $result;
}
private static function oath_truncate($hash, $length = 6, $debug=false) {
$result="";
// Convert to dec
if($debug) {
print "converting hex hash into characters<br/>";
}
$hashcharacters = str_split($hash,2);
if($debug) {
print_r($hashcharacters);
print "<br/>and convert to decimals:<br/>";
}
for ($j=0; $j<count($hashcharacters); $j++) {
$hmac_result[]=hexdec($hashcharacters[$j]);
}
if($debug) {
print_r($hmac_result);
}
// http://php.net/manual/ru/function.hash-hmac.php
// adopted from brent at thebrent dot net 21-May-2009 08:17 comment
$offset = $hmac_result[19] & 0xf;
if($debug) {
print "Calculating offset as 19th element of hmac:".$hmac_result[19]."<br/>";
print "offset:".$offset;
}
$result = (
(($hmac_result[$offset+0] & 0x7f) << 24 ) |
(($hmac_result[$offset+1] & 0xff) << 16 ) |
(($hmac_result[$offset+2] & 0xff) << 8 ) |
($hmac_result[$offset+3] & 0xff)
) % pow(10,$length);
return $result;
}
}

View File

@@ -20,7 +20,7 @@ $locale['not_loaded'] = 'Nicht geladen';
$locale['loading_spinner'] = 'Bitte warten, installieren...'; $locale['loading_spinner'] = 'Bitte warten, installieren...';
$locale['importing_spinner'] = 'Bitte warte, Daten werden importiert...'; $locale['importing_spinner'] = 'Bitte warte, Daten werden importiert...';
$locale['please_fill_all'] = 'Bitte füllen Sie alle Felder aus!'; $locale['please_fill_all'] = 'Bitte füllen Sie alle Felder aus!';
$locale['already_installed'] = 'MyAAC wurde bereits installiert. Bitte löschen <b>install/</b> Verzeichnis. Wenn Sie MyAAC neu installieren möchten, löschen Sie die Datei <strong>config.local.php</strong> aus dem Hauptverzeichnis und aktualisieren Sie die Seite.'; $locale['already_installed'] = 'MyAAC wurde bereits installiert. Wenn Sie MyAAC neu installieren möchten, löschen Sie die Datei <strong>config.local.php</strong> aus dem Hauptverzeichnis und aktualisieren Sie die Seite.';
// welcome // welcome
$locale['step_welcome'] = 'Willkommen'; $locale['step_welcome'] = 'Willkommen';
@@ -67,7 +67,7 @@ $locale['step_database'] = 'Schema importieren';
$locale['step_database_title'] = 'MySQL schema importieren'; $locale['step_database_title'] = 'MySQL schema importieren';
$locale['step_database_importing'] = 'Ihre Datenbank ist MySQL. Datenbankname ist: "$DATABASE_NAME$". Schema wird jetzt importiert...'; $locale['step_database_importing'] = 'Ihre Datenbank ist MySQL. Datenbankname ist: "$DATABASE_NAME$". Schema wird jetzt importiert...';
$locale['step_database_error_path'] = 'Bitte geben Sie den Serverpfad an.'; $locale['step_database_error_path'] = 'Bitte geben Sie den Serverpfad an.';
$locale['step_database_error_config'] = 'Datei config.lua kann nicht gefunden werden. Ist der Serverpfad korrekt? Gehen Sie zurück und überprüfen Sie noch einmal.'; $locale['step_database_error_config'] = 'Datei config.lua oder config/server.toml kann nicht gefunden werden. Ist der Serverpfad korrekt? Gehen Sie zurück und überprüfen Sie noch einmal.';
$locale['step_database_error_database_empty'] = 'Der Datenbanktyp kann nicht aus config.lua ermittelt werden. Ihr OTS wird von diesem AAC nicht unterstützt.'; $locale['step_database_error_database_empty'] = 'Der Datenbanktyp kann nicht aus config.lua ermittelt werden. Ihr OTS wird von diesem AAC nicht unterstützt.';
$locale['step_database_error_only_mysql'] = 'Dieser AAC unterstützt nur MySQL. Aus Ihrer Konfigurationsdatei scheint Ihr OTS die Datenbank $DATABASE_TYPE$ zu verwenden. Bitte ändern Sie Ihre Datenbank in MySQL und folgen Sie dann der Installation erneut.'; $locale['step_database_error_only_mysql'] = 'Dieser AAC unterstützt nur MySQL. Aus Ihrer Konfigurationsdatei scheint Ihr OTS die Datenbank $DATABASE_TYPE$ zu verwenden. Bitte ändern Sie Ihre Datenbank in MySQL und folgen Sie dann der Installation erneut.';
$locale['step_database_error_table'] = 'Die Tabelle $TABLE$ existiert nicht. Bitte importieren Sie zuerst Ihr OTS-Datenbankschema.'; $locale['step_database_error_table'] = 'Die Tabelle $TABLE$ existiert nicht. Bitte importieren Sie zuerst Ihr OTS-Datenbankschema.';

View File

@@ -20,7 +20,7 @@ $locale['not_loaded'] = 'Not loaded';
$locale['loading_spinner'] = 'Please wait, installing...'; $locale['loading_spinner'] = 'Please wait, installing...';
$locale['importing_spinner'] = 'Please wait, importing data...'; $locale['importing_spinner'] = 'Please wait, importing data...';
$locale['please_fill_all'] = 'Please fill all inputs!'; $locale['please_fill_all'] = 'Please fill all inputs!';
$locale['already_installed'] = 'MyAAC has been already installed. Please delete <b>install/</b> directory. If you want to reinstall MyAAC - please delete <strong>config.local.php</strong> file from the main directory and refresh the page.'; $locale['already_installed'] = 'MyAAC has been already installed. If you want to reinstall MyAAC - please delete <strong>config.local.php</strong> file from the main directory and refresh the page.';
// welcome // welcome
$locale['step_welcome'] = 'Welcome'; $locale['step_welcome'] = 'Welcome';
@@ -72,7 +72,7 @@ $locale['step_database_title'] = 'Import MySQL schema';
$locale['step_database_importing'] = 'Your database is MySQL. Database name is: "$DATABASE_NAME$". Importing schema now...'; $locale['step_database_importing'] = 'Your database is MySQL. Database name is: "$DATABASE_NAME$". Importing schema now...';
$locale['step_database_config_saved'] = 'Local configuration has been saved into file: config.local.php'; $locale['step_database_config_saved'] = 'Local configuration has been saved into file: config.local.php';
$locale['step_database_error_path'] = 'Please specify server path.'; $locale['step_database_error_path'] = 'Please specify server path.';
$locale['step_database_error_config'] = 'Cannot find config.lua file. Is your server path correct? Go back and check again.'; $locale['step_database_error_config'] = 'Cannot find config.lua or config/server.toml file. Is your server path correct? Go back and check again.';
$locale['step_database_error_database_empty'] = 'Cannot determine database type from config.lua. Your OTS is unsupported by this AAC.'; $locale['step_database_error_database_empty'] = 'Cannot determine database type from config.lua. Your OTS is unsupported by this AAC.';
$locale['step_database_error_only_mysql'] = 'This AAC supports only MySQL. From your config file it seems that your OTS is using: $DATABASE_TYPE$ database. Please change your database to MySQL and then follow the installation again.'; $locale['step_database_error_only_mysql'] = 'This AAC supports only MySQL. From your config file it seems that your OTS is using: $DATABASE_TYPE$ database. Please change your database to MySQL and then follow the installation again.';
$locale['step_database_error_table'] = 'Table $TABLE$ doesn\'t exist. Please import your OTS database schema first.'; $locale['step_database_error_table'] = 'Table $TABLE$ doesn\'t exist. Please import your OTS database schema first.';

View File

@@ -20,7 +20,7 @@ $locale['not_loaded'] = 'Nie załadowane';
$locale['loading_spinner'] = 'Proszę czekać, trwa instalacja...'; $locale['loading_spinner'] = 'Proszę czekać, trwa instalacja...';
$locale['importing_spinner'] = 'Proszę czekać, trwa importowanie danych...'; $locale['importing_spinner'] = 'Proszę czekać, trwa importowanie danych...';
$locale['please_fill_all'] = 'Proszę wypełnić wszystkie pola!'; $locale['please_fill_all'] = 'Proszę wypełnić wszystkie pola!';
$locale['already_installed'] = 'MyAAC został już zainstalowany. Proszę usunąć katalog <b>install/</b>. Jeśli chcesz zainstalować MyAAC od nowa - proszę usuń plik <strong>config.local.php</strong> z katalogu głównego i odśwież stronę.'; $locale['already_installed'] = 'MyAAC został już zainstalowany. Jeśli chcesz zainstalować MyAAC od nowa - proszę usuń plik <strong>config.local.php</strong> z katalogu głównego i odśwież stronę.';
// welcome // welcome
$locale['step_welcome'] = 'Witamy'; $locale['step_welcome'] = 'Witamy';
@@ -71,7 +71,7 @@ $locale['step_database_title'] = 'Baza MySQL';
$locale['step_database_importing'] = 'Twoja baza to MySQL. Nazwa bazy danych to: "$DATABASE_NAME$". Importowanie schematu...'; $locale['step_database_importing'] = 'Twoja baza to MySQL. Nazwa bazy danych to: "$DATABASE_NAME$". Importowanie schematu...';
$locale['step_database_config_saved'] = 'Lokalna konfiguracja została zapisana do pliku: config.local.php'; $locale['step_database_config_saved'] = 'Lokalna konfiguracja została zapisana do pliku: config.local.php';
$locale['step_database_error_path'] = 'Proszę podać ścieżkę do serwera.'; $locale['step_database_error_path'] = 'Proszę podać ścieżkę do serwera.';
$locale['step_database_error_config'] = 'Nie można znaleźć pliku config.lua. Czy ścieżka do katalogu serwera jest poprawna? Wróć się i sprawdź ponownie.'; $locale['step_database_error_config'] = 'Nie można znaleźć pliku config.lua lub config/server.toml. Czy ścieżka do katalogu serwera jest poprawna? Wróć się i sprawdź ponownie.';
$locale['step_database_error_database_empty'] = 'Nie można wykryć typu bazy danych z pliku config.lua. Prawdopodobnie Twój OTS nie jest wspierany przez ten AAC.'; $locale['step_database_error_database_empty'] = 'Nie można wykryć typu bazy danych z pliku config.lua. Prawdopodobnie Twój OTS nie jest wspierany przez ten AAC.';
$locale['step_database_error_only_mysql'] = 'Ten AAC wspiera tylko bazy danych MySQL. Z Twojego pliku config wynika, że Twój serwera używa bazy: $DATABASE_TYPE$. Proszę zmienić typ bazy na MySQL i ponownie przystąpić do instalacji.'; $locale['step_database_error_only_mysql'] = 'Ten AAC wspiera tylko bazy danych MySQL. Z Twojego pliku config wynika, że Twój serwera używa bazy: $DATABASE_TYPE$. Proszę zmienić typ bazy na MySQL i ponownie przystąpić do instalacji.';
$locale['step_database_error_table'] = 'Tabela $TABLE$ nie istnieje. Proszę najpierw zaimportować schemat bazy danych serwera OTS.'; $locale['step_database_error_table'] = 'Tabela $TABLE$ nie istnieje. Proszę najpierw zaimportować schemat bazy danych serwera OTS.';

View File

@@ -20,7 +20,7 @@ $locale['not_loaded'] = 'Não carregado';
$locale['loading_spinner'] = 'Por favor aguarde, instalando...'; $locale['loading_spinner'] = 'Por favor aguarde, instalando...';
$locale['importing_spinner'] = 'Por favor, aguarde, importando dados...'; $locale['importing_spinner'] = 'Por favor, aguarde, importando dados...';
$locale['please_fill_all'] = 'Por favor, preencha todas as entradas!'; $locale['please_fill_all'] = 'Por favor, preencha todas as entradas!';
$locale['already_installed'] = 'MyAAC já foi instalado. Por favor, apague o diretório <b> install/ <b/>. Se você quiser reinstalar o MyAAC - exclua o arquivo <strong> config.local.php </strong> do diretório principal e atualize a página.'; $locale['already_installed'] = 'MyAAC já foi instalado. Se você quiser reinstalar o MyAAC - exclua o arquivo <strong> config.local.php </strong> do diretório principal e atualize a página.';
// welcome // welcome
$locale['step_welcome'] = 'Bem vindo'; $locale['step_welcome'] = 'Bem vindo';
@@ -61,7 +61,7 @@ $locale['step_database'] = 'Importar schema';
$locale['step_database_title'] = 'Importar MySQL schema'; $locale['step_database_title'] = 'Importar MySQL schema';
$locale['step_database_importing'] = 'Seu banco de dados é o MySQL. O nome do banco de dados é: "$DATABASE_NAME$". Importando schema agora...'; $locale['step_database_importing'] = 'Seu banco de dados é o MySQL. O nome do banco de dados é: "$DATABASE_NAME$". Importando schema agora...';
$locale['step_database_error_path'] = 'Por favor, especifique o caminho da pasta do servidor.'; $locale['step_database_error_path'] = 'Por favor, especifique o caminho da pasta do servidor.';
$locale['step_database_error_config'] = 'Não é possível encontrar o arquivo config.lua. O caminho da pasta do seu servidor está correto? Volte e verifique novamente.'; $locale['step_database_error_config'] = 'Não é possível encontrar o arquivo config.lua ou config/server.toml. O caminho da pasta do seu servidor está correto? Volte e verifique novamente.';
$locale['step_database_error_database_empty'] = 'Não é possível determinar o tipo de banco de dados a partir do config.lua. Seu OTS não é suportado por este AAC.'; $locale['step_database_error_database_empty'] = 'Não é possível determinar o tipo de banco de dados a partir do config.lua. Seu OTS não é suportado por este AAC.';
$locale['step_database_error_only_mysql'] = 'Este AAC suporta apenas o MySQL. A partir do seu arquivo de configuração, parece que o seu OTS está usando: $DATABASE_TYPE$ database. Por favor, mude seu banco de dados para o MySQL e siga a instalação novamente.'; $locale['step_database_error_only_mysql'] = 'Este AAC suporta apenas o MySQL. A partir do seu arquivo de configuração, parece que o seu OTS está usando: $DATABASE_TYPE$ database. Por favor, mude seu banco de dados para o MySQL e siga a instalação novamente.';
$locale['step_database_error_table'] = 'A tabela $TABLE$ não existe. Por favor, importe seu schema de banco de dados OTS primeiro.'; $locale['step_database_error_table'] = 'A tabela $TABLE$ não existe. Por favor, importe seu schema de banco de dados OTS primeiro.';

View File

@@ -18,7 +18,7 @@ $locale['loaded'] = 'Laddad';
$locale['not_loaded'] = 'Inte Laddad'; $locale['not_loaded'] = 'Inte Laddad';
$locale['please_fill_all'] = 'Vänligen fyll i allt!'; $locale['please_fill_all'] = 'Vänligen fyll i allt!';
$locale['already_installed'] = 'MyAAC är redan installerat. Vänligen ta bort <b>install/<b/> mappen. Om du vill installera MyAAC igen - ta bort filen <strong>config.local.php</strong> från huvudkatalogen och uppdatera sidan.'; $locale['already_installed'] = 'MyAAC är redan installerat. Om du vill installera MyAAC igen - ta bort filen <strong>config.local.php</strong> från huvudkatalogen och uppdatera sidan.';
// welcome // welcome
$locale['step_welcome'] = 'Välkommen'; $locale['step_welcome'] = 'Välkommen';

View File

@@ -1,8 +0,0 @@
CREATE TABLE `myaac_account_email_codes`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`account_id` int NOT NULL,
`code` varchar(6) NOT NULL,
`created_at` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;

View File

@@ -1,36 +1,10 @@
<?php <?php
// 2fa
// add the myaac_account_email_codes
/**
* @var OTS_DB_MySQL $db
*/
$up = function () use ($db) { $up = function () use ($db) {
if (!$db->hasColumn('accounts', '2fa_type')) { $db->modifyColumn(TABLE_PREFIX . 'config', 'name', "varchar(255) NOT NULL");
$db->addColumn('accounts', '2fa_type', "tinyint NOT NULL DEFAULT 0 AFTER `web_flags`"); $db->modifyColumn(TABLE_PREFIX . 'config', 'value', "varchar(10000) NOT NULL");
}
if (!$db->hasColumn('accounts', '2fa_secret')) {
$db->addColumn('accounts', '2fa_secret', "varchar(16) NOT NULL DEFAULT '' AFTER `2fa_type`");
}
// add myaac_account_email_codes table
if (!$db->hasTable(TABLE_PREFIX . 'account_email_codes')) {
$db->exec(file_get_contents(__DIR__ . '/51-account_email_codes.sql'));
}
}; };
$down = function () use ($db) { $down = function () {
if ($db->hasColumn('accounts', '2fa_type')) { // nothing to do, to not lose data
$db->dropColumn('accounts', '2fa_type');
}
if ($db->hasColumn('accounts', '2fa_secret')) {
$db->dropColumn('accounts', '2fa_secret');
}
if ($db->hasTable(TABLE_PREFIX . 'account_email_codes')) {
$db->dropTable(TABLE_PREFIX . 'account_email_codes');
}
}; };

13
system/migrations/52.php Normal file
View File

@@ -0,0 +1,13 @@
<?php
/**
* 2026-04-12
* Add indexes to myaac_account_actions table
*/
$up = function () use ($db) {
$db->query("CREATE INDEX `myaac_account_actions_account_id` ON `myaac_account_actions` (`account_id`);");
$db->query("CREATE INDEX `myaac_account_actions_ip` ON `myaac_account_actions` (`ip`);");
};
$down = function () {
// nothing to do, to not lose data
};

View File

@@ -1,26 +0,0 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
require __DIR__ . '/../base.php';
if (!isRequestMethod('post')) {
error('This page cannot be accessed directly.');
return;
}
if (!$account_logged->isLoaded()) {
error('Account not found!');
return;
}
if (!$twoFactorAuth->isActive($twoFactorAuth::TYPE_APP)) {
error("Your account does not have Two Factor App Authentication enabled.");
return;
}
$twoFactorAuth->disable();
$twig->display('success.html.twig', [
'title' => 'Disabled',
'description' => 'Two Factor App Authentication has been disabled.'
]);

View File

@@ -1,105 +0,0 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
use MyAAC\TwoFactorAuth\TwoFactorAuth;
require __DIR__ . '/../base.php';
if ($twoFactorAuth->isActive()) {
$errors[] = 'Two-factor authentication is already enabled on your account.';
$twig->display('error_box.html.twig', ['errors' => $errors]);
return;
}
$explodeRecoveryKey = explode('-', $account_logged->getCustomField('key'));
$newRecoveryKeyFormat = (count($explodeRecoveryKey) == 4);
if (ACTION == 'request') {
if ($newRecoveryKeyFormat) {
$key = $_POST['key1'] . '-' . $_POST['key2'] . '-' . $_POST['key3'] . '-' . $_POST['key4'];
}
else {
$key = $_POST['key'];
}
$accountKey = $account_logged->getCustomField('key');
if (!empty($key) && $key == $accountKey) {
$secret = getSession('2fa_secret');
if ($secret === null) {
$secret = generateRandom2faSecret();
setSession('2fa_secret', $secret);
}
$twoFactorAuth->appDisplayEnable($secret);
return;
}
else {
if (empty($key)) {
$errors[] = 'Please enter the recovery key!';
}
else {
$errors[] = 'Invalid recovery key!';
}
}
}
if (ACTION == 'link') {
$secret = getSession('2fa_secret');
if ($secret === null) {
$twig->display('error_box.html.twig', ['errors' => ['Secret not set. Go back and try again.']]);
return;
}
$authCode = $_POST['auth-code'] ?? '';
if (!empty($authCode)) {
$otp = $twoFactorAuth->appInitTOTP($secret);
if (!$otp->verify($authCode)) {
$errors = ['Token is invalid!'];
$twig->display('error_box.html.twig', ['errors' => $errors]);
$twoFactorAuth->appDisplayEnable($secret, $otp, $errors);
return;
}
if ($db->hasColumn('accounts', 'secret')) {
$account_logged->setCustomField('secret', $secret);
}
$account_logged->setCustomField('2fa_secret', $secret);
$twoFactorAuth->enable(TwoFactorAuth::TYPE_APP);
$twig->display('success.html.twig',
[
'title' => 'Authenticator App Connected',
'description' => 'You successfully connected your Tibia account to an authenticator app.'
]
);
return;
}
else {
$errors = ['You have to enter the code generated by the authenticator!'];
$twig->display('error_box.html.twig', ['errors' => $errors]);
$twoFactorAuth->appDisplayEnable($secret, null, $errors);
return;
}
}
if (!empty($errors)) {
$twig->display('error_box.html.twig', ['errors' => $errors]);
}
$twig->display('account/2fa/app/enable.warning.html.twig',
[
'newRecoveryKeyFormat' => $newRecoveryKeyFormat,
'errors' => $errors,
]
);

View File

@@ -1,41 +0,0 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
use MyAAC\TwoFactorAuth\TwoFactorAuth;
csrfProtect();
$title = 'Two Factor Authentication';
/**
* @var OTS_Account $account_logged
*/
$code = $_REQUEST['auth-code'] ?? '';
if (!$account_logged->isLoaded()) {
$current_session = getSession('account');
if($current_session) {
$account_logged = new OTS_Account();
$account_logged->load($current_session);
}
}
$twoFactorAuth = TwoFactorAuth::getInstance($account_logged);
$twig->addGlobal('account_logged', $account_logged);
/**
* Took from ZnoteAAC
* @author Znote
*/
function generateRandom2faSecret($length = 16): string
{
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}

View File

@@ -1,34 +0,0 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
require __DIR__ . '/../base.php';
if ((!setting('core.mail_enabled'))) {
$twig->display('error_box.html.twig', ['errors' => ['Account Two-Factor E-Mail Authentication disabled.']]);
return;
}
if (!isRequestMethod('post')) {
error('This page cannot be accessed directly.');
return;
}
if (!$account_logged->isLoaded()) {
error('Account not found!');
return;
}
if (!$twoFactorAuth->isActive($twoFactorAuth::TYPE_EMAIL)) {
error("Your account does not have Two Factor E-Mail Authentication enabled.");
return;
}
$twoFactorAuth->disable();
$twoFactorAuth->deleteOldCodes();
$twig->display('success.html.twig',
[
'title' => 'Email Code Authentication Disabled',
'description' => 'You have successfully <strong>disabled</strong> the <b>Email Code Authentication</b> for your account.'
]
);

View File

@@ -1,51 +0,0 @@
<?php
use MyAAC\TwoFactorAuth\TwoFactorAuth;
defined('MYAAC') or die('Direct access not allowed!');
require __DIR__ . '/../base.php';
if ((!setting('core.mail_enabled'))) {
$twig->display('error_box.html.twig', ['errors' => ['Account Two-Factor E-Mail Authentication disabled.']]);
return;
}
if ($twoFactorAuth->isActive()) {
$errors[] = 'Two-factor authentication is already enabled on your account.';
$twig->display('error_box.html.twig', ['errors' => $errors]);
return;
}
if (!$twoFactorAuth->hasRecentEmailCode(15 * 60)) {
$twoFactorAuth->resendEmailCode();
}
if (isset($_POST['save'])) {
if (!empty($code)) {
$twoFactorAuth->setAuthGateway(TwoFactorAuth::TYPE_EMAIL);
if ($twoFactorAuth->getAuthGateway()->verifyCode($code)) {
$serverName = configLua('serverName');
$twoFactorAuth->enable(TwoFactorAuth::TYPE_EMAIL);
$twoFactorAuth->deleteOldCodes();
$twig->display('success.html.twig', [
'title' => 'Email Code Authentication Activated',
'description' => sprintf('You have successfully activated <b>email code authentication</b> for your account. This means an <b>email code</b> will be sent to the email address assigned to your account whenever you try to log in to the %s client or the %s website. In order to log in, you will need to enter the <b>most recent email code</b> you have received.', $serverName, $serverName)
]);
return;
}
else {
$errors[] = 'Invalid email code!';
}
}
}
if (!empty($errors)) {
$twig->display('error_box.html.twig', ['errors' => $errors]);
}
$twig->display('account/2fa/email/enable.html.twig', ['wrongCode' => count($errors) > 0]);

View File

@@ -1,32 +0,0 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
require __DIR__ . '/../base.php';
if ((!setting('core.mail_enabled'))) {
$twig->display('error_box.html.twig', ['errors' => ['Account Two-Factor E-Mail Authentication disabled.']]);
return;
}
if (!$account_logged->isLoaded()) {
error('Account not found!');
return;
}
if ($twoFactorAuth->isActive($twoFactorAuth::TYPE_APP)) {
error('You have to disable the app auth first!');
return;
}
if ($twoFactorAuth->hasRecentEmailCode(30 * 60)) {
$errors = ['Sorry, one email per 30 minutes'];
}
else {
$twoFactorAuth->resendEmailCode();
}
if (!empty($errors)) {
$twig->display('error_box.html.twig', ['errors' => $errors]);
}
$twig->display('account/2fa/email/enable.html.twig');

View File

@@ -17,10 +17,6 @@ if(!$logged)
if(!empty($errors)) if(!empty($errors))
$twig->display('error_box.html.twig', array('errors' => $errors)); $twig->display('error_box.html.twig', array('errors' => $errors));
if (defined('HIDE_LOGIN_BOX') && HIDE_LOGIN_BOX) {
return;
}
$twig->display('account.login.html.twig', array( $twig->display('account.login.html.twig', array(
'redirect' => $_REQUEST['redirect'] ?? null, 'redirect' => $_REQUEST['redirect'] ?? null,
'account' => USE_ACCOUNT_NAME ? 'Name' : 'Number', 'account' => USE_ACCOUNT_NAME ? 'Name' : 'Number',
@@ -34,11 +30,3 @@ if(!$logged)
else { else {
$show_form = true; $show_form = true;
} }
function generateRecoveryKey(): string
{
return generateRandomString(5, false, true, true) . '-' .
generateRandomString(5, false, true, true) . '-' .
generateRandomString(5, false, true, true) . '-' .
generateRandomString(5, false, true, true);
}

View File

@@ -171,7 +171,7 @@ if($save)
} }
if(setting('core.account_create_character_create')) { if(setting('core.account_create_character_create')) {
$character_name = isset($_POST['name']) ? stripslashes(ucwords(strtolower($_POST['name']))) : null; $character_name = isset($_POST['name']) ? trim(stripslashes($_POST['name'])) : null;
$character_sex = isset($_POST['sex']) ? (int)$_POST['sex'] : null; $character_sex = isset($_POST['sex']) ? (int)$_POST['sex'] : null;
$character_vocation = isset($_POST['vocation']) ? (int)$_POST['vocation'] : null; $character_vocation = isset($_POST['vocation']) ? (int)$_POST['vocation'] : null;
$character_town = isset($_POST['town']) ? (int)$_POST['town'] : null; $character_town = isset($_POST['town']) ? (int)$_POST['town'] : null;

View File

@@ -10,7 +10,6 @@
*/ */
use MyAAC\RateLimit; use MyAAC\RateLimit;
use MyAAC\TwoFactorAuth\TwoFactorAuth;
defined('MYAAC') or die('Direct access not allowed!'); defined('MYAAC') or die('Direct access not allowed!');
@@ -53,18 +52,8 @@ if(!empty($login_account) && !empty($login_password))
$errors[] = 'Your account is not verified. Please verify your email address. If the message is not coming check the SPAM folder in your E-Mail client.<br/>' . $errors[] = 'Your account is not verified. Please verify your email address. If the message is not coming check the SPAM folder in your E-Mail client.<br/>' .
'You can resend the Email here: <a href="' . $link . '">' . $link . '</a>'; 'You can resend the Email here: <a href="' . $link . '">' . $link . '</a>';
} else { } else {
setSession('account', $account_logged->getId());
if (!$hooks->trigger(HOOK_ACCOUNT_LOGIN_PRE)) {
return;
}
$twoFactorAuth = TwoFactorAuth::getInstance($account_logged);
if (!$twoFactorAuth->process($login_account, $login_password, $remember_me, $_POST['auth-code'] ?? '')) {
return;
}
session_regenerate_id(); session_regenerate_id();
setSession('account', $account_logged->getId());
setSession('password', encrypt((USE_ACCOUNT_SALT ? $account_logged->getCustomField('salt') : '') . $login_password)); setSession('password', encrypt((USE_ACCOUNT_SALT ? $account_logged->getCustomField('salt') : '') . $login_password));
if($remember_me) { if($remember_me) {
setSession('remember_me', true); setSession('remember_me', true);

View File

@@ -8,9 +8,6 @@
* @copyright 2019 MyAAC * @copyright 2019 MyAAC
* @link https://my-aac.org * @link https://my-aac.org
*/ */
use MyAAC\TwoFactorAuth\TwoFactorAuth;
defined('MYAAC') or die('Direct access not allowed!'); defined('MYAAC') or die('Direct access not allowed!');
$title = 'Account Management'; $title = 'Account Management';
@@ -119,8 +116,6 @@ $twig->display('account.management.html.twig', array(
'account_registered' => $account_registered, 'account_registered' => $account_registered,
'account_rlname' => $account_rlname, 'account_rlname' => $account_rlname,
'account_location' => $account_location, 'account_location' => $account_location,
'twoFactorViews' => TwoFactorAuth::getInstance($account_logged)->getAccountManageViews(),
'actions' => $actions, 'actions' => $actions,
'players' => $account_players, 'players' => $account_players
)); ));

View File

@@ -37,7 +37,7 @@ else
if($points >= setting('core.account_generate_new_reckey_price')) if($points >= setting('core.account_generate_new_reckey_price'))
{ {
$show_form = false; $show_form = false;
$new_rec_key = generateRecoveryKey(); $new_rec_key = generateRandomString(10, false, true, true);
$mailBody = $twig->render('mail.account.register.html.twig', array( $mailBody = $twig->render('mail.account.register.html.twig', array(
'recovery_key' => $new_rec_key 'recovery_key' => $new_rec_key

View File

@@ -27,7 +27,7 @@ if(isset($_POST['registeraccountsave']) && $_POST['registeraccountsave'] == "1")
if($reg_password == $account_logged->getPassword()) { if($reg_password == $account_logged->getPassword()) {
if(empty($old_key)) { if(empty($old_key)) {
$show_form = false; $show_form = false;
$new_rec_key = generateRecoveryKey(); $new_rec_key = generateRandomString(10, false, true, true);
$account_logged->setCustomField("key", $new_rec_key); $account_logged->setCustomField("key", $new_rec_key);
$account_logged->logAction('Generated recovery key.'); $account_logged->logAction('Generated recovery key.');

View File

@@ -11,20 +11,20 @@
defined('MYAAC') or die('Direct access not allowed!'); defined('MYAAC') or die('Direct access not allowed!');
$title = 'Experience Stages'; $title = 'Experience Stages';
if(file_exists($config['data_path'] . 'XML/stages.xml')) { if((!isset($config['lua']['experienceStages']) || !getBoolean($config['lua']['experienceStages']))
$stages = new DOMDocument(); && (!isset($config['lua']['rateUseStages']) || !getBoolean($config['lua']['rateUseStages']))
$stages->load($config['data_path'] . 'XML/stages.xml'); ) {
}
if(!isset($config['lua']['experienceStages']) || !getBoolean($config['lua']['experienceStages']))
{
$enabled = false; $enabled = false;
if(isset($stages)) { if(file_exists($config['data_path'] . 'XML/stages.xml')) {
$stages = new DOMDocument();
$stages->load($config['data_path'] . 'XML/stages.xml');
foreach($stages->getElementsByTagName('config') as $node) { foreach($stages->getElementsByTagName('config') as $node) {
/** @var DOMElement $node */ /** @var DOMElement $node */
if($node->getAttribute('enabled')) if($node->getAttribute('enabled')) {
$enabled = true; $enabled = true;
}
} }
} }
@@ -42,21 +42,12 @@ if(!isset($config['lua']['experienceStages']) || !getBoolean($config['lua']['exp
} }
} }
if(!$stages) $stages = new MyAAC\Server\ExpStages();
{ $stagesArray = $stages->get();
echo 'Error: cannot load <b>stages.xml</b>!';
if (empty($stagesArray)) {
echo 'Error when loading experience stages.';
return; return;
} }
$stagesArray = [];
foreach($stages->getElementsByTagName('stage') as $stage)
{
/** @var DOMElement $stage */
$maxLevel = $stage->getAttribute('maxlevel');
$stagesArray[] = [
'levels' => $stage->getAttribute('minlevel') . (isset($maxLevel[0]) ? '-' . $maxLevel : '+'),
'multiplier' => $stage->getAttribute('multiplier')
];
}
$twig->display('experience_stages.html.twig', ['stages' => $stagesArray]); $twig->display('experience_stages.html.twig', ['stages' => $stagesArray]);

View File

@@ -13,7 +13,7 @@ use MyAAC\Cache\Cache;
use MyAAC\Models\Player; use MyAAC\Models\Player;
use MyAAC\Models\PlayerDeath; use MyAAC\Models\PlayerDeath;
use MyAAC\Models\PlayerKillers; use MyAAC\Models\PlayerKillers;
use MyAAC\Server\XML\Vocations; use MyAAC\Server\Vocations;
defined('MYAAC') or die('Direct access not allowed!'); defined('MYAAC') or die('Direct access not allowed!');
$title = 'Highscores'; $title = 'Highscores';
@@ -207,10 +207,14 @@ if (empty($highscores)) {
} }
$highscores = $query->get()->map(function($row) { $highscores = $query->get()->map(function($row) {
/**
* @var Player $row
*/
$tmp = $row->toArray(); $tmp = $row->toArray();
$tmp['online'] = $row->online_status; $tmp['online'] = $row->online_status;
$tmp['vocation'] = $row->vocation_name; $tmp['vocation'] = $row->vocation_name;
$tmp['outfit_url'] = $row->outfit_url; // @phpstan-ignore-line $tmp['outfit_url'] = $row->outfit_url;
$tmp['link'] = getPlayerLink($row->name, false);
unset($tmp['online_table']); unset($tmp['online_table']);
return $tmp; return $tmp;
@@ -244,7 +248,6 @@ foreach($highscores as $id => &$player)
$player['experience'] = number_format($player['experience']); $player['experience'] = number_format($player['experience']);
} }
$player['link'] = getPlayerLink($player['name'], false);
$player['flag'] = getFlagImage($player['country']); $player['flag'] = getFlagImage($player['country']);
$player['outfit'] = '<img style="position:absolute;margin-top:-50px;margin-left:-30px" src="' . $player['outfit_url'] . '" alt="" />'; $player['outfit'] = '<img style="position:absolute;margin-top:-50px;margin-left:-30px" src="' . $player['outfit_url'] . '" alt="" />';

View File

@@ -12,7 +12,7 @@
use MyAAC\Cache\Cache; use MyAAC\Cache\Cache;
use MyAAC\Models\ServerConfig; use MyAAC\Models\ServerConfig;
use MyAAC\Models\ServerRecord; use MyAAC\Models\ServerRecord;
use MyAAC\Server\XML\Vocations; use MyAAC\Server\Vocations;
defined('MYAAC') or die('Direct access not allowed!'); defined('MYAAC') or die('Direct access not allowed!');
$title = 'Who is online?'; $title = 'Who is online?';
@@ -87,13 +87,16 @@ $cached = Cache::remember("online_$order", setting('core.online_cache_ttl') * 60
'name' => getPlayerLink($player['name']), 'name' => getPlayerLink($player['name']),
'player' => $player, 'player' => $player,
'level' => $player['level'], 'level' => $player['level'],
'vocation' => $configVocations[$player['vocation']], 'vocation' => $configVocations[$player['vocation']] ?? 'Unknown',
'skull' => $skull, 'skull' => $skull,
'country_image' => getFlagImage($player['country']), 'country_image' => getFlagImage($player['country']),
'outfit' => 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'], 'outfit' => 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'],
); );
$vocations[Vocations::getOriginal($player['vocation'])]++; $originalId = Vocations::getOriginal($player['vocation']);
if ($originalId) {
$vocations[$originalId]++;
}
} }
$record = ''; $record = '';

View File

@@ -12,6 +12,7 @@
*/ */
use MyAAC\Cache; use MyAAC\Cache;
use MyAAC\Server\Config;
use MyAAC\Settings; use MyAAC\Settings;
$templates = Cache::remember('templates', 5 * 60, function () { $templates = Cache::remember('templates', 5 * 60, function () {
@@ -1802,8 +1803,8 @@ Sent by MyAAC,<br/>
// test config.lua existence // test config.lua existence
// if fail - revert the setting and inform the user // if fail - revert the setting and inform the user
if (!file_exists($server_path . 'config.lua')) { if (!Config::exists()) {
error('Server Path is invalid - cannot find config.lua in the directory. Setting have been reverted.'); error('Server Path is invalid - cannot find config.lua or config/server.toml in the directory. Setting have been reverted.');
$configToSave['server_path'] = $configOriginal['server_path']; $configToSave['server_path'] = $configOriginal['server_path'];
} }

View File

@@ -2,6 +2,7 @@
namespace MyAAC\Commands; namespace MyAAC\Commands;
use MyAAC\Server\Config;
use POT; use POT;
trait Env trait Env
@@ -21,7 +22,7 @@ trait Env
if($config['server_path'][strlen($config['server_path']) - 1] !== '/') if($config['server_path'][strlen($config['server_path']) - 1] !== '/')
$config['server_path'] .= '/'; $config['server_path'] .= '/';
$config['lua'] = load_config_lua($config['server_path'] . 'config.lua'); $config['server'] = $config['lua'] = Config::get();
// POT // POT
require_once SYSTEM . 'libs/pot/OTS.php'; require_once SYSTEM . 'libs/pot/OTS.php';

View File

@@ -39,7 +39,7 @@ class GiveAdminCommand extends Command
} }
if (!$account->isLoaded()) { if (!$account->isLoaded()) {
$io->error('Cannot find account mit supplied parameter: ' . $accountParam); $io->error('Cannot find account with supplied parameter: ' . $accountParam);
return self::FAILURE; return self::FAILURE;
} }

View File

@@ -27,6 +27,7 @@ namespace MyAAC;
use MyAAC\Cache\Cache; use MyAAC\Cache\Cache;
use MyAAC\Models\Town; use MyAAC\Models\Town;
use MyAAC\Server\Items;
class DataLoader class DataLoader
{ {
@@ -40,7 +41,7 @@ class DataLoader
{ {
self::$startTime = microtime(true); self::$startTime = microtime(true);
if(Items::loadFromXML()) { if(Items::load()) {
success(self::$locale['step_database_loaded_items'] . self::getLoadedTime()); success(self::$locale['step_database_loaded_items'] . self::getLoadedTime());
} }
else { else {

View File

@@ -0,0 +1,52 @@
<?php
namespace MyAAC\Debug;
use DebugBar\DataCollector\PDO\PDOCollector;
use DebugBar\DataCollector\PDO\TraceablePDO;
use DebugBar\DataCollector\TimeDataCollector;
class PDOCollectorWithBacktrace extends PDOCollector
{
protected function collectPDO(TraceablePDO $pdo, ?TimeDataCollector $timeCollector = null, $connectionName = null): array
{
$data = parent::collectPDO($pdo, $timeCollector, $connectionName);
if ($pdo instanceof TraceablePDOWithBacktrace) {
$backtraces = $pdo->getBacktraces();
foreach ($data['statements'] as $i => &$stmt) {
if (isset($backtraces[$i])) {
$stmt['backtrace'] = $this->formatBacktrace($backtraces[$i]);
}
}
unset($stmt);
}
return $data;
}
private function formatBacktrace(array $backtrace): array
{
$result = [];
foreach ($backtrace as $frame) {
if (!isset($frame['file'], $frame['line'])) {
continue;
}
if (str_contains($frame['file'], DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR)) {
continue;
}
if (str_contains($frame['file'], DIRECTORY_SEPARATOR . 'Debug' . DIRECTORY_SEPARATOR)) {
continue;
}
$function = isset($frame['class'])
? $frame['class'] . ($frame['type'] ?? '::') . ($frame['function'] ?? '')
: ($frame['function'] ?? '');
$result[] = ($function ? $function . '() ' : '') . $frame['file'] . ':' . $frame['line'];
}
return $result;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace MyAAC\Debug;
use DebugBar\DataCollector\PDO\TraceablePDO;
use DebugBar\DataCollector\PDO\TracedStatement;
class TraceablePDOWithBacktrace extends TraceablePDO
{
/** @var array[] */
protected array $backtraces = [];
public function addExecutedStatement(TracedStatement $stmt): void
{
$this->backtraces[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
parent::addExecutedStatement($stmt);
}
/**
* @return array[]
*/
public function getBacktraces(): array
{
return $this->backtraces;
}
}

View File

@@ -14,6 +14,26 @@ class Hooks
self::$_hooks[$hook->type()][] = $hook; self::$_hooks[$hook->type()][] = $hook;
} }
public function unregister($name, $type, $file): void
{
if (is_string($type)) {
$type = constant($type);
}
if(!isset(self::$_hooks[$type])) {
return;
}
foreach(self::$_hooks[$type] as $id => $hook) {
if($name == $hook->name()
&& $type == $hook->type()
&& $file == $hook->file()
) {
unset(self::$_hooks[$type][$id]);
}
}
}
public function trigger($type, $params = []): bool public function trigger($type, $params = []): bool
{ {
$ret = true; $ret = true;

View File

@@ -1,172 +1,14 @@
<?php <?php
/** /**
* Items class * @deprecated
* * This class is deprecated and will be removed in future versions. Please use the appropriate MyAAC\Server\Items class instead.
* @package MyAAC
* @author Gesior <jerzyskalski@wp.pl>
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/ */
namespace MyAAC; namespace MyAAC;
use MyAAC\Cache\PHP as CachePHP; class Items extends Server\Items
use MyAAC\Models\Spell;
class Items
{ {
private static $error = ''; public static function load(): bool {
public static $items; parent::init();
public static function loadFromXML($show = false)
{
$file_path = config('data_path') . 'items/items.xml';
if (!file_exists($file_path)) {
self::$error = 'Cannot load file ' . $file_path;
return false;
}
$xml = new \DOMDocument;
$xml->load($file_path);
$items = array();
foreach ($xml->getElementsByTagName('item') as $item) {
if ($item->getAttribute('fromid')) {
for ($id = $item->getAttribute('fromid'); $id <= $item->getAttribute('toid'); $id++) {
$tmp = self::parseNode($id, $item, $show);
$items[$tmp['id']] = $tmp['content'];
}
} else {
$tmp = self::parseNode($item->getAttribute('id'), $item, $show);
$items[$tmp['id']] = $tmp['content'];
}
}
$cache_php = new CachePHP(config('cache_prefix'), CACHE . 'persistent/');
$cache_php->set('items', $items, 5 * 365 * 24 * 60 * 60);
return true; return true;
} }
public static function parseNode($id, $node, $show = false) {
$name = $node->getAttribute('name');
$article = $node->getAttribute('article');
$plural = $node->getAttribute('plural');
$attributes = array();
foreach($node->getElementsByTagName('attribute') as $attr) {
$attributes[strtolower($attr->getAttribute('key'))] = $attr->getAttribute('value');
}
return array('id' => $id, 'content' => array('article' => $article, 'name' => $name, 'plural' => $plural, 'attributes' => $attributes));
}
public static function getError() {
return self::$error;
}
public static function load() {
if(self::$items) {
return;
}
$cache_php = new CachePHP(config('cache_prefix'), CACHE . 'persistent/');
self::$items = $cache_php->get('items');
}
public static function get($id) {
self::load();
return self::$items[$id] ?? [];
}
public static function getDescription($id, $count = 1): string
{
$item = self::get($id);
$attr = $item['attributes'];
$s = '';
if(!empty($item['name'])) {
if($count > 1) {
if($attr['showcount']) {
$s .= $count . ' ';
}
if(!empty($item['plural'])) {
$s .= $item['plural'];
}
else if((int)$attr['showcount'] == 0) {
$s .= $item['name'];
}
else {
$s .= $item['name'] . 's';
}
}
else {
if(!empty($item['aticle'])) {
$s .= $item['article'] . ' ';
}
$s .= $item['name'];
}
}
else
$s .= 'an item of type ' . $item['id'];
if(isset($attr['type']) && strtolower($attr['type']) == 'rune') {
$spell = Spell::where('item_id', $id)->first();
if($spell) {
if($spell->level > 0 && $spell->maglevel > 0) {
$s .= '. ' . ($count > 1 ? 'They' : 'It') . ' can only be used by ';
}
$configVocations = config('vocations');
if(!empty(trim($spell->vocations))) {
$vocations = json_decode($spell->vocations);
if(count($vocations) > 0) {
foreach($vocations as $voc => $show) {
$vocations[$configVocations[$voc]] = $show;
}
}
}
else {
$s .= 'players';
}
$s .= ' with';
if ($spell->level > 0) {
$s .= ' level ' . $spell->level;
}
if ($spell->maglevel > 0) {
if ($spell->level > 0) {
$s .= ' and';
}
$s .= ' magic level ' . $spell->maglevel;
}
$s .= ' or higher';
}
}
if (!empty($item['weaponType'])) {
if ($item['weaponType'] == 'distance' && isset($item['ammoType'])) {
$s .= ' (Range:' . $item['range'];
}
if (isset($item['attack']) && $item['attack'] != 0) {
$s .= ', Atk ' . ($item['attack'] > 0 ? '+' . $item['attack'] : '-' . $item['attack']);
}
if (isset($item['hitChance']) && $item['hitChance'] != -1) {
$s .= ', Hit% ' . ($item['hitChance'] > 0 ? '+' . $item['hitChance'] : '-' . $item['hitChance']);
}
elseif ($item['weaponType'] != 'ammo') {
}
}
return $s;
}
} }

View File

@@ -18,6 +18,15 @@ class Account extends Model {
public $timestamps = false; public $timestamps = false;
protected $fillable = [
'name', 'number', 'email', 'password',
'key', 'created', 'rlname', 'location', 'country',
'web_lastlogin', 'web_flags',
'email_new', 'email_new_time', 'email_code',
'premium_points', 'coins', 'coins_transferable',
'premium_ends_at', 'premend', 'lastday', 'premdays',
];
protected $casts = [ protected $casts = [
'lastday' => 'integer', 'lastday' => 'integer',
'premdays' => 'integer', 'premdays' => 'integer',

View File

@@ -1,14 +0,0 @@
<?php
namespace MyAAC\Models;
use Illuminate\Database\Eloquent\Model;
class AccountEMailCode extends Model {
protected $table = TABLE_PREFIX . 'account_email_codes';
public $timestamps = false;
protected $fillable = ['account_id', 'code', 'created_at'];
}

View File

@@ -1,15 +0,0 @@
<?php
namespace MyAAC\Models;
use Illuminate\Database\Eloquent\Model;
class BugTracker extends Model {
protected $table = TABLE_PREFIX . 'bugtracker';
public $timestamps = false;
protected $fillable = ['account', 'type', 'status', 'text', 'id', 'subject', 'reply', 'who', 'uid', 'tag'];
}

View File

@@ -5,8 +5,10 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOne;
/** /**
* @property string $name
* @property int $level * @property int $level
* @property int $vocation * @property int $vocation
* @property int $promotion
* @property int $online * @property int $online
* @property int $looktype * @property int $looktype
* @property int $lookhead * @property int $lookhead
@@ -14,6 +16,8 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
* @property int $looklegs * @property int $looklegs
* @property int $lookfeet * @property int $lookfeet
* @property int $lookaddons * @property int $lookaddons
* @property bool $online_status
* @property string $vocation_name
* @property string $outfit_url * @property string $outfit_url
* @property hasOne $onlineTable * @property hasOne $onlineTable
*/ */

View File

@@ -12,13 +12,11 @@
namespace MyAAC; namespace MyAAC;
use MyAAC\Models\Monster; use MyAAC\Models\Monster;
use MyAAC\Server\Items;
class Monsters { class Monsters {
/** private static \OTS_MonstersList $monstersList;
* @var \OTS_MonstersList private static string $lastError = '';
*/
private static $monstersList;
private static $lastError = '';
public static function loadFromXML($show = false) { public static function loadFromXML($show = false) {
try { try {
@@ -39,7 +37,7 @@ class Monsters {
} }
$items = array(); $items = array();
Items::load(); Items::init();
foreach((array)Items::$items as $id => $item) { foreach((array)Items::$items as $id => $item) {
$items[$item['name']] = $id; $items[$item['name']] = $id;
} }

View File

@@ -7,9 +7,11 @@ use MyAAC\Cache\Cache;
use MyAAC\Models\Menu; use MyAAC\Models\Menu;
class Plugins { class Plugins {
private static $warnings = []; private static array $warnings = [];
private static $error = null; private static string $error = '';
private static $plugin_json = []; private static array $plugin_json = [];
const DEFAULT_PRIORITY = 1000;
public static function getInits() public static function getInits()
{ {
@@ -20,13 +22,31 @@ class Plugins {
continue; continue;
} }
$initPriority = self::DEFAULT_PRIORITY;
if (isset($plugin['autoload']['init-priority'])) {
$initPriority = (int) $plugin['autoload']['init-priority'];
}
$pluginInits = glob(PLUGINS . $plugin['filename'] . '/init.php'); $pluginInits = glob(PLUGINS . $plugin['filename'] . '/init.php');
foreach ($pluginInits as $path) { foreach ($pluginInits as $path) {
$inits[] = $path; $inits[] = [
'file' => $path,
'priority' => $initPriority
];
} }
} }
return $inits; usort($inits, function ($a, $b)
{
return $a['priority'] <=> $b['priority'];
});
$ret = [];
foreach ($inits as $init) {
$ret[] = $init['file'];
}
return $ret;
}); });
} }
@@ -39,7 +59,7 @@ class Plugins {
continue; continue;
} }
$adminPagesDefaultPriority = 1000; $adminPagesDefaultPriority = self::DEFAULT_PRIORITY;
if (isset($plugin['admin-pages-default-priority'])) { if (isset($plugin['admin-pages-default-priority'])) {
$adminPagesDefaultPriority = $plugin['admin-pages-default-priority']; $adminPagesDefaultPriority = $plugin['admin-pages-default-priority'];
} }
@@ -117,7 +137,7 @@ class Plugins {
$routes = []; $routes = [];
foreach(self::getAllPluginsJson() as $plugin) { foreach(self::getAllPluginsJson() as $plugin) {
$routesDefaultPriority = 1000; $routesDefaultPriority = self::DEFAULT_PRIORITY;
if (isset($plugin['routes-default-priority'])) { if (isset($plugin['routes-default-priority'])) {
$routesDefaultPriority = $plugin['routes-default-priority']; $routesDefaultPriority = $plugin['routes-default-priority'];
} }
@@ -165,7 +185,7 @@ class Plugins {
} }
} }
$pagesDefaultPriority = 1000; $pagesDefaultPriority = self::DEFAULT_PRIORITY;
if (isset($plugin['pages-default-priority'])) { if (isset($plugin['pages-default-priority'])) {
$pagesDefaultPriority = $plugin['pages-default-priority']; $pagesDefaultPriority = $plugin['pages-default-priority'];
} }
@@ -318,7 +338,7 @@ class Plugins {
foreach(self::getAllPluginsJson() as $plugin) { foreach(self::getAllPluginsJson() as $plugin) {
if (isset($plugin['hooks'])) { if (isset($plugin['hooks'])) {
foreach ($plugin['hooks'] as $_name => $info) { foreach ($plugin['hooks'] as $_name => $info) {
$priority = 1000; $priority = self::DEFAULT_PRIORITY;
if (str_contains($info['type'], 'HOOK_')) { if (str_contains($info['type'], 'HOOK_')) {
$info['type'] = str_replace('HOOK_', '', $info['type']); $info['type'] = str_replace('HOOK_', '', $info['type']);
@@ -432,7 +452,7 @@ class Plugins {
return $plugins; return $plugins;
} }
public static function getPluginSettings($filename) public static function getPluginSettings($filename): mixed
{ {
$plugin_json = self::getPluginJson($filename); $plugin_json = self::getPluginJson($filename);
if (!$plugin_json) { if (!$plugin_json) {
@@ -868,6 +888,15 @@ class Plugins {
} }
} }
global $hooks;
foreach($plugin_info['hooks'] ?? [] as $name => $info) {
if (str_contains($info['type'], 'HOOK_')) {
$info['type'] = str_replace('HOOK_', '', $info['type']);
}
$hooks->unregister($name, 'HOOK_' . $info['type'], $info['file']);
}
clearCache(); clearCache();
return true; return true;
} }
@@ -892,15 +921,15 @@ class Plugins {
return Semver::satisfies($plugin_info['version'], $version); return Semver::satisfies($plugin_info['version'], $version);
} }
public static function getWarnings() { public static function getWarnings(): array {
return self::$warnings; return self::$warnings;
} }
public static function clearWarnings() { public static function clearWarnings(): void {
self::$warnings = []; self::$warnings = [];
} }
public static function getError() { public static function getError(): string {
return self::$error; return self::$error;
} }
@@ -911,7 +940,7 @@ class Plugins {
* @param string $templateName * @param string $templateName
* @param array $menus * @param array $menus
*/ */
public static function installMenus($templateName, $menus, $clearOld = false) public static function installMenus($templateName, $menus, $clearOld = false): void
{ {
global $db; global $db;
@@ -962,7 +991,7 @@ class Plugins {
} }
} }
private static function getAutoLoadOption(array $plugin, string $optionName, bool $default = true) private static function getAutoLoadOption(array $plugin, string $optionName, bool $default = true): bool
{ {
if (isset($plugin['autoload'])) { if (isset($plugin['autoload'])) {
$autoload = $plugin['autoload']; $autoload = $plugin['autoload'];
@@ -971,7 +1000,7 @@ class Plugins {
return getBoolean($autoload[$optionName]); return getBoolean($autoload[$optionName]);
} }
} }
else if (is_bool($autoload)) { elseif (is_bool($autoload)) {
return $autoload; return $autoload;
} }
} }

View File

@@ -0,0 +1,28 @@
<?php
namespace MyAAC\Server;
use MyAAC\Cache\Cache;
class Config
{
public static function get()
{
return Cache::remember('config_server', 10 * 60, function () {
if (file_exists(config('server_path') . Lua\Config::FILE)) {
$config = new Lua\Config();
}
else {
$config = new TOML\Config();
}
$config->load();
return $config->get();
});
}
public static function exists(): bool {
return file_exists(config('server_path') . Lua\Config::FILE) || file_exists(config('server_path') . 'config/server.toml');
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace MyAAC\Server;
use MyAAC\Cache\Cache;
class ExpStages
{
private static array $stages = [];
public static function get() {
if (count(self::$stages) == 0) {
self::$stages = Cache::remember('exp_stages', 10 * 60, function () {
if (file_exists(config('server_path') . TOML\ExpStages::FILE)) {
$expStages = new TOML\ExpStages();
}
elseif (file_exists(config('data_path') . XML\ExpStages::FILE)) {
$expStages = new XML\ExpStages();
}
elseif (file_exists(config('data_path') . Lua\ExpStages::FILE)) {
$expStages = new Lua\ExpStages();
}
else {
return [];
}
$expStages->load();
return $expStages->get();
});
}
return self::$stages;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace MyAAC\Server;
use MyAAC\Cache\Cache;
class Groups
{
private static array $groups = [];
public static function get() {
if (count(self::$groups) == 0) {
self::$groups = Cache::remember('groups', 10 * 60, function () {
if (file_exists(config('server_path') . TOML\Groups::FILE)) {
$groups = new TOML\Groups();
}
else {
$groups = new XML\Groups();
}
$groups->load();
return $groups->get();
});
}
return self::$groups;
}
}

154
system/src/Server/Items.php Normal file
View File

@@ -0,0 +1,154 @@
<?php
/**
* Items class
*
* @package MyAAC
* @author Gesior <jerzyskalski@wp.pl>
* @author Slawkens <slawkens@gmail.com>
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
namespace MyAAC\Server;
use MyAAC\Cache\PHP as CachePHP;
use MyAAC\Models\Spell;
class Items
{
public static array $items = [];
private static string $error = '';
const FILE_ITEMS_TOML = 'items/items.toml';
const FILE_ITEMS_XML = 'items/items.xml';
public static function getError(): string {
return self::$error;
}
public static function load(): bool {
if (file_exists(config('data_path') . self::FILE_ITEMS_TOML)) {
$items = new TOML\Items();
}
elseif (file_exists(config('data_path') . self::FILE_ITEMS_XML)) {
$items = new XML\Items();
}
else {
self::$error = 'Cannot load items.xml or items.toml file. Files not found.';
return false;
}
if (!$items->load()) {
self::$error = $items->getError();
return false;
}
return true;
}
public static function init(): void {
if(count(self::$items) > 0) {
return;
}
$cache_php = new CachePHP(config('cache_prefix'), CACHE . 'persistent/');
self::$items = (array)$cache_php->get('items');
}
public static function get($id) {
self::init();
return self::$items[$id] ?? [];
}
public static function getDescription($id, $count = 1): string
{
$item = self::get($id);
$attr = $item['attributes'];
$s = '';
if(!empty($item['name'])) {
if($count > 1) {
if($attr['showcount']) {
$s .= $count . ' ';
}
if(!empty($item['plural'])) {
$s .= $item['plural'];
}
else if((int)$attr['showcount'] == 0) {
$s .= $item['name'];
}
else {
$s .= $item['name'] . 's';
}
}
else {
if(!empty($item['aticle'])) {
$s .= $item['article'] . ' ';
}
$s .= $item['name'];
}
}
else
$s .= 'an item of type ' . $item['id'];
if(isset($attr['type']) && strtolower($attr['type']) == 'rune') {
$spell = Spell::where('item_id', $id)->first();
if($spell) {
if($spell->level > 0 && $spell->maglevel > 0) {
$s .= '. ' . ($count > 1 ? 'They' : 'It') . ' can only be used by ';
}
$configVocations = config('vocations');
if(!empty(trim($spell->vocations))) {
$vocations = json_decode($spell->vocations);
if(count($vocations) > 0) {
foreach($vocations as $voc => $show) {
$vocations[$configVocations[$voc]] = $show;
}
}
}
else {
$s .= 'players';
}
$s .= ' with';
if ($spell->level > 0) {
$s .= ' level ' . $spell->level;
}
if ($spell->maglevel > 0) {
if ($spell->level > 0) {
$s .= ' and';
}
$s .= ' magic level ' . $spell->maglevel;
}
$s .= ' or higher';
}
}
if (!empty($item['weaponType'])) {
if ($item['weaponType'] == 'distance' && isset($item['ammoType'])) {
$s .= ' (Range:' . $item['range'];
}
if (isset($item['attack']) && $item['attack'] != 0) {
$s .= ', Atk ' . ($item['attack'] > 0 ? '+' . $item['attack'] : '-' . $item['attack']);
}
if (isset($item['hitChance']) && $item['hitChance'] != -1) {
$s .= ', Hit% ' . ($item['hitChance'] > 0 ? '+' . $item['hitChance'] : '-' . $item['hitChance']);
}
elseif ($item['weaponType'] != 'ammo') {
}
}
return $s;
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace MyAAC\Server\Lua;
class Config
{
const FILE = 'config.lua';
private array $config = [];
public function load(): void
{
$file = config('server_path') . self::FILE;
$this->config = Loader::load($file);
if($this->config === false) {
log_append('error.log', '[Config] Fatal error: Cannot load config.lua (' . $file . ').');
throw new \RuntimeException('ERROR: Cannot find ' . $file . ' file.');
}
}
public function get(): array {
return $this->config;
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace MyAAC\Server\Lua;
class ExpStages
{
private array $stages = [];
const FILE = 'stages.lua';
public function load(): void
{
$file = config('data_path') . self::FILE;
if(!@file_exists($file)) {
return;
}
if (!extension_loaded('lua')) {
return;
}
$lua = new \Lua();
try {
$stagesContent = file_get_contents($file);
$stagesContent .= 'return experienceStages';
$stages = $lua->eval($stagesContent);
}
catch (\Exception $e) {
error('Error: Cannot load stages.lua. More info in system/logs/error.log file.');
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load stages.lua - $file. Error: " . $e->getMessage());
return;
}
foreach ($stages as $stage) {
$this->stages[] = [
'levels' => $stage['minlevel'] . (isset($stage['maxlevel']) ? '-' . $stage['maxlevel'] : '+'),
'multiplier' => $stage['multiplier']
];
}
}
public function get(): array {
return $this->stages;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace MyAAC\Server\Lua;
class Loader
{
public static function load($file): bool|array
{
if(!@file_exists($file)){
return false;
}
$result = [];
$config_string = str_replace(array("\r\n", "\r"), "\n", file_get_contents($file));
$lines = explode("\n", $config_string);
if(count($lines) > 0) {
foreach($lines as $ln => $line) {
$line = trim($line);
if(isset($line[0]) && ($line[0] === '{' || $line[0] === '}')) {
// arrays are not supported yet
// just ignore the error
continue;
}
$tmp_exp = explode('=', $line, 2);
if(str_contains($line, 'dofile')) {
$delimiter = '"';
if(!str_contains($line, $delimiter)) {
$delimiter = "'";
}
$tmp = explode($delimiter, $line);
$result = array_merge($result, self::load(config('server_path') . $tmp[1]));
}
else if(count($tmp_exp) >= 2) {
$key = trim($tmp_exp[0]);
if(!str_starts_with($key, '--')) {
$value = trim($tmp_exp[1]);
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] = substr(substr($value, 1), 0, -1);
elseif(in_array($value, array('true', 'false')))
$result[$key] = $value === 'true';
elseif(@$value[0] === '{') {
// arrays are not supported yet
// just ignore the error
continue;
}
else
{
foreach($result as $tmp_key => $tmp_value) { // load values defined by other keys, like: dailyFragsToBlackSkull = dailyFragsToRedSkull
$value = str_replace($tmp_key, $tmp_value, $value);
}
try {
$ret = eval("return $value;");
}
catch (\Throwable $e) {
throw new \RuntimeException('ERROR: Loading config.lua file. Line: ' . ($ln + 1) . ' - Unable to parse value "' . $value . '" - ' . $e->getMessage());
}
if((string) $ret == '' && trim($value) !== '""') {
throw new \RuntimeException('ERROR: Loading config.lua file. Line ' . ($ln + 1) . ' is not valid [key: ' . $key . ']');
}
$result[$key] = $ret;
}
}
}
}
}
return $result;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace MyAAC\Server;
use MyAAC\Cache\Cache;
class Mounts
{
private static array $mounts = [];
public static function get()
{
if (count(self::$mounts) == 0) {
self::$mounts = Cache::remember('mounts', 10 * 60, function () {
if (file_exists(config('server_path') . TOML\Mounts::FILE)) {
$mounts = new TOML\Mounts();
}
else {
$mounts = new XML\Mounts();
}
$mounts->load();
return $mounts->get();
});
}
return self::$mounts;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace MyAAC\Server;
use MyAAC\Cache\Cache;
class Outfits
{
private static array $outfits = [];
public static function get()
{
if (count(self::$outfits) == 0) {
self::$outfits = Cache::remember('outfits', 10 * 60, function () {
if (file_exists(config('server_path') . TOML\Outfits::FILE)) {
$outfits = new TOML\Outfits();
} else {
$outfits = new XML\Outfits();
}
$outfits->load();
return $outfits->get();
});
}
return self::$outfits;
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace MyAAC\Server\TOML;
use Devium\Toml\Toml;
use RuntimeException;
class Config
{
private array $config = [];
public function load(): void
{
$path = config('server_path') . 'config/';
$files = glob($path . '*.toml');
// filter files we don't need
$ignore = ['account_manager', 'groups', 'mounts', 'object_pools', 'outfits', 'scripts'];
$files = array_filter($files, function ($file) use ($ignore) {
foreach ($ignore as $item) {
if (str_contains($file, $item)) {
return false;
}
}
return true;
});
foreach ($files as $file) {
$key = basename($file, '.toml');
$toml = file_get_contents($file);
try {
$this->config[$key] = Toml::decode($toml, asArray: true);
}
catch (\Exception $e) {
throw new RuntimeException("Error: Cannot load config/$key.toml. More info in system/logs/error.log file.");
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load config/$key.toml - $file. Error: " . $e->getMessage());
return;
}
}
$this->init();
}
private function init(): void
{
$this->config['serverName'] = $this->config['server']['identity']['name'] ?? 'Unknown';
$this->config['freePremium'] = $this->config['server']['accounts']['free_premium'] ?? false;
$this->config['ip'] = $this->config['server']['network']['ip'] ?? '127.0.0.1';
$this->config['worldType'] = $this->config['server']['world']['type'] ?? 'unknown';
$this->config['experienceStages'] = $this->config['stages']['config']['enabled'] ?? false;
$this->config['houseRentPeriod'] = $this->config['server']['houses']['rent_period'] ?? 'never';
$this->config['pzLocked'] = $this->config['combat']['skull']['pz_locked'] ?? 60 * 1000;
$this->config['url'] = $this->config['server']['identity']['url'] ?? 'http://localhost';
$this->config['protectionLevel'] = $this->config['server']['pvp']['protection_level'] ?? 0;
$this->config['rateExp'] = $this->config['rates']['rates']['experience'] ?? 1;
$this->config['rateMagic'] = $this->config['rates']['rates']['magic'] ?? 1;
$this->config['rateSkill'] = $this->config['rates']['rates']['skill'] ?? 1;
$this->config['rateLoot'] = $this->config['rates']['rates']['loot'] ?? 1;
$this->config['rateSpawn'] = $this->config['rates']['rates']['spawn'] ?? 1;
}
public function get(): array {
return $this->config;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace MyAAC\Server\TOML;
use Devium\Toml\Toml;
class ExpStages
{
private array $stages = [];
const FILE = 'config/stages.toml';
public function load(): void
{
$file = config('server_path') . self::FILE;
if(!@file_exists($file)) {
return;
}
$toml = file_get_contents($file);
try {
$stages = Toml::decode($toml, asArray: true);
}
catch (\Exception $e) {
error('Error: Cannot load stages.toml. More info in system/logs/error.log file.');
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load stages.toml - $file. Error: " . $e->getMessage());
return;
}
foreach ($stages['stage'] as $stage) {
$this->stages[] = [
'levels' => $stage['minlevel'] . (isset($stage['maxlevel']) ? '-' . $stage['maxlevel'] : '+'),
'multiplier' => $stage['multiplier']
];
}
}
public function get(): array {
return $this->stages;
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace MyAAC\Server\TOML;
use Devium\Toml\Toml;
class Groups
{
private array $groups = [];
const FILE = 'config/groups.toml';
public function load(): void
{
$file = config('server_path') . self::FILE;
if(!@file_exists($file)) {
error('Error: Cannot load groups.toml. More info in system/logs/error.log file.');
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load groups.toml - $file. It doesn't exist.");
return;
}
$toml = file_get_contents($file);
try {
$groups = Toml::decode($toml, asArray: true);
}
catch (\Exception $e) {
error('Error: Cannot load groups.toml. More info in system/logs/error.log file.');
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load groups.toml - $file. Error: " . $e->getMessage());
return;
}
foreach ($groups as $group)
{
$this->groups[$group['id']] = [
'id' => $group['id'],
'name' => $group['name'],
'access' => $group['access'],
];
}
}
public function get(): array {
return $this->groups;
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace MyAAC\Server\TOML;
use MyAAC\Cache\PHP as CachePHP;
class Items
{
private string $error = '';
public function getError(): string {
return $this->error;
}
public function load(): bool
{
$file = config('data_path') . 'items/items.toml';
if (!file_exists($file)) {
$this->error = 'Cannot load file ' . $file;
return false;
}
//$toml = file_get_contents($file);
//$items = \Devium\Toml\Toml::decode($toml, asArray: false);
$itemsParser = new ItemsParser();
$itemsParsed = $itemsParser->parse($file);
$items = [];
foreach ($itemsParsed as $item) {
$attributes = array_filter($item, function ($key) {
return !in_array($key, ['id', 'article', 'name', 'plural']);
}, ARRAY_FILTER_USE_KEY);
$id = $item['id'] ?? null;
if ($id === null) {
continue;
}
$items[$id] = [
'article' => $item['article'] ?? '',
'name' => $item['name'] ?? '',
'plural' => $item['plural'] ?? '',
'attributes' => $attributes,
];
}
$cache_php = new CachePHP(config('cache_prefix'), CACHE . 'persistent/');
$cache_php->set('items', $items, 5 * 365 * 24 * 60 * 60);
return true;
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace MyAAC\Server\TOML;
class ItemsParser
{
public function parse(string $path): array
{
$ret = [];
$i = 0;
$handle = fopen($path, 'r');
if ($handle === false) {
throw new \RuntimeException('Failed to open items file: ' . $path);
}
$parse = '';
while (($line = fgets($handle)) !== false) {
if (str_contains($line, '[[items]]') && $i++ != 0) {
//global $whoopsHandler;
//$whoopsHandler->addDataTable('ini', [$parse]);
$ret[] = parse_ini_string($parse);
$parse = '';
continue;
}
// skip lines like this
// field = {type = "fire", initdamage = 20, ticks = 10000, count = 7, damage = 10}
// as it cannot be parsed by parse_ini_string
if (str_starts_with(ltrim($line), 'field =')) {
continue;
}
$parse .= $line;
}
if ($parse !== '') {
$ret[] = parse_ini_string($parse);
}
fclose($handle);
return $ret;
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace MyAAC\Server\TOML;
use Devium\Toml\Toml;
class Mounts
{
private array $mounts = [];
const FILE = 'config/mounts.toml';
public function load(): void
{
$file = config('server_path') . self::FILE;
if(!@file_exists($file)) {
return;
}
$toml = file_get_contents($file);
try {
$mounts = Toml::decode($toml, asArray: true);
}
catch (\Exception $e) {
error('Error: Cannot load mounts.toml. More info in system/logs/error.log file.');
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load mounts.toml - $file. Error: " . $e->getMessage());
return;
}
foreach ($mounts as $name => $mount)
{
$this->mounts[] = [
'id' => $mount['id'],
'client_id' => $mount['clientid'] ?? false,
'name' => $name,
'speed' => $mount['speed'] ?? 0,
'premium' => $mount['premium'] ?? false,
];
}
}
public function get(): array {
return $this->mounts;
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace MyAAC\Server\TOML;
use Devium\Toml\Toml;
class Outfits
{
private array $outfits = [];
const FILE = 'config/outfits.toml';
public function load(): void
{
$file = config('server_path') . self::FILE;
if(!@file_exists($file)) {
return;
}
$toml = file_get_contents($file);
try {
$outfits = Toml::decode($toml, asArray: true);
}
catch (\Exception $e) {
error('Error: Cannot load outfits.toml. More info in system/logs/error.log file.');
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load outfits.toml - $file. Error: " . $e->getMessage());
return;
}
foreach ($outfits as $outfit)
{
$this->outfits[] = [
'id' => $outfit['id'],
'sex' => ($outfit['sex'] == 'male' ? SEX_MALE : SEX_FEMALE),
'name' => $outfit['name'],
'premium' => $outfit['premium'] ?? false,
'locked' => $outfit['locked'] ?? false,
'enabled' => $outfit['enabled'] ?? true,
];
}
}
public function get(): array {
return $this->outfits;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace MyAAC\Server\TOML;
use Devium\Toml\Toml;
class Vocations
{
private array $vocations = [];
private array $vocationsFrom = [];
public function load(): void
{
$tomlVocations = glob(config('data_path') . 'vocations/*.toml');
if (count($tomlVocations) <= 0) {
throw new \RuntimeException('ERROR: Cannot load any .toml vocation from the data/vocations folder.');
}
foreach ($tomlVocations as $file) {
$toml = file_get_contents($file);
try {
$vocations = Toml::decode($toml, asArray: true);
}
catch (\Exception $e) {
$basename = basename($file);
error("Error: Cannot load vocations/$basename. More info in system/logs/error.log file.");
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load mounts.toml - $file. Error: " . $e->getMessage());
break;
}
foreach ($vocations as $vocationArray) {
$id = $vocationArray['id'];
$this->vocations[$id] = $vocationArray['name'];
$this->vocationsFrom[$id] = $vocationArray['promotedfrom'];
}
}
ksort($this->vocations, SORT_NUMERIC);
ksort($this->vocationsFrom, SORT_NUMERIC);
}
public function get(): array {
return $this->vocations;
}
public function getFrom(): array {
return $this->vocationsFrom;
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace MyAAC\Server;
use MyAAC\Cache\Cache;
class Vocations
{
private static array $vocations = [];
private static array $vocationsFrom = [];
public function __construct() {
$cached = Cache::remember('vocations', 10 * 60, function () {
$tomlVocations = glob(config('data_path') . 'vocations/*.toml');
if (count($tomlVocations) > 0) {
$vocations = new TOML\Vocations();
}
else {
$vocations = new XML\Vocations();
}
$vocations->load();
$from = $vocations->getFrom();
$amount = 0;
foreach ($from as $vocId => $fromVocation) {
if ($vocId != 0 && $vocId == $fromVocation) {
$amount++;
}
}
return ['vocations' => $vocations->get(), 'vocationsFrom' => $from, 'amount' => $amount];
});
self::$vocations = $cached['vocations'];
self::$vocationsFrom = $cached['vocationsFrom'];
config(['vocations', self::$vocations]);
config(['vocations_amount', $cached['amount']]);
}
public static function get(): array {
return self::$vocations;
}
public static function getFrom(): array {
return self::$vocationsFrom;
}
public static function getPromoted(int $id): ?int {
foreach (self::$vocationsFrom as $vocId => $fromVocation) {
if ($id == $fromVocation && $vocId != $id) {
return $vocId;
}
}
return null;
}
public static function getOriginal(int $id): ?int {
if (!isset(self::$vocationsFrom[$id])) {
return null;
}
while ($tmpId = self::$vocationsFrom[$id]) {
if ($tmpId == $id) {
break;
}
$id = $tmpId;
}
return $id;
}
public static function getBase($includingRook = true): array {
$vocations = [];
foreach (self::$vocationsFrom as $vocId => $fromVoc) {
if ($vocId == $fromVoc && ($vocId != 0 || $includingRook)) {
$vocations[] = $vocId;
}
}
return $vocations;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace MyAAC\Server\XML;
class ExpStages
{
private array $stages = [];
const FILE = 'XML/stages.xml';
public function load(): void
{
$file = config('data_path') . self::FILE;
if(!@file_exists($file)) {
return;
}
$xml = new \DOMDocument();
if(!$xml->load($file)) {
error('Error: Cannot load stages.xml. More info in system/logs/error.log file.');
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load stages.xml - $file. Error: " . print_r(error_get_last(), true));
return;
}
foreach($xml->getElementsByTagName('stage') as $stage)
{
/** @var \DOMElement $stage */
$maxLevel = $stage->getAttribute('maxlevel');
$this->stages[] = [
'levels' => $stage->getAttribute('minlevel') . (isset($maxLevel[0]) ? '-' . $maxLevel : '+'),
'multiplier' => $stage->getAttribute('multiplier')
];
}
}
public function get(): array {
return $this->stages;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace MyAAC\Server\XML;
class Groups
{
private array $groups = [];
const FILE = 'XML/groups.xml';
public function load(): void
{
$file = config('data_path') . self::FILE;
if(!@file_exists($file)) {
error('Error: Cannot load groups.xml. More info in system/logs/error.log file.');
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load groups.xml - $file. It doesn't exist.");
return;
}
$groups = new \DOMDocument();
if(!@$groups->load($file)) {
error('Error: Cannot load groups.xml. More info in system/logs/error.log file.');
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load groups.xml - $file. Error: " . print_r(error_get_last(), true));
return;
}
// loads groups
foreach( $groups->getElementsByTagName('group') as $group)
{
$this->groups[$group->getAttribute('id')] = [
'id' => $group->getAttribute('id'),
'name' => $group->getAttribute('name'),
'access' => $group->getAttribute('access')
];
}
}
public function get(): array {
return $this->groups;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace MyAAC\Server\XML;
use MyAAC\Cache\PHP as CachePHP;
class Items
{
private string $error = '';
const FILE = 'items/items.xml';
public function getError(): string {
return $this->error;
}
public function load(): bool
{
$file = config('data_path') . self::FILE;
if (!file_exists($file)) {
$this->error = 'Cannot load file ' . $file;
return false;
}
$items = [];
try {
$xml = new \SimpleXMLElement(file_get_contents($file));
} catch (\Exception $e) {
$this->error = 'Error: Cannot load items.xml. More info in system/logs/error.log file.';
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load items.xml - $file. Error: " . $e->getMessage());
return false;
}
foreach($xml->xpath('item') as $item) {
if ($item->attributes()->fromid) {
for ($id = (int)$item->attributes()->fromid; $id <= (int)$item->attributes()->toid; $id++) {
$tmp = $this->parseNode($id, $item);
$items[$tmp['id']] = $tmp['content'];
}
} else {
$tmp = $this->parseNode($item->attributes()->id, $item);
$items[$tmp['id']] = $tmp['content'];
}
}
$cache_php = new CachePHP(config('cache_prefix'), CACHE . 'persistent/');
$cache_php->set('items', $items, 5 * 365 * 24 * 60 * 60);
return true;
}
public function parseNode($id, $node): array
{
$name = $node->attributes()->name;
$article = $node->attributes()->article;
$plural = $node->attributes()->plural;
$attributes = [];
foreach($node->xpath('attribute') as $attr) {
$attributes[strtolower($attr->attributes()->key)] = (string)$attr->attributes()->value;
if ($attr->xpath('attribute')) {
foreach($attr->xpath('attribute') as $attr2) {
$attributes[strtolower($attr2->attributes()->key)] = (string)$attr2->attributes()->value;
}
}
}
return [
'id' => (int)$id,
'content' => [
'article' => (string)$article,
'name' => (string)$name,
'plural' => (string)$plural,
'attributes' => $attributes
],
];
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace MyAAC\Server\XML;
class Mounts
{
private array $mounts = [];
const FILE = 'XML/mounts.xml';
public function load(): void
{
$file = config('data_path') . self::FILE;
if(!@file_exists($file)) {
return;
}
$xml = new \DOMDocument();
if(!$xml->load($file)) {
error('Error: Cannot load mounts.xml. More info in system/logs/error.log file.');
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load mounts.xml - $file. Error: " . print_r(error_get_last(), true));
return;
}
foreach ($xml->getElementsByTagName('mount') as $mount) {
$this->mounts[] = $this->parseMountNode($mount);
}
}
private function parseMountNode($node): array
{
$id = (int)$node->getAttribute('id');
$client_id = (int)$node->getAttribute('clientid');
$name = $node->getAttribute('name');
$speed = (int)$node->getAttribute('speed');
$premium = getBoolean($node->getAttribute('premium'));
$type = $node->getAttribute('type');
return [
'id' => $id,
'client_id' => $client_id,
'name' => $name,
'speed' => $speed,
'premium' => $premium,
'type' => $type
];
}
public function get(): array {
return $this->mounts;
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace MyAAC\Server\XML;
class Outfits
{
private array $outfits = [];
const FILE = 'XML/outfits.xml';
public function load(): void
{
$file = config('data_path') . self::FILE;
if(!@file_exists($file)) {
return;
}
$xml = new \DOMDocument();
if(!$xml->load($file)) {
error('Error: Cannot load outfits.xml. More info in system/logs/error.log file.');
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load outfits.xml - $file. Error: " . print_r(error_get_last(), true));
return;
}
foreach ($xml->getElementsByTagName('outfit') as $outfit) {
$this->outfits[] = $this->parseOutfitNode($outfit);
}
}
private function parseOutfitNode($node): array
{
$looktype = (int)$node->getAttribute('looktype');
$type = (int)$node->getAttribute('type');
$name = $node->getAttribute('name');
$premium = getBoolean($node->getAttribute('premium'));
$locked = !getBoolean($node->getAttribute('unlocked'));
$enabled = getBoolean($node->getAttribute('enabled'));
return [
'id' => $looktype,
'sex' => ($type === 1 ? SEX_MALE : SEX_FEMALE),
'name' => $name,
'premium' => $premium,
'locked' => $locked,
'enabled' => $enabled,
];
}
public function get(): array {
return $this->outfits;
}
}

View File

@@ -2,100 +2,42 @@
namespace MyAAC\Server\XML; namespace MyAAC\Server\XML;
use MyAAC\Cache\Cache;
class Vocations class Vocations
{ {
private static array $vocations; private array $vocations = [];
private static array $vocationsFrom; private array $vocationsFrom = [];
public function __construct() const FILE = 'vocations.xml';
{
$cached = Cache::remember('vocations', 10 * 60, function () {
$this->load();
$from = $this->getFrom();
$amount = 0;
foreach ($from as $vocId => $fromVocation) {
if ($vocId != 0 && $vocId == $fromVocation) {
$amount++;
}
}
return ['vocations' => $this->get(), 'vocationsFrom' => $from, 'amount' => $amount];
});
self::$vocations = $cached['vocations'];
self::$vocationsFrom = $cached['vocationsFrom'];
config(['vocations', self::$vocations]);
config(['vocations_amount', $cached['amount']]);
}
public function load(): void public function load(): void
{ {
if(!class_exists('DOMDocument')) { $file = config('data_path') . 'XML/' . self::FILE;
throw new \RuntimeException('Please install PHP xml extension. MyAAC will not work without it.');
}
$vocationsXML = new \DOMDocument();
$file = config('data_path') . 'XML/vocations.xml';
if(!@file_exists($file)) { if(!@file_exists($file)) {
$file = config('data_path') . 'vocations.xml'; $file = config('data_path') . self::FILE;
} }
if(!$vocationsXML->load($file)) { $xml = new \DOMDocument();
throw new \RuntimeException('ERROR: Cannot load <i>vocations.xml</i> - the file is malformed. Check the file with xml syntax validator.'); if(!$xml->load($file)) {
error('Error: Cannot load vocations.xml. More info in system/logs/error.log file.');
log_append('error.log', "[" . __CLASS__ . "] Fatal error: Cannot load vocations.xml - $file. Error: " . print_r(error_get_last(), true));
return;
} }
foreach($vocationsXML->getElementsByTagName('vocation') as $vocation) { foreach($xml->getElementsByTagName('vocation') as $vocation) {
$id = $vocation->getAttribute('id'); $id = $vocation->getAttribute('id');
self::$vocations[$id] = $vocation->getAttribute('name'); $this->vocations[$id] = $vocation->getAttribute('name');
$fromVocation = (int) $vocation->getAttribute('fromvoc'); $fromVocation = (int) $vocation->getAttribute('fromvoc');
self::$vocationsFrom[$id] = $fromVocation; $this->vocationsFrom[$id] = $fromVocation;
} }
} }
public static function get(): array { public function get(): array {
return self::$vocations; return $this->vocations;
} }
public static function getFrom(): array { public function getFrom(): array {
return self::$vocationsFrom; return $this->vocationsFrom;
}
public static function getPromoted(int $id): ?int {
foreach (self::$vocationsFrom as $vocId => $fromVocation) {
if ($id == $fromVocation && $vocId != $id) {
return $vocId;
}
}
return null;
}
public static function getOriginal(int $id): ?int {
while ($tmpId = self::$vocationsFrom[$id]) {
if ($tmpId == $id) {
break;
}
$id = $tmpId;
}
return $id;
}
public static function getBase($includingRook = true): array {
$vocations = [];
foreach (self::$vocationsFrom as $vocId => $fromVoc) {
if ($vocId == $fromVoc && ($vocId != 0 || $includingRook)) {
$vocations[] = $vocId;
}
}
return $vocations;
} }
} }

View File

@@ -367,6 +367,7 @@ class Settings implements \ArrayAccess
</div> </div>
<div class="box-footer"> <div class="box-footer">
<button name="save" type="submit" class="btn btn-primary">Save</button> <button name="save" type="submit" class="btn btn-primary">Save</button>
<button form="reset-settings-form" name="reset" type="submit" class="btn btn-warning position-absolute" style="right: 0; bottom: 0" onclick="return confirm('Are you sure? This will clear all settings for this plugin!')">Reset</button>
</div> </div>
<?php <?php

View File

@@ -1,19 +0,0 @@
<?php
namespace MyAAC\TwoFactorAuth\Gateway;
use MyAAC\TwoFactorAuth\Interface\AuthGatewayInterface;
use OTPHP\TOTP;
class AppAuthGateway extends BaseAuthGateway implements AuthGatewayInterface
{
public function verifyCode(string $code): bool
{
$otp = TOTP::createFromSecret($this->account->getCustomField('secret'));
$otp->setLabel($this->account->getEmail());
$otp->setIssuer(configLua('serverName'));
return $otp->verify($code);
}
}

View File

@@ -1,12 +0,0 @@
<?php
namespace MyAAC\TwoFactorAuth\Gateway;
class BaseAuthGateway
{
protected \OTS_Account $account;
public function __construct(\OTS_Account $account) {
$this->account = $account;
}
}

View File

@@ -1,16 +0,0 @@
<?php
namespace MyAAC\TwoFactorAuth\Gateway;
use MyAAC\Models\AccountEMailCode;
use MyAAC\TwoFactorAuth\Interface\AuthGatewayInterface;
use MyAAC\TwoFactorAuth\TwoFactorAuth;
class EmailAuthGateway extends BaseAuthGateway implements AuthGatewayInterface
{
public function verifyCode(string $code): bool
{
return AccountEMailCode::where('account_id', '=', $this->account->getId())->where('code', $code)->where('created_at', '>', time() - TwoFactorAuth::EMAIL_CODE_VALID_UNTIL)->first() !== null;
}
}

View File

@@ -1,9 +0,0 @@
<?php
namespace MyAAC\TwoFactorAuth\Interface;
interface AuthGatewayInterface
{
public function __construct(\OTS_Account $account);
public function verifyCode(string $code): bool;
}

View File

@@ -1,270 +0,0 @@
<?php
namespace MyAAC\TwoFactorAuth;
use MyAAC\Models\AccountEMailCode;
use MyAAC\TwoFactorAuth\Gateway\AppAuthGateway;
use MyAAC\TwoFactorAuth\Gateway\EmailAuthGateway;
use OTPHP\TOTP;
class TwoFactorAuth
{
const TYPE_NONE = 0;
const TYPE_EMAIL = 1;
const TYPE_APP = 2;
// maybe later
//const TYPE_SMS = 3;
const EMAIL_CODE_VALID_UNTIL = 24 * 60 * 60;
private static self $instance;
private \OTS_Account $account;
private int $authType;
private EmailAuthGateway|AppAuthGateway $authGateway;
public function __construct(\OTS_Account|int $account) {
if (is_int($account)) {
$this->account = new \OTS_Account();
$this->account->load($account);
}
else {
$this->account = $account;
}
$this->authType = (int)$this->account->getCustomField('2fa_type');
$this->setAuthGateway($this->authType);
}
public static function getInstance($account = null): self
{
if (!isset(self::$instance)) {
self::$instance = new self($account);
}
return self::$instance;
}
public function process($login_account, $login_password, $remember_me, $code): bool
{
global $twig;
if (!$this->isActive()) {
return true;
}
$view = 'app';
if ($this->authType == self::TYPE_EMAIL) {
$view = 'email';#
}
if (empty($code)) {
if ($this->authType == self::TYPE_EMAIL) {
if (!$this->hasRecentEmailCode(15 * 60)) {
$this->resendEmailCode();
}
}
define('HIDE_LOGIN_BOX', true);
$twig->display("account/2fa/$view/login.html.twig", [
'account_login' => $login_account,
'password_login' => $login_password,
'remember_me' => $remember_me,
]);
return false;
}
if ($this->getAuthGateway()->verifyCode($code)) {
if ($this->authType === self::TYPE_EMAIL) {
$this->deleteOldCodes();
}
return true;
}
if (setting('core.mail_enabled')) {
$mailBody = $twig->render('mail.account.2fa.email-code.wrong-attempt.html.twig');
if (!_mail($this->account->getEMail(), configLua('serverName') . ' - Failed Two-Factor Authentication Attempt', $mailBody)) {
error('An error occurred while sending email. For Admin: More info can be found in system/logs/mailer-error.log');
}
}
define('HIDE_LOGIN_BOX', true);
if ($this->authType == self::TYPE_APP) {
$errors[] = 'The token is invalid!';
}
else {
$errors[] = 'Invalid E-Mail code!';
}
$twig->display('error_box.html.twig', ['errors' => $errors]);
$twig->display("account/2fa/$view/login.html.twig",
[
'account_login' => $login_account,
'password_login' => $login_password,
'remember_me' => $remember_me,
'wrongCode' => true,
]);
return false;
}
public function processClientLogin($code, string &$error, &$errorCode): bool
{
if (!$this->isActive()) {
return true;
}
if ($this->authType == self::TYPE_EMAIL) {
$errorCode = 8;
}
if ($code === false) {
$error = 'Submit a valid two-factor authentication token.';
if ($this->authType == self::TYPE_EMAIL) {
if (!$this->hasRecentEmailCode(15 * 60)) {
$this->resendEmailCode();
}
}
return false;
}
if (!$this->getAuthGateway()->verifyCode($code)) {
$error = 'Two-factor authentication failed, token is wrong.';
return false;
}
if ($this->authType === self::TYPE_EMAIL) {
$this->deleteOldCodes();
}
return true;
}
public function setAuthGateway(int $authType): void
{
if ($authType === self::TYPE_EMAIL) {
$this->authGateway = new EmailAuthGateway($this->account);
}
else if ($authType === self::TYPE_APP) {
$this->authGateway = new AppAuthGateway($this->account);
}
}
public function getAccountManageViews(): array
{
if ($this->authType == self::TYPE_EMAIL) {
$twoFactorView = 'account/2fa/main.protected.html.twig';
$twoFactorView2 = 'account/2fa/email/manage.connected.html.twig';
}
elseif ($this->authType == self::TYPE_APP) {
$twoFactorView = 'account/2fa/app/manage.connected.html.twig';
$twoFactorView2 = 'account/2fa/main.protected.html.twig';
}
else {
$twoFactorView = 'account/2fa/app/manage.enable.html.twig';
$twoFactorView2 = 'account/2fa/email/manage.enable.html.twig';
}
return [$twoFactorView, $twoFactorView2];
}
public function enable(int $type): void {
$this->account->setCustomField('2fa_type', $type);
}
public function disable(): void
{
global $db;
$this->account->setCustomField('2fa_type', self::TYPE_NONE);
if ($db->hasColumn('accounts', 'secret')) {
$this->account->setCustomField('secret', null);
}
$this->account->setCustomField('2fa_secret', '');
}
public function isActive(?int $authType = null): bool {
if ($authType !== null) {
return $this->authType === $authType;
}
return $this->authType != self::TYPE_NONE;
}
public function getAuthType(): int {
return $this->authType;
}
public function getAuthGateway(): AppAuthGateway|EmailAuthGateway {
return $this->authGateway;
}
public function hasRecentEmailCode($since = self::EMAIL_CODE_VALID_UNTIL): bool {
return AccountEMailCode::where('account_id', '=', $this->account->getId())->where('created_at', '>', time() - $since)->first() !== null;
}
public function deleteOldCodes(): void {
AccountEMailCode::where('account_id', '=', $this->account->getId())->delete();
}
public function appInitTOTP(string $secret): TOTP
{
$otp = TOTP::createFromSecret($secret);
$otp->setLabel($this->account->getEmail());
$otp->setIssuer(configLua('serverName'));
return $otp;
}
public function appDisplayEnable(string $secret, ?TOTP $otp = null, array $errors = []): void
{
global $twig;
if ($otp === null) {
$otp = $this->appInitTOTP($secret);
}
$grCodeUri = $otp->getQrCodeUri(
'https://api.qrserver.com/v1/create-qr-code/?data=[DATA]&size=200x200&ecc=M',
'[DATA]'
);
$twig->display('account/2fa/app/enable.html.twig', [
'grCodeUri' => $grCodeUri,
'secret' => $secret,
'errors' => $errors,
]);
}
public function resendEmailCode(): void
{
global $twig;
$newCode = generateRandomString(6, true, false, true);
AccountEMailCode::create([
'account_id' => $this->account->getId(),
'code' => $newCode,
'created_at' => time(),
]);
$mailBody = $twig->render('mail.account.2fa.email-code.html.twig', [
'code' => $newCode,
]);
if (!_mail($this->account->getEMail(), configLua('serverName') . ' - Requested Authentication Email Code', $mailBody)) {
error('An error occurred while sending email. For Admin: More info can be found in system/logs/mailer-error.log');
}
}
}

View File

@@ -183,7 +183,7 @@ class Validator
return false; return false;
} }
// installer doesn't know config.php yet // installer doesn't know settings yet
// that's why we need to ignore the nulls // that's why we need to ignore the nulls
if(defined('MYAAC_INSTALL')) { if(defined('MYAAC_INSTALL')) {
$minLength = 4; $minLength = 4;
@@ -207,21 +207,15 @@ class Validator
return false; return false;
} }
if(strspn($name, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM- [ ] '") != $length) if(strspn($name, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM ") != $length)
{ {
self::$lastError = "Invalid name format. Use only A-Z, spaces and '."; self::$lastError = "This name contains invalid letters. Please use only A-Z, a-z and space!";
return false; return false;
} }
if(preg_match('/ {2,}/', $name)) if(preg_match('/ {2,}/', $name))
{ {
self::$lastError = 'Invalid character name format. Use only A-Z and no double spaces.'; self::$lastError = 'Invalid character name format. Use only A-Z, a-z and no double spaces.';
return false;
}
if(!preg_match("/[A-z ']/", $name))
{
self::$lastError = "Invalid name format. Use only A-Z, spaces and '.";
return false; return false;
} }
@@ -230,17 +224,23 @@ class Validator
/** /**
* Validate new character name. * Validate new character name.
* Name lenght must be 3-25 chars * Name length must be 3-25 chars
* *
* @param string $name Name to check * @param string $name Name to check
* @return bool Is name valid? * @return bool Is name valid?
*/ */
public static function newCharacterName($name) public static function newCharacterName($name)
{ {
global $db, $config; global $db;
$name = trim($name);
$name_lower = strtolower($name); $name_lower = strtolower($name);
if(strlen($name) < 1) {
self::$lastError = 'Please enter a name.';
return false;
}
$first_words_blocked = array_merge(["'", '-'], setting('core.create_character_name_blocked_prefix')); $first_words_blocked = array_merge(["'", '-'], setting('core.create_character_name_blocked_prefix'));
foreach($first_words_blocked as $word) { foreach($first_words_blocked as $word) {
if($word == substr($name_lower, 0, strlen($word))) { if($word == substr($name_lower, 0, strlen($word))) {
@@ -249,11 +249,6 @@ class Validator
} }
} }
if(str_ends_with($name_lower, "'") || str_ends_with($name_lower, "-")) {
self::$lastError = 'Your name contains illegal characters.';
return false;
}
if(substr($name_lower, 1, 1) == ' ') { if(substr($name_lower, 1, 1) == ' ') {
self::$lastError = 'Your name contains illegal space.'; self::$lastError = 'Your name contains illegal space.';
return false; return false;
@@ -265,11 +260,36 @@ class Validator
} }
if(preg_match('/ {2,}/', $name)) { if(preg_match('/ {2,}/', $name)) {
self::$lastError = 'Invalid character name format. Use only A-Z and numbers 0-9 and no double spaces.'; self::$lastError = 'Invalid character name format. Use only A-Z and no double spaces.';
return false; return false;
} }
if(strtolower($config['lua']['serverName']) == $name_lower) { if (substr($name[0], 0, 1) !== strtoupper(substr($name[0], 0, 1))) {
self::$lastError = 'The first letter of a name has to be a capital letter.';
return false;
}
foreach (explode(' ', $name) as $word) {
$wordCut = substr($word, 1, strlen($word));
$hasUpperCase = preg_match('/[A-Z]/', $wordCut);
if ($hasUpperCase) {
self::$lastError = 'In names capital letters are only allowed at the beginning of a word.';
return false;
}
if (strlen($word) == 1) {
self::$lastError = 'This name contains a word with only one letter. Please use more than one letter for each word.';
return false;
}
$hasVowel = preg_match('/[aeiouAEIOU]/', $word);
if (!$hasVowel) {
self::$lastError = 'This name contains a word without vowels. Please choose another name.';
return false;
}
}
if(strtolower(configLua('serverName')) == $name_lower) {
self::$lastError = 'Your name cannot be same as server name.'; self::$lastError = 'Your name cannot be same as server name.';
return false; return false;
} }

View File

@@ -1,5 +1,8 @@
<?php <?php
const SEX_FEMALE = 0;
const SEX_MALE = 1;
const SKILL_FRAGS = -1; const SKILL_FRAGS = -1;
const SKILL_BALANCE = -2; const SKILL_BALANCE = -2;
@@ -69,7 +72,6 @@ define('HOOK_ACCOUNT_LOGIN_AFTER_PASSWORD', ++$i);
define('HOOK_ACCOUNT_LOGIN_AFTER_REMEMBER_ME', ++$i); define('HOOK_ACCOUNT_LOGIN_AFTER_REMEMBER_ME', ++$i);
define('HOOK_ACCOUNT_LOGIN_AFTER_PAGE', ++$i); define('HOOK_ACCOUNT_LOGIN_AFTER_PAGE', ++$i);
define('HOOK_ACCOUNT_LOGIN_POST', ++$i); define('HOOK_ACCOUNT_LOGIN_POST', ++$i);
define('HOOK_ACCOUNT_LOGIN_PRE', ++$i);
define('HOOK_ACCOUNT_LOST_CHECK_CODE_FINISH_AFTER_PASSWORD', ++$i); define('HOOK_ACCOUNT_LOST_CHECK_CODE_FINISH_AFTER_PASSWORD', ++$i);
define('HOOK_ACCOUNT_LOST_CHECK_CODE_FINISH_AFTER_PASSWORD_REPEAT', ++$i); define('HOOK_ACCOUNT_LOST_CHECK_CODE_FINISH_AFTER_PASSWORD_REPEAT', ++$i);
define('HOOK_ACCOUNT_LOST_EMAIL_SET_NEW_PASSWORD_POST', ++$i); define('HOOK_ACCOUNT_LOST_EMAIL_SET_NEW_PASSWORD_POST', ++$i);

View File

@@ -147,9 +147,6 @@
{% include('buttons.base.html.twig') %} {% include('buttons.base.html.twig') %}
</form> </form>
<br/> <br/>
{{ include('account/2fa/main.html.twig') }}
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_ACCOUNT_LOGS') }} {{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_ACCOUNT_LOGS') }}
<a name="Account+Logs" ></a> <a name="Account+Logs" ></a>
<h2>Account Logs</h2> <h2>Account Logs</h2>

View File

@@ -1,76 +0,0 @@
<table style="width:100%;">
<tbody>
<tr>
<td>
<div class="TableContentContainer ">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>
<ol>
<li>Open an authenticator app of your choice (e.g. <a
target="_blank"
href="https://support.google.com/accounts/answer/1066447"
rel="noopener noreferrer">Google Authenticator</a>, <a
target="_blank" href="https://www.authy.com/users"
rel="noopener noreferrer">Authy</a>). In the app you
will be asked either to enter a key manually:<br><b>{{ secret }}</b><br>or
to scan the barcode below:<br>
<img alt="QR code" style="margin-top: 15px; margin-bottom: 15px;"
src="{{ grCodeUri }}">
</li>
<li><label for="totp">Enter the verification code you have received from the used
authenticator app:</label><br>
<div style="margin-top: 15px; margin-bottom: 15px;">
<input form="form" id="auth-code" name="auth-code" maxlength="6" autocomplete="off">
{% if errors|length > 0 %}
<br/>
<div class="FormFieldError">{{ errors[0] }}</div>
{% endif %}
</div>
</li>
<li>Click on "Continue" to connect the authenticator app to your
Tibia account.
</li>
</ol>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
<br>
<table style="width: 100%;">
<tbody>
<tr align="center" valign="top">
<td>
<form id="form" method="post" action="{{ getLink('account/2fa/app/enable') }}">
<input type="hidden" name="action" value="link">
{{ csrf() }}
{% set button_color = 'green' %}
{% set button_name = 'Continue' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
<td>
<form action="{{ getLink('account/manage') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_color = 'blue' %}
{% set button_name = 'Cancel' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
</tr>
</tbody>
</table>

View File

@@ -1,101 +0,0 @@
{% set title = 'Warning' %}
{% set background = config('darkborder') %}
{% set content %}
<table style="width:100%;">
<tbody>
<tr>
<td>
<span class="red"><b>Please read this warning carefully as it contains important security information! If you skip this message, you might lose your {{ config.lua.serverName }} account!</b></span><br><br>
<p>Before you connect your account with an authenticator app, you will be asked to
enter your recovery key. If you do not have a valid recovery key, you need to
order a new one before you can connect your account with an authenticator.</p>
<p>Why?<br>The recovery key is the only way to unlink the authenticator app from
your {{ config.lua.serverName }} account in various cases, among others, if:</p>
<ul style="list-style-type:square">
<li>you lose your device (mobile phone, tablet, etc.) with the authenticator
app
</li>
<li>the device with the authenticator app does not work anymore</li>
<li>the device with the authenticator app gets stolen</li>
<li>you delete the authenticator app from your device and reinstall it</li>
<li>your device is reset for some reason</li>
</ul>
<p></p>
<p>Please note that the authenticator app data is not saved on your device's account
(e.g. Google or iTunes sync) even if you have app data backup&amp;synchronisation
activated in the settings of your device!</p>
<p>In all these scenarios, the recovery key is the only way to get access to your
{{ config.lua.serverName }} account. Note that not even customer support will be able to help you in
these cases if you do not have a valid recovery key.<br>For this reason, make
sure to store your recovery key always in a safe place!</p><br>Do you have a
valid recovery key and would like to request the email with the confirmation key to
start connecting your {{ config.lua.serverName }} account to an authenticator app?<br><br><b>Enter your
recovery key:</b><br/>
<div style="margin-top: 15px; margin-bottom: 15px;">
{% if newRecoveryKeyFormat %}
<input form="form" class="UpperCaseInput" name="key1" value="" size="5" maxlength="5" autocomplete="off"> -
<input form="form" class="UpperCaseInput" name="key2" value="" size="5" maxlength="5" autocomplete="off"> - <input form="form" class="UpperCaseInput" name="key3" value="" size="5" maxlength="5" autocomplete="off"> -
<input form="form" class="UpperCaseInput" name="key4" value="" size="5" maxlength="5" autocomplete="off">
{% else %}
<input form="form" class="UpperCaseInput" name="key" value="" autocomplete="off">
{% endif %}
{% if errors|length > 0 %}
<br/>
<div class="FormFieldError">{{ errors[0] }}</div>
{% endif %}
</div>
</td>
</tr>
</tbody>
</table>
{% endset %}
{% include 'tables.headline.html.twig' %}
<br>
<table style="width:100%;">
<tbody>
<tr align="center">
<td>
<form id="form" action="{{ getLink('account/2fa/app/enable') }}" method="post" style="padding:0;margin:0;">
<input type="hidden" name="action" value="request" />
{{ csrf() }}
{% set button_name = 'Request' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
<td>
<form action="{{ getLink('account/register') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_name = 'Order Recovery Key' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
<td>
<form action="{{ getLink('account/manage') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_name = 'Cancel Request' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
</tr>
</tbody>
</table>
<style>
.UpperCaseInput {
text-transform: uppercase;
}
</style>

View File

@@ -1,64 +0,0 @@
{% set title = 'Enter Authenticator App Token' %}
{% set background = config('darkborder') %}
{% set content %}
<table style="width:100%;">
<tbody>
<tr>
<td>
<div class="TableContentContainer ">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>Enter the verification code generated by the app:<br>
<div style="margin-top: 15px; margin-bottom: 15px;">
<div class="LabelV200" style="float:left;">Authenticator App Token:</div>
<input form="form" id="auth-code" name="auth-code" maxlength="6" autocomplete="off" required autofocus></div>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
{% endset %}
{% include 'tables.headline.html.twig' %}
<br/>
<table style="width: 100%;">
<tbody>
<tr align="center" valign="top">
<td>
<form id="form" action="{{ getLink('account/manage') }}" method="post">
{{ csrf() }}
<input type="hidden" name="account_login" value="{{ account_login ?? '' }}" />
<input type="hidden" name="password_login" value="{{ password_login ?? '' }}" />
{% if remember_me %}
<input type="hidden" name="remember_me" value="true" />
{% endif %}
<input type="hidden" name="step" value="verify">
{% set button_color = 'green' %}
{% set button_name = 'Continue' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
<td>
<form action="{{ getLink('account/manage') }}" method="post"
style="padding:0;margin:0;">
{{ csrf() }}
{% set button_color = 'blue' %}
{% set button_name = 'Cancel' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
</tr>
</tbody>
</table>

View File

@@ -1,25 +0,0 @@
<tr>
<td>
<div class="TableContentContainer ">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>
<div style="float: right; width: 135px;">
<form action="{{ getLink('account/2fa/app/disable') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_name = 'Unlink' %}
{{ include('buttons.base.html.twig') }}
</form>
</div>
<b>Your Tibia account is <span style="color: green">connected</span> to an authenticator app.</b>
<p>If you do not want to use an authenticator app any longer, you can "Unlink" the authenticator
App. Note, however, an authenticator app is an important security feature which helps to
prevent any unauthorized access to your Tibia account.</p></td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>

Some files were not shown because too many files have changed in this diff Show More