Compare commits

...

62 Commits

Author SHA1 Message Date
slawkens
c6ddcf829e Change message 2025-10-12 10:14:43 +02:00
slawkens
cbb88413d9 Nothing important: change variable name 2025-10-12 10:14:35 +02:00
slawkens
2a2512f5d0 Merge branch 'main' into feature/resend-email-verify 2025-10-12 00:15:18 +02:00
slawkens
9acad15451 Allow links in error_box 2025-10-12 00:15:04 +02:00
slawkens
9a0ec33f6d feat: Resend Email Verify
+ rework the whole concept, based on new table for email hashes
This make it possible that every email will work, not matter if first or last
2025-10-12 00:14:41 +02:00
slawkens
8c3cb0e06f New configurable: hooks_debug
To view where hooks are located in .twig files
2025-10-11 18:34:15 +02:00
slawkens
2eae44e075 Add missing compat config: email_lai_sec_interval 2025-10-08 14:39:23 +02:00
slawkens
8272f1373c Fix database column info cache 2025-10-03 16:24:02 +02:00
slawkens
901df48d13 Add promotion into getTopPlayers 2025-10-03 00:31:03 +02:00
slawkens
2da0024c68 Add lookmount into getTopPlayers 2025-10-03 00:25:41 +02:00
slawkens
0d8f68a48e Fix menus for ?subtopic= 2025-10-02 22:31:16 +02:00
slawkens
0cb9d3a208 Fix routes_final cache 2025-10-02 22:31:02 +02:00
slawkens
d8b73f55a3 Fix routes_final for prod env 2025-10-02 22:16:29 +02:00
slawkens
3bb272ebbb Allow for img in online_datacenter 2025-10-02 22:13:33 +02:00
slawkens
64acf70d38 Cache::remember -1 = infinite 2025-10-02 22:13:15 +02:00
slawkens
97f9d3d6f6 Add option to use ?subtopic=x for plugins pages 2025-10-02 15:06:57 +02:00
Slawomir Boczek
f54b1bdd2a First attempt (#331) 2025-09-28 19:00:51 +02:00
slawkens
c898fe25ef New function: getColumnInfo($table, $column) 2025-09-28 16:21:31 +02:00
slawkens
73c07d470d Add variable types, don't use $config 2025-09-28 16:10:58 +02:00
slawkens
56bd7ec5ed Prevent injection in $db->hasColumn 2025-09-28 16:09:14 +02:00
slawkens
4c6277c124 Start v1.8.3-dev 2025-09-28 14:16:28 +02:00
slawkens
228780f0ad Just leaving it here, for future use (twig hook display)
Maybe configurable in the future
2025-09-28 14:14:26 +02:00
slawkens
4e9999cc0d Do not use constant on twig hooks
So it can be displayed which hook is used
2025-09-28 14:13:51 +02:00
slawkens
8bc328d6fb Now v1.8.2 real 2025-09-26 10:14:48 +02:00
slawkens
ac41b82579 Update index.php 2025-09-26 10:14:38 +02:00
slawkens
df7b6e29fb Replace firstChild with firstElementChild (Thanks to @un000000) 2025-09-26 08:56:15 +02:00
slawkens
e0cc19ad86 Release v1.8.2 2025-09-26 07:54:40 +02:00
slawkens
85e7005fd3 Fix Menu div wrong tag/closing (#329) 2025-09-24 15:39:47 +02:00
slawkens
3c0cb53e17 Add missing csrf() - fix create account buton 2025-09-23 21:45:49 +02:00
slawkens
d0112d1a67 Fix exception when email cannot be send on create account 2025-09-23 21:45:32 +02:00
slawkens
ed9beaf2b6 Fix account lost routes in tibiacom template 2025-09-14 21:02:01 +02:00
slawkens
5aa9bbf1c8 Ignore child tables of myaac-table class 2025-09-14 20:50:00 +02:00
slawkens
a6032093b2 Better look for myaac-table 2025-09-14 19:35:12 +02:00
slawkens
4eab805d26 Fix when config.local.php cannot be saved 2025-09-09 17:49:05 +02:00
slawkens
3f24f961b1 Possibility to override routes with plugins pages, like characters.php
No need to define routes in plugin.json anymore
2025-09-09 15:17:06 +02:00
slawkens
0b86459940 Start v1.8.2-dev 2025-09-07 09:33:18 +02:00
slawkens
7a9b11434e Release v1.8.1 2025-09-05 13:25:25 +02:00
slawkens
9725a3c2bd Some servers don't have guild_invites table 2025-09-03 23:47:27 +02:00
slawkens
46adeefce3 Update settings.php 2025-08-27 15:30:52 +02:00
slawkens
e4b66f34ac Fix check for donate column 2025-08-27 12:15:52 +02:00
slawkens
2465bb6f9a Update settings.php 2025-08-27 11:40:54 +02:00
André Morais
42671c5c19 Update settings.php (#321)
* Update settings.php

added Transferable Coins to the store dropdown menu in the admin area

* Adjust code a bit

---------

Co-authored-by: slawkens <slawkens@gmail.com>
2025-08-27 11:26:46 +02:00
slawkens
fec773ba4b plugin:enable/disable commands 2025-08-25 11:35:56 +02:00
slawkens
1b9f68c9ec Update PluginUninstallCommand.php 2025-08-25 10:58:54 +02:00
slawkens
7a08f91d3f plugin:unistall command 2025-08-25 09:31:50 +02:00
slawkens
4b948e9510 Option to change/set plugin settings by plugin name 2025-08-22 18:20:37 +02:00
slawkens
17ca93d020 Same with default 2025-08-22 17:51:19 +02:00
slawkens
bcc4b48eb0 Settings: Option to set boolean values as "yes" 2025-08-22 17:39:14 +02:00
slawkens
f8c4332e03 Option to reset plugin settings by plugin name 2025-08-22 17:27:53 +02:00
slawkens
235e0f394d Refactor code to use Cache::remember 2025-08-22 16:04:52 +02:00
slawkens
3451715e96 Settings class: Add type hints 2025-08-22 15:30:19 +02:00
slawkens
d85681880e Rename file name to PluginSetupCommand 2025-08-21 21:12:55 +02:00
slawkens
4701461b1f Add some comment about optional sorting, into migrate:run command 2025-08-21 20:54:58 +02:00
slawkens
482f4067b2 Menus should be saved for each template separately
Trying to fix some weird bug
2025-08-17 18:45:49 +02:00
slawkens
2f26748112 ❤️ 2025-08-17 18:19:07 +02:00
slawkens
98073a110a Fix online skulls display (Fix #320) 2025-08-17 17:50:16 +02:00
slawkens
11dae90fa9 Fix MenuBotton display if some elements are removed
From menu_categories
2025-08-12 17:42:06 +02:00
slawkens
20f99903ae Fix submenu initialization for missing elements
Added a check in InitializeMenu to skip submenu items if their corresponding DOM element does not exist, preventing potential JavaScript errors.
2025-08-12 12:46:39 +02:00
slawkens
b6e1620f14 Fix #318 (online.php throws error in one scenario) 2025-08-07 21:17:25 +02:00
dependabot[bot]
9cb7792623 Bump tmp from 0.2.3 to 0.2.4 (#317)
Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.3 to 0.2.4.
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.2.3...v0.2.4)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-07 16:21:22 +02:00
dependabot[bot]
0db908be18 Bump form-data from 4.0.2 to 4.0.4 (#315)
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.2 to 4.0.4.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.2...v4.0.4)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-02 14:15:13 +02:00
slawkens
785d38312b Start 1.8.1-dev 2025-08-02 12:41:35 +02:00
43 changed files with 746 additions and 208 deletions

View File

@@ -1,6 +1,38 @@
# Changelog
## [1.8 - 01.08.2025]
## [1.8.2 - 26.09.2025]
### Added
* Routes: Possibility to override routes with plugins pages, like characters.php - No need to define routes in plugin.json anymore (https://github.com/slawkens/myaac/commit/3f24f961b1cdeff5c60387e837ae454448bc5e1b)
### Changed
* Style: Better look for myaac-table (https://github.com/slawkens/myaac/commit/a6032093b21e5bb3f0e75d2704da87d6dea6469d, https://github.com/slawkens/myaac/commit/5aa9bbf1c8e580d973ec82ac012489f8e7bc437e)
### Fixed
* Install: Fix when config.local.php cannot be saved (https://github.com/slawkens/myaac/commit/4eab805d26d8c5562b29ed699769919d77dabced)
* Create Account: Fix an exception when email cannot be sent (https://github.com/slawkens/myaac/commit/d0112d1a67e8b854b65ad131f0375b79305df8d3)
* Login Page: Add missing csrf() - fix create account button (https://github.com/slawkens/myaac/commit/3c0cb53e17dd0b85394cfa0fdc9cf9ad8d4551df)
* tibiacom template: Fix account lost menu (https://github.com/slawkens/myaac/commit/ed9beaf2b6ca069e304e569c52e5b9188b58f05c)
* tibiacom template: Fix Menu div wrong tag/closing (#329) (https://github.com/slawkens/myaac/commit/85e7005fd3f0be51466151a3c122b96085fdfe68)
* tibiacom template: Replace firstChild with firstElementChild (Thanks to @un000000) (https://github.com/slawkens/myaac/commit/df7b6e29fb8875da97f431468c81ee99116271d9)
## [1.8.1 - 05.09.2025]
### Added
* New Commands: plugin:enable/disable/uninstall {plugin-name} (https://github.com/slawkens/myaac/commit/7a08f91d3fc0897c1ff76089ef3c649a2c6d2003, https://github.com/slawkens/myaac/commit/fec773ba4b740f35c0a3ef92ca8444a4c7d02082)
* Gifts: Added Transferable Coins to the store dropdown menu in the admin area (by @andreoam, #321) (https://github.com/slawkens/myaac/commit/42671c5c199dd9e91c774d8c9d30da9e12f1b695)
### Changed
* Commands: Allow settings to be changed/reset by plugin name (https://github.com/slawkens/myaac/commit/f8c4332e03e838d285ea0afb4b72b7c23e324d45, https://github.com/slawkens/myaac/commit/4b948e9510f7ba69d00f84d7fdaea8b3bf05b630)
* Templates: Menus should be saved for each template separately (https://github.com/slawkens/myaac/commit/482f4067b2a2e7513d9ba214274a361ffaf123d8)
### Fixed
* Online: Fix skulls display (#320) (https://github.com/slawkens/myaac/commit/98073a110ae13f9592ec9d2c4d1d1aace87587a9)
* Online: Fix if there is no world_id in the server_record table (https://github.com/slawkens/myaac/commit/b6e1620f14c20eecfc9001a7d86dfb67942985c6) (Reported by @gesior in #318)
* tibiacom: some fixes to menus (https://github.com/slawkens/myaac/commit/20f99903ae80c74ad66c1cf5a5ea8d0b0fc2fd70, https://github.com/slawkens/myaac/commit/11dae90fa94fbbf47447017db5e5847c33d6aadf)
* Guilds: Fix for some servers that don't have guild_invites table (https://github.com/slawkens/myaac/commit/9725a3c2bdb7003f5cb48febb77604c31a9b805b)
## [1.8 - 02.08.2025]
### Added
* Templates - Kathrine: Possibility to add custom menu categories (https://github.com/slawkens/myaac/commit/ec11c1402417c25980582467546d1c1e9bb8267f)

View File

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

View File

@@ -26,8 +26,8 @@
if (version_compare(phpversion(), '8.1', '<')) die('PHP version 8.1 or higher is required.');
const MYAAC = true;
const MYAAC_VERSION = '1.8';
const DATABASE_VERSION = 45;
const MYAAC_VERSION = '1.8.3-dev';
const DATABASE_VERSION = 46;
const TABLE_PREFIX = 'myaac_';
define('START_TIME', microtime(true));
define('MYAAC_OS', stripos(PHP_OS, 'WIN') === 0 ? 'WINDOWS' : (strtoupper(PHP_OS) === 'DARWIN' ? 'MAC' : 'LINUX'));

View File

@@ -1,4 +1,4 @@
SET @myaac_database_version = 45;
SET @myaac_database_version = 46;
CREATE TABLE `myaac_account_actions`
(
@@ -10,6 +10,15 @@ CREATE TABLE `myaac_account_actions`
KEY (`account_id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;
CREATE TABLE `myaac_account_emails_verify`
(
`id` int NOT NULL AUTO_INCREMENT,
`account_id` int NOT NULL,
`hash` varchar(32) NOT NULL,
`sent_at` int NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;
CREATE TABLE `myaac_admin_menu`
(
`id` int NOT NULL AUTO_INCREMENT,

View File

@@ -42,45 +42,44 @@ if(!$error) {
$configToSave['cache_prefix'] = 'myaac_' . generateRandomString(8, true, false, true);
$configToSave['database_auto_migrate'] = true;
if(!$error) {
$content = '';
$saved = Settings::saveConfig($configToSave, BASE . 'config.local.php', $content);
if ($saved) {
success($locale['step_database_config_saved']);
$_SESSION['saved'] = true;
$content = '';
$saved = Settings::saveConfig($configToSave, BASE . 'config.local.php', $content);
if ($saved || file_exists(BASE . 'config.local.php')) {
success($locale['step_database_config_saved']);
$_SESSION['saved'] = true;
require BASE . 'config.local.php';
require BASE . 'install/includes/config.php';
require BASE . 'config.local.php';
require BASE . 'install/includes/config.php';
if (!$error) {
require BASE . 'install/includes/database.php';
if (!$error) {
require BASE . 'install/includes/database.php';
if (isset($database_error)) { // we failed connect to the database
error($database_error);
if (isset($database_error)) { // we failed connect to the database
error($database_error);
}
else {
if (!$db->hasTable('accounts')) {
$tmp = str_replace('$TABLE$', 'accounts', $locale['step_database_error_table']);
error($tmp);
$error = true;
}
else {
if (!$db->hasTable('accounts')) {
$tmp = str_replace('$TABLE$', 'accounts', $locale['step_database_error_table']);
error($tmp);
$error = true;
}
if (!$error) {
$twig->display('install.installer.html.twig', array(
'url' => 'tools/5-database.php',
'message' => $locale['loading_spinner']
));
}
if (!$error) {
$twig->display('install.installer.html.twig', array(
'url' => 'tools/5-database.php',
'message' => $locale['loading_spinner']
));
}
}
} else {
$_SESSION['config_content'] = $content;
unset($_SESSION['saved']);
$locale['step_database_error_file'] = str_replace('$FILE$', '<b>' . BASE . 'config.php</b>', $locale['step_database_error_file']);
error($locale['step_database_error_file'] . '<br/>
<textarea cols="70" rows="10">' . $content . '</textarea>');
}
} else {
$error = true;
$_SESSION['config_content'] = $content;
unset($_SESSION['saved']);
$locale['step_database_error_file'] = str_replace('$FILE$', '<b>' . BASE . 'config.local.php</b>', $locale['step_database_error_file']);
error($locale['step_database_error_file'] . '<br/>
<textarea cols="70" rows="10">' . $content . '</textarea>');
}
}
?>

View File

@@ -102,18 +102,13 @@ if(!$db->hasColumn('accounts', 'web_flags')) {
success($locale['step_database_adding_field'] . ' accounts.web_flags...');
}
if(!$db->hasColumn('accounts', 'email_hash')) {
if(query("ALTER TABLE `accounts` ADD `email_hash` VARCHAR(32) NOT NULL DEFAULT '' AFTER `web_flags`;"))
success($locale['step_database_adding_field'] . ' accounts.email_hash...');
}
if(!$db->hasColumn('accounts', 'email_verified')) {
if(query("ALTER TABLE `accounts` ADD `email_verified` TINYINT(1) NOT NULL DEFAULT 0 AFTER `email_hash`;"))
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...');
}
if(!$db->hasColumn('accounts', 'email_new')) {
if(query("ALTER TABLE `accounts` ADD `email_new` VARCHAR(255) NOT NULL DEFAULT '' AFTER `email_hash`;"))
if(query("ALTER TABLE `accounts` ADD `email_new` VARCHAR(255) NOT NULL DEFAULT '' AFTER `email_verified`;"))
success($locale['step_database_adding_field'] . ' accounts.email_new...');
}

13
package-lock.json generated
View File

@@ -976,15 +976,16 @@
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -2084,9 +2085,9 @@
"license": "MIT"
},
"node_modules/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz",
"integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==",
"dev": true,
"license": "MIT",
"engines": {

View File

@@ -81,6 +81,7 @@ $deprecatedConfig = [
'account_change_character_name_points' => 'account_change_character_name_price',
'account_change_character_sex',
'account_change_character_sex_points' => 'account_change_character_name_price',
'email_lai_sec_interval' => 'mail_lost_account_interval',
];
foreach ($deprecatedConfig as $key => $value) {

View File

@@ -1142,10 +1142,18 @@ function getTopPlayers($limit = 5, $skill = 'level') {
'looktype', 'lookhead', 'lookbody', 'looklegs', 'lookfeet'
];
if ($db->hasColumn('players', 'promotion')) {
$columns[] = 'promotion';
}
if ($db->hasColumn('players', 'lookaddons')) {
$columns[] = 'lookaddons';
}
if ($db->hasColumn('players', 'lookmount')) {
$columns[] = 'lookmount';
}
return Player::query()
->select($columns)
->withOnlineStatus()

View File

@@ -26,10 +26,11 @@ use MyAAC\Cache\Cache;
*/
class OTS_DB_MySQL extends OTS_Base_DB
{
private $has_table_cache = array();
private $has_column_cache = array();
private array $has_table_cache = [];
private array $has_column_cache = [];
private array $get_column_info_cache = [];
private $clearCacheAfter = false;
private bool $clearCacheAfter = false;
/**
* Creates database connection.
*
@@ -119,6 +120,11 @@ class OTS_DB_MySQL extends OTS_Base_DB
if($cache->fetch('database_columns', $tmp) && $tmp) {
$this->has_column_cache = unserialize($tmp);
}
$tmp = null;
if($cache->fetch('database_columns_info', $tmp) && $tmp) {
$this->get_column_info_cache = unserialize($tmp);
}
}
}
@@ -155,11 +161,13 @@ class OTS_DB_MySQL extends OTS_Base_DB
if ($this->clearCacheAfter) {
$cache->delete('database_tables');
$cache->delete('database_columns');
$cache->delete('database_columns_info');
$cache->delete('database_checksum');
}
else {
$cache->set('database_tables', serialize($this->has_table_cache), 3600);
$cache->set('database_columns', serialize($this->has_column_cache), 3600);
$cache->set('database_columns_info', serialize($this->get_column_info_cache), 3600);
$cache->set('database_checksum', serialize(sha1($config['database_host'] . '.' . $config['database_name'])), 3600);
}
}
@@ -209,7 +217,8 @@ class OTS_DB_MySQL extends OTS_Base_DB
return $sql;
}
public function hasTable($name) {
public function hasTable($name): bool
{
if(isset($this->has_table_cache[$name])) {
return $this->has_table_cache[$name];
}
@@ -217,12 +226,13 @@ class OTS_DB_MySQL extends OTS_Base_DB
return $this->hasTableInternal($name);
}
private function hasTableInternal($name) {
global $config;
return ($this->has_table_cache[$name] = $this->query('SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = ' . $this->quote($config['database_name']) . ' AND `TABLE_NAME` = ' . $this->quote($name) . ' LIMIT 1;')->rowCount() > 0);
private function hasTableInternal($name): bool
{
return ($this->has_table_cache[$name] = $this->query('SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = ' . $this->quote(config('database_name')) . ' AND `TABLE_NAME` = ' . $this->quote($name) . ' LIMIT 1;')->rowCount() > 0);
}
public function hasColumn($table, $column) {
public function hasColumn($table, $column): bool
{
if(isset($this->has_column_cache[$table . '.' . $column])) {
return $this->has_column_cache[$table . '.' . $column];
}
@@ -230,8 +240,8 @@ class OTS_DB_MySQL extends OTS_Base_DB
return $this->hasColumnInternal($table, $column);
}
private function hasColumnInternal($table, $column) {
return $this->hasTable($table) && ($this->has_column_cache[$table . '.' . $column] = count($this->query('SHOW COLUMNS FROM `' . $table . "` LIKE '" . $column . "'")->fetchAll()) > 0);
private function hasColumnInternal($table, $column): bool {
return $this->hasTable($table) && ($this->has_column_cache[$table . '.' . $column] = count($this->query('SHOW COLUMNS FROM `' . $table . "` LIKE " . $this->quote($column))->fetchAll()) > 0);
}
public function hasTableAndColumns(string $table, array $columns = []): bool
@@ -247,7 +257,53 @@ class OTS_DB_MySQL extends OTS_Base_DB
return true;
}
public function revalidateCache() {
public function getColumnInfo(string $table, string $column): bool|array
{
if(isset($this->get_column_info_cache[$table . '.' . $column])) {
return $this->get_column_info_cache[$table . '.' . $column];
}
return $this->getColumnInfoInternal($table, $column);
}
private function getColumnInfoInternal(string $table, string $column): bool|array
{
if (!$this->hasTable($table) || !$this->hasColumn($table, $column)) {
return false;
}
$formatResult = function ($result) {
return [
'field' => $result['Field'],
'type' => $result['Type'],
'null' => strtolower($result['Null']),
'default' => $result['Default'],
'extra' => $result['Extra'],
];
};
$query = $this->query('SHOW COLUMNS FROM `' . $table . "` LIKE " . $this->quote($column));
$rowCount = $query->rowCount();
if ($rowCount > 1) {
$tmp = [];
$results = $query->fetchAll(PDO::FETCH_ASSOC);
foreach ($results as $result) {
$tmp[] = $formatResult($result);
}
return ($this->get_column_info_cache[$table . '.' . $column] = $tmp);
}
else if ($rowCount == 1) {
$result = $query->fetch(PDO::FETCH_ASSOC);
return ($this->get_column_info_cache[$table . '.' . $column] = $formatResult($result));
}
return [];
}
public function revalidateCache(): void
{
foreach($this->has_table_cache as $key => $value) {
$this->hasTableInternal($key);
}
@@ -262,6 +318,21 @@ class OTS_DB_MySQL extends OTS_Base_DB
$this->hasColumnInternal($explode[0], $explode[1]);
}
}
foreach($this->get_column_info_cache as $key => $value) {
$explode = explode('.', $key);
if(!isset($this->has_table_cache[$explode[0]])) { // first check if table exist
$this->hasTableInternal($explode[0]);
}
if($this->has_table_cache[$explode[0]]) {
$this->hasColumnInternal($explode[0], $explode[1]);
}
if($this->has_table_cache[$explode[0]]) {
$this->getColumnInfoInternal($explode[0], $explode[1]);
}
}
}
public function setClearCacheAfter($clearCache)

View File

@@ -0,0 +1,8 @@
CREATE TABLE `myaac_account_emails_verify`
(
`id` int NOT NULL AUTO_INCREMENT,
`account_id` int NOT NULL,
`hash` varchar(32) NOT NULL,
`sent_at` int NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;

24
system/migrations/46.php Normal file
View File

@@ -0,0 +1,24 @@
<?php
/**
* @var OTS_DB_MySQL $db
*/
$up = function () use ($db) {
if ($db->hasColumn('accounts', 'email_hash')) {
$db->dropColumn('accounts', 'email_hash');
}
if (!$db->hasTable(TABLE_PREFIX . 'account_emails_verify')) {
$db->query(file_get_contents(__DIR__ . '/46-account_emails_verify.sql'));
}
};
$down = function () use ($db) {
if (!$db->hasColumn('accounts', 'email_hash')) {
$db->addColumn('accounts', 'email_hash', "varchar(32) NOT NULL DEFAULT ''");
}
if ($db->hasTable(TABLE_PREFIX . 'account_emails_verify')) {
$db->dropTable(TABLE_PREFIX . 'account_emails_verify');
}
};

View File

@@ -9,6 +9,7 @@
*/
use MyAAC\Models\Account;
use MyAAC\Models\AccountEmailVerify;
defined('MYAAC') or die('Direct access not allowed!');
@@ -20,16 +21,20 @@ if(empty($hash)) {
return;
}
if(!Account::where('email_hash', $hash)->exists()) {
note("Your email couldn't be verified. Please contact staff to do it manually.");
// by default link is valid for 30 days
$accountEmailVerify = AccountEmailVerify::where('hash', $hash)->where('sent_at', '>', time() - 30 * 24 * 60 * 60)->first();
if(!$accountEmailVerify) {
note("Wrong link or link has expired.");
}
else
{
$accountModel = Account::where('email_hash', $hash)->where('email_verified', 0)->first();
$accountModel = Account::where('id', $accountEmailVerify->account_id)->where('email_verified', 0)->first();
if ($accountModel) {
$accountModel->email_verified = 1;
$accountModel->save();
AccountEmailVerify::where('account_id', $accountModel->id)->delete();
success('You have now verified your e-mail, this will increase the security of your account. Thank you for doing this. You can now <a href=' . getLink('account/manage') . '>log in</a>.');
$account = new OTS_Account();
@@ -39,6 +44,6 @@ else
}
}
else {
error('Link has expired.');
error('Your account is already verified.');
}
}

View File

@@ -10,6 +10,7 @@
*/
use MyAAC\CreateCharacter;
use MyAAC\Models\AccountEmailVerify;
defined('MYAAC') or die('Direct access not allowed!');
$title = 'Create Account';
@@ -244,7 +245,12 @@ if($save)
if(setting('core.mail_enabled') && setting('core.account_mail_verify'))
{
$hash = md5(generateRandomString(16, true, true) . $email);
$new_account->setCustomField('email_hash', $hash);
AccountEmailVerify::create([
'account_id' => $new_account->getId(),
'hash' => $hash,
'sent_at' => time(),
]);
$verify_url = getLink('account/confirm-email/' . $hash);
$body_html = $twig->render('mail.account.verify.html.twig', array(
@@ -268,8 +274,10 @@ if($save)
}
else
{
error('An error occorred while sending email! Account not created. Try again. For Admin: More info can be found in system/logs/mailer-error.log');
error('An error occurred while sending email! Account not created. Try again. For Admin: More info can be found in system/logs/mailer-error.log');
$new_account->delete();
return;
}
}
else

View File

@@ -48,7 +48,9 @@ if(!empty($login_account) && !empty($login_password))
)
{
if (setting('core.account_mail_verify') && (int)$account_logged->getCustomField('email_verified') !== 1) {
$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.';
$link = getLink('account/resend-email-verify');
$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>';
} else {
session_regenerate_id();
setSession('account', $account_logged->getId());

View File

@@ -0,0 +1,94 @@
<?php
use MyAAC\Models\AccountEmailVerify;
defined('MYAAC') or die('Direct access not allowed!');
$title = 'Resend Email';
$errorWithBackButton = function ($msg) use ($twig) {
$errors = [$msg];
$twig->display('error_box.html.twig', ['errors' => $errors]);
$twig->display('account.back_button.html.twig', [
'action' => getLink('account/resend-email-verify'),
]);
};
if (!setting('core.mail_enabled') || !setting('core.account_mail_verify')) {
$errorWithBackButton('Resending email is not possible on this server.');
return;
}
$showForm = true;
if (isset($_POST['submit']) && $_POST['submit'] == '1') {
$email = $_REQUEST['email'];
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errorWithBackButton('Please enter valid Email.');
return;
}
$account = new OTS_Account();
$account->findByEMail($email);
if ($account->isLoaded()) {
if ($account->getCustomField('email_verified') == '1') {
$errorWithBackButton('This account is already verified! You can <a href=' . getLink('account/manage') . '>log in</a> on the website.');
return;
}
$accountEmailVerify = AccountEmailVerify::where('account_id', $account->getId())->orderBy('sent_at', 'DESC')->first();
if ($accountEmailVerify && time() - $accountEmailVerify->sent_at < 60) {
$errorWithBackButton('Only one Email per minute is allowed. Please try again later.');
return;
}
$tmp_account = $email;
if (!config('account_login_by_email')) {
$tmp_account = (USE_ACCOUNT_NAME ? $account->getName() : $account->getId());
}
$hash = md5(generateRandomString(16, true, true) . $email);
AccountEmailVerify::create([
'account_id' => $account->getId(),
'hash' => $hash,
'sent_at' => time(),
]);
$verify_url = getLink('account/confirm-email/' . $hash);
$body_html = $twig->render('mail.account.resend-email-verify.html.twig', array(
'account' => $tmp_account,
'verify_url' => generateLink($verify_url, $verify_url, true)
));
if (_mail($account->getEMail(), configLua('serverName') . ' - Verify Account', $body_html)) {
$message = "If account with this email exists - you will become an email with verification link.";
$showForm = false;
} else {
$message = "<p class='error'>An error occurred while sending email (<b>{$email}</b> )! Try again later. For Admin: More info can be found in system/logs/mailer-error.log</p>";
}
}
else {
$message = "<br />If account with this email exists - you will become an email with verification link.";
$showForm = false;
}
$twig->display('success.html.twig', array(
'title' => 'Verify Email Sent',
'description' => $message,
));
}
//show errors if not empty
if (!empty($errors)) {
$twig->display('error_box.html.twig', ['errors' => $errors]);
$twig->display('account.back_button.html.twig', [
'action' => getLink('account/resend-email-verify'),
]);
}
if ($showForm) {
$twig->display('account.resend-email-verify.html.twig');
}

View File

@@ -23,6 +23,12 @@ if(!Validator::guildName($guild_name)) {
$errors[] = Validator::getLastError();
}
if (!$db->hasTableAndColumns('guild_invites', ['player_id'])) {
$errors[] = "Guild invite is not possible on this website.";
$twig->display('error_box.html.twig', ['errors' => $errors]);
return;
}
if(empty($errors)) {
$guild = new OTS_Guild();
$guild->find($guild_name);
@@ -58,7 +64,7 @@ if(empty($errors)) {
}
}
if(!$guild_vice) {
if(empty($errors) && !$guild_vice) {
$errors[] = 'You are not a leader or vice leader of guild <b>'.$guild_name.'</b>.'.$level_in_guild;
}
@@ -84,6 +90,7 @@ if(isset($_POST['todo']) && $_POST['todo'] == 'save') {
}
}
}
if(empty($errors)) {
include(SYSTEM . 'libs/pot/InvitesDriver.php');
new InvitesDriver($guild);
@@ -104,6 +111,7 @@ if(!empty($errors)) {
else {
if(isset($_POST['todo']) && $_POST['todo'] == 'save') {
$guild->invite($player);
$twig->display('success.html.twig', array(
'title' => 'Invite player',
'description' => 'Player with name <b>' . $player->getName() . '</b> has been invited to your guild.',

View File

@@ -121,25 +121,28 @@ foreach($rank_list as $rank)
}
}
include(SYSTEM . 'libs/pot/InvitesDriver.php');
new InvitesDriver($guild);
$invited_list = $guild->listInvites();
$invited_list = [];
$show_accept_invite = 0;
if($logged && count($invited_list) > 0)
{
foreach($invited_list as $invited_player)
{
if(count($account_players) > 0)
{
foreach($account_players as $player_from_acc)
{
if($player_from_acc->isLoaded() && $invited_player->isLoaded() && $player_from_acc->getName() == $invited_player->getName())
$show_accept_invite++;
if ($db->hasTableAndColumns('guild_invites', ['player_id'])) {
include(SYSTEM . 'libs/pot/InvitesDriver.php');
new InvitesDriver($guild);
$invited_list = $guild->listInvites();
if($logged && count($invited_list) > 0) {
foreach($invited_list as $invited_player) {
if(count($account_players) > 0) {
foreach($account_players as $player_from_acc) {
if($player_from_acc->isLoaded() && $invited_player->isLoaded() && $player_from_acc->getName() == $invited_player->getName()) {
$show_accept_invite++;
}
}
}
}
}
}
$useGuildNick = $db->hasTable('guild_members') || $db->hasTable('guild_membership') || $db->hasColumn('players', 'guildnick');
$twig->display('guilds.view.html.twig', array(

View File

@@ -105,8 +105,17 @@ $cached = Cache::remember("online_$order", setting('core.online_cache_ttl') * 60
$result = null;
$timestamp = false;
if($db->hasTable('server_record')) {
$timestamp = true;
$result = ServerRecord::where('world_id', configLua('worldId'))->orderByDesc('record')->first()->toArray();
$timestamp = $db->hasColumn('server_record', 'timestamp');
$serverRecordQuery = ServerRecord::query();
if ($db->hasColumn('server_record', 'world_id')) {
$serverRecordQuery->where('world_id', configLua('worldId'));
}
$result = $serverRecordQuery->orderByDesc('record')->first();
if ($result) {
$result = $result->toArray();
}
} else if($db->hasTable('server_config')) { // tfs 1.0
$row = ServerConfig::where('config', 'players_record')->first();
if ($row) {

View File

@@ -88,25 +88,38 @@ if($logged && $account_logged && $account_logged->isLoaded()) {
/**
* Routes loading
*/
$routesFinal = [];
$dispatcher = FastRoute\cachedDispatcher(function (FastRoute\RouteCollector $r) {
$routesFinal = [];
global $cache, $routesFinal;
foreach(getDatabasePages() as $page) {
$routesFinal[] = ['*', $page, '__database__/' . $page, 100];
}
$routes = require SYSTEM . 'routes.php';
Plugins::clearWarnings();
foreach (Plugins::getRoutes() as $route) {
$routesFinal[] = [$route[0], $route[1], $route[2], $route[3] ?? 1000];
foreach (Plugins::getRoutes() as $pluginRoute) {
$routesFinal[] = [$pluginRoute[0], $pluginRoute[1], $pluginRoute[2], $pluginRoute[3] ?? 1000];
// Possibility to override routes with plugins pages, like characters.php
foreach ($routes as &$route) {
if (str_contains($pluginRoute[2], 'pages/' . $route[2])) {
$route[2] = $pluginRoute[2];
}
}
/*
echo '<pre>';
var_dump($route[1], $route[3], $route[2]);
var_dump($pluginRoute[1], $pluginRoute[3], $pluginRoute[2]);
echo '/<pre>';
*/
}
$routes = require SYSTEM . 'routes.php';
foreach ($routes as $route) {
if (!str_contains($route[2], '__redirect__') && !str_contains($route[2], '__database__')) {
if (!str_contains($route[2], '__redirect__') && !str_contains($route[2], '__database__')
&& !str_contains($route[2], 'plugins/')
) {
if (!is_file(BASE . 'system/pages/' . $route[2])) {
continue;
}
@@ -154,7 +167,7 @@ $dispatcher = FastRoute\cachedDispatcher(function (FastRoute\RouteCollector $r)
echo '</pre>';
die;
*/
foreach ($routesFinal as $route) {
foreach ($routesFinal as &$route) {
if ($route[0] === '*') {
$route[0] = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'];
}
@@ -187,6 +200,10 @@ $dispatcher = FastRoute\cachedDispatcher(function (FastRoute\RouteCollector $r)
log_append('router.log', $warning);
}
}
if ($cache->enabled()) {
$cache->set('routes_final', serialize($routesFinal), 10 * 365 * 24 * 60 * 60); // 10 years / infinite
}
},
[
'cacheFile' => CACHE . 'route.cache',
@@ -201,7 +218,7 @@ $found = true;
// old support for pages like /?subtopic=accountmanagement
$page = $_REQUEST['p'] ?? ($_REQUEST['subtopic'] ?? '');
if(!empty($page) && preg_match('/^[A-z0-9\-]+$/', $page)) {
if(!empty($page) && preg_match('/^[A-z0-9\/\-]+$/', $page)) {
if (isset($_REQUEST['p'])) { // some plugins may require this
$_REQUEST['subtopic'] = $_REQUEST['p'];
}
@@ -210,9 +227,26 @@ if(!empty($page) && preg_match('/^[A-z0-9\-]+$/', $page)) {
require SYSTEM . 'compat/pages.php';
}
$file = loadPageFromFileSystem($page, $found);
if(!$found) {
$file = false;
$foundRoute = false;
$tmp = null;
if ($cache->enabled() && $cache->fetch('routes_final', $tmp)) {
$routesFinal = unserialize($tmp);
}
foreach ($routesFinal as $route) {
if ($page === $route[1]) {
$file = $route[2];
$foundRoute = true;
break;
}
}
if (!$foundRoute) {
$file = loadPageFromFileSystem($page, $found);
if(!$found) {
$file = false;
}
}
}
else {

View File

@@ -28,6 +28,15 @@ if (!IS_CLI) {
$siteURL = $serverUrl . $baseDir;
}
$donateColumnOptions = [
'premium_points' => 'Premium Points',
'coins' => 'Coins',
];
if (defined('HAS_ACCOUNT_COINS_TRANSFERABLE') && (HAS_ACCOUNT_COINS_TRANSFERABLE || HAS_ACCOUNT_TRANSFERABLE_COINS)) {
$donateColumnOptions[ACCOUNT_COINS_TRANSFERABLE_COLUMN] = 'Coins Transferable';
}
return [
'name' => 'MyAAC',
'settings' => [
@@ -1295,7 +1304,7 @@ Sent by MyAAC,<br/>
'name' => 'Data Center',
'type' => 'text',
'desc' => 'Server Location, will be shown on online page',
'default' => 'Frankfurt - Germany',
'default' => 'Poland - Warsaw',
],
[
'type' => 'section',
@@ -1598,13 +1607,14 @@ Sent by MyAAC,<br/>
'name' => 'Donate Column',
'type' => 'options',
'desc' => 'What to give to player after donation - what column in accounts table to use.',
'options' => ['premium_points' => 'Premium Points', 'coins' => 'Coins'],
'options' => $donateColumnOptions,
'default' => 'premium_points',
'callbacks' => [
'beforeSave' => function($key, $value, &$errorMessage) {
global $db;
if ($value == 'coins' && !HAS_ACCOUNT_COINS) {
$errorMessage = "Shop: Donate Column: Cannot set column to coins, because it doesn't exist in database.";
if (!$db->hasColumn('accounts', $value)) {
$errorMessage = "Shop: Donate Column: Cannot set column to $value, because it doesn't exist in database.";
return false;
}
return true;

View File

@@ -115,6 +115,11 @@ class Cache
return unserialize($value);
}
// -1 for infinite cache
if ($ttl == -1) {
$ttl = 10 * 365 * 24 * 60 * 60; // 10 years should be enough
}
$value = $callback();
$cache->set($key, serialize($value), $ttl);
return $value;

View File

@@ -45,6 +45,22 @@ class MigrateRunCommand extends Command
$down = $input->getOption('down') ?? false;
/**
* Sort according to $down option.
* Do we really want it?
* Or should we use order provided by user,
* even when it's not sorted correctly?
* Leaving it for consideration.
*/
/*
if ($down) {
rsort($ids);
}
else {
sort($ids);
}
*/
foreach ($ids as $id) {
$this->executeMigration($id, $io, !$down);
}

View File

@@ -0,0 +1,36 @@
<?php
namespace MyAAC\Commands;
use MyAAC\Plugins;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class PluginDisableCommand extends Command
{
protected function configure(): void
{
$this->setName('plugin:disable')
->setDescription('This command disables plugin')
->addArgument('plugin-name', InputArgument::REQUIRED, 'Plugin that you want to disable');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
require SYSTEM . 'init.php';
$io = new SymfonyStyle($input, $output);
$pluginName = $input->getArgument('plugin-name');
if (!Plugins::disable($pluginName)) {
$io->error('Error while disabling plugin ' . $pluginName . ': ' . Plugins::getError());
return 2;
}
$io->success('Successfully disabled plugin ' . $pluginName);
return Command::SUCCESS;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace MyAAC\Commands;
use MyAAC\Plugins;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class PluginEnableCommand extends Command
{
protected function configure(): void
{
$this->setName('plugin:enable')
->setDescription('This command enables plugin')
->addArgument('plugin-name', InputArgument::REQUIRED, 'Plugin that you want to enable');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
require SYSTEM . 'init.php';
$io = new SymfonyStyle($input, $output);
$pluginName = $input->getArgument('plugin-name');
if (!Plugins::enable($pluginName)) {
$io->error('Error while enabling plugin ' . $pluginName . ': ' . Plugins::getError());
return 2;
}
$io->success('Successfully enabled plugin ' . $pluginName);
return Command::SUCCESS;
}
}

View File

@@ -8,7 +8,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class PluginInstallInstallCommand extends Command
class PluginSetupCommand extends Command
{
protected function configure(): void
{

View File

@@ -0,0 +1,40 @@
<?php
namespace MyAAC\Commands;
use MyAAC\Plugins;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class PluginUninstallCommand extends Command
{
protected function configure(): void
{
$this->setName('plugin:uninstall')
->setDescription('This command uninstalls plugin')
->addArgument('plugin-name', InputArgument::REQUIRED, 'Plugin that you want to uninstall');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
require SYSTEM . 'init.php';
$io = new SymfonyStyle($input, $output);
$pluginName = $input->getArgument('plugin-name');
if (!Plugins::uninstall($pluginName)) {
$io->error('Error while uninstalling plugin ' . $pluginName . ': ' . Plugins::getError());
return 2;
}
foreach(Plugins::getWarnings() as $warning) {
$io->warning($warning);
}
$io->success('Successfully uninstalled plugin ' . $pluginName);
return Command::SUCCESS;
}
}

View File

@@ -3,6 +3,7 @@
namespace MyAAC\Commands;
use MyAAC\Models\Settings as SettingsModel;
use MyAAC\Plugins;
use MyAAC\Settings;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -34,7 +35,14 @@ class SettingsResetCommand extends Command
return Command::FAILURE;
}
if (!$name) {
// find by plugin name
foreach (Plugins::getAllPluginsSettings() as $key => $setting) {
if ($setting['pluginFilename'] === $name) {
$name = $key;
}
}
if (empty($name)) {
SettingsModel::truncate();
}
else {

View File

@@ -3,6 +3,7 @@
namespace MyAAC\Commands;
use MyAAC\Models\Settings as SettingsModel;
use MyAAC\Plugins;
use MyAAC\Settings;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -17,7 +18,7 @@ class SettingsSetCommand extends Command
->setDescription('Updates the setting specified by argument in database')
->addArgument('key',
InputArgument::REQUIRED,
'Setting name/key'
'Setting key in format name.key'
)
->addArgument('value',
InputArgument::REQUIRED,
@@ -34,6 +35,18 @@ class SettingsSetCommand extends Command
$key = $input->getArgument('key');
$value = $input->getArgument('value');
// format settings_name.key
// example: core.template
$explode = explode('.', $key);
// find by plugin name
foreach (Plugins::getAllPluginsSettings() as $_key => $setting) {
if ($setting['pluginFilename'] === $explode[0]) {
$explode[0] = $_key;
$key = implode('.', $explode);
}
}
$settings = Settings::getInstance();
$settings->clearCache();
$settings->load();
@@ -44,10 +57,6 @@ class SettingsSetCommand extends Command
return Command::FAILURE;
}
// format plugin_name.key
// example: core.template
$explode = explode('.', $key);
$settings->updateInDatabase($explode[0], $explode[1], $value);
$settings->clearCache();

View File

@@ -0,0 +1,15 @@
<?php
namespace MyAAC\Models;
use Illuminate\Database\Eloquent\Model;
class AccountEmailVerify extends Model
{
protected $table = TABLE_PREFIX . 'account_emails_verify';
public $timestamps = false;
protected $fillable = ['account_id', 'hash', 'sent_at'];
}

View File

@@ -7,16 +7,13 @@ use MyAAC\Models\Settings as ModelsSettings;
class Settings implements \ArrayAccess
{
static private $instance;
private $settingsFile = [];
private $settingsDatabase = [];
private $cache = [];
private $valuesAsked = [];
private $errors = [];
static private ?Settings $instance = null;
private array $settingsFile = [];
private array $settingsDatabase = [];
private array $cache = [];
private array $valuesAsked = [];
private array $errors = [];
/**
* @return Settings
*/
public static function getInstance(): Settings
{
if (!self::$instance) {
@@ -26,28 +23,21 @@ class Settings implements \ArrayAccess
return self::$instance;
}
public function load()
public function load(): void
{
$cache = Cache::getInstance();
if ($cache->enabled()) {
$tmp = '';
if ($cache->fetch('settings', $tmp)) {
$this->settingsDatabase = unserialize($tmp);
return;
$this->settingsDatabase = Cache::remember('settings', 10 * 60, function () {
$settingsDatabase = [];
$settings = ModelsSettings::all();
foreach ($settings as $setting) {
$settingsDatabase[$setting->name][$setting->key] = $setting->value;
}
}
$settings = ModelsSettings::all();
foreach ($settings as $setting) {
$this->settingsDatabase[$setting->name][$setting->key] = $setting->value;
}
if ($cache->enabled()) {
$cache->set('settings', serialize($this->settingsDatabase), 600);
}
return $settingsDatabase;
});
}
public function save($pluginName, $values)
public function save($pluginName, $values): bool
{
$this->loadPlugin($pluginName);
@@ -104,7 +94,7 @@ class Settings implements \ArrayAccess
return true;
}
public function updateInDatabase($pluginName, $key, $value)
public function updateInDatabase($pluginName, $key, $value): void
{
if (ModelsSettings::where(['name' => $pluginName, 'key' => $key])->exists()) {
ModelsSettings::where(['name' => $pluginName, 'key' => $key])->update(['value' => $value]);
@@ -117,7 +107,7 @@ class Settings implements \ArrayAccess
$this->clearCache();
}
public function deleteFromDatabase($pluginName, $key = null)
public function deleteFromDatabase($pluginName, $key = null): void
{
if (!isset($key)) {
ModelsSettings::where('name', $pluginName)->delete();
@@ -217,7 +207,7 @@ class Settings implements \ArrayAccess
if (isset($setting['hidden']) && $setting['hidden']) {
$value = '';
if ($setting['type'] === 'boolean') {
$value = ($setting['default'] ? 'true' : 'false');
$value = (getBoolean($setting['default']) ? 'true' : 'false');
}
else if (in_array($setting['type'], ['text', 'number', 'float', 'double', 'email', 'password', 'textarea'])) {
$value = $setting['default'];
@@ -230,12 +220,7 @@ class Settings implements \ArrayAccess
}
else if ($setting['type'] === 'boolean') {
if(isset($settingsDb[$key])) {
if($settingsDb[$key] === 'true') {
$value = true;
}
else {
$value = false;
}
$value = getBoolean($settingsDb[$key]);
}
else {
$value = ($setting['default'] ?? false);
@@ -383,7 +368,7 @@ class Settings implements \ArrayAccess
}
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
public function offsetSet($offset, $value): void
{
if (is_null($offset)) {
throw new \RuntimeException("Settings: You cannot set empty offset with value: $value!");
@@ -423,7 +408,7 @@ class Settings implements \ArrayAccess
}
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
public function offsetUnset($offset): void
{
$this->loadPlugin($offset);
@@ -455,7 +440,7 @@ class Settings implements \ArrayAccess
* @return array|mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
public function offsetGet($offset): mixed
{
// try cache hit
if(isset($this->cache[$offset])) {
@@ -521,7 +506,7 @@ class Settings implements \ArrayAccess
return $ret;
}
private function updateValuesAsked($offset)
private function updateValuesAsked($offset): void
{
$pluginKeyName = $offset;
if (strpos($offset, '.')) {
@@ -537,7 +522,7 @@ class Settings implements \ArrayAccess
}
}
private function loadPlugin($offset)
private function loadPlugin($offset): void
{
$this->updateValuesAsked($offset);
@@ -566,7 +551,7 @@ class Settings implements \ArrayAccess
}
}
public static function saveConfig($config, $filename, &$content = '')
public static function saveConfig($config, $filename, &$content = ''): bool|int
{
$content = "<?php" . PHP_EOL;

View File

@@ -148,7 +148,7 @@ function get_template_menus(): array
{
global $template_name;
$result = Cache::remember('template_menus', 10 * 60, function () use ($template_name) {
$result = Cache::remember('template_menus_' . $template_name, 10 * 60, function () use ($template_name) {
$result = Menu::select(['name', 'link', 'blank', 'color', 'category'])
->where('template', $template_name)
->orderBy('category')

View File

@@ -0,0 +1,45 @@
Please enter your account Email address.<br/><br/>
{% set title = 'Resend Email' %}
{% set background = config('darkborder') %}
{% set content %}
<table style="width:100%;">
<tr>
<td class="LabelV" >
<span><label for="email">Email Address:</label></span>
</td>
<td style="width:90%;">
<input type="email" form="form" id="email" name="email" size="30" maxlength="50" autofocus/>
</td>
</tr>
</table>
{% endset %}
{% include 'tables.headline.html.twig' %}
<br/>
<table style="width:100%;">
<tr align="center">
<td>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td style="border:0;">
<form id="form" action="{{ getLink('account/resend-email-verify') }}" method="post">
{{ csrf() }}
<input type="hidden" name="submit" value="1"/>
{{ include('buttons.submit.html.twig') }}
</form>
</td>
<tr>
</table>
</td>
<td>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td style="border:0;">
<form action="{{ getLink('news') }}" method="post">
{{ include('buttons.back.html.twig') }}
</form>
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@@ -9,7 +9,7 @@
<table border="0" cellpadding="0" cellspacing="0" width="100%"><tr>
<td><img src="{{ template_path }}/images/general/blank.gif" width="10" height="1" border="0"></td>
<td>
{{ hook(constant('HOOK_CHARACTERS_BEFORE_INFORMATIONS')) }}
{{ hook('HOOK_CHARACTERS_BEFORE_INFORMATIONS') }}
{% if canEdit %}
<a href="{{ constant('ADMIN_URL') }}?p=players&id={{ player.getId() }}" title="Edit in Admin Panel" target="_blank">
<img src="images/edit.png"/>Edit
@@ -153,11 +153,11 @@
<td>{% if account.isPremium() %}Premium Account{% else %}Free Account{% endif %}</td>
</tr>
</table>
{{ hook(constant('HOOK_CHARACTERS_AFTER_INFORMATIONS')) }}
{{ hook('HOOK_CHARACTERS_AFTER_INFORMATIONS') }}
<br/>
<table border="0" width="100%">
<tr>
{{ hook(constant('HOOK_CHARACTERS_BEFORE_SKILLS')) }}
{{ hook('HOOK_CHARACTERS_BEFORE_SKILLS') }}
{% if config.characters.skills %}
<!-- SKILLS -->
@@ -179,7 +179,7 @@
<!-- SKILLS_END -->
{% endif %}
{{ hook(constant('HOOK_CHARACTERS_AFTER_SKILLS')) }}
{{ hook('HOOK_CHARACTERS_AFTER_SKILLS') }}
{% if quests_enabled %}
<!-- QUESTS -->
@@ -201,7 +201,7 @@
<!-- QUESTS_END -->
{% endif %}
{{ hook(constant('HOOK_CHARACTERS_AFTER_QUESTS')) }}
{{ hook('HOOK_CHARACTERS_AFTER_QUESTS') }}
{% if config.characters.equipment %}
<!-- EQUIPMENT -->
@@ -239,11 +239,11 @@
<!-- EQUIPMENT_END -->
{% endif %}
{{ hook(constant('HOOK_CHARACTERS_AFTER_EQUIPMENT')) }}
{{ hook('HOOK_CHARACTERS_AFTER_EQUIPMENT') }}
</tr>
</table>
{{ hook(constant('HOOK_CHARACTERS_BEFORE_DEATHS')) }}
{{ hook('HOOK_CHARACTERS_BEFORE_DEATHS') }}
{% if deaths|length > 0 %}
<!-- DEATHS -->
@@ -283,7 +283,7 @@
<!-- FRAGS_END -->
{% endif %}
{{ hook(constant('HOOK_CHARACTERS_BEFORE_SIGNATURE')) }}
{{ hook('HOOK_CHARACTERS_BEFORE_SIGNATURE') }}
{% if setting('core.signature_enabled') %}
<!-- SIGNATURE -->
@@ -327,7 +327,7 @@
</table>
<!-- SIGNATURE_END -->
{% endif %}
{{ hook(constant('HOOK_CHARACTERS_AFTER_SIGNATURE')) }}
{{ hook('HOOK_CHARACTERS_AFTER_SIGNATURE') }}
{% if not player.isHidden() %}
{% set rows = 0 %}
<!-- ACCOUNT_INFORMATION -->
@@ -377,7 +377,7 @@
</tr>
</table>
<!-- ACCOUNT_INFORMATION_END -->
{{ hook(constant('HOOK_CHARACTERS_AFTER_ACCOUNT')) }}
{{ hook('HOOK_CHARACTERS_AFTER_ACCOUNT') }}
<!-- CHARACTERS_LIST -->
<br/><br/>
<table border="0" cellspacing="1" cellpadding="4" width="100%">
@@ -421,7 +421,7 @@
</table>
<!-- CHARACTERS_LIST_END -->
{% endif %}
{{ hook(constant('HOOK_CHARACTERS_AFTER_CHARACTERS')) }}
{{ hook('HOOK_CHARACTERS_AFTER_CHARACTERS') }}
{% if canEdit %}
<a href="{{ constant('ADMIN_URL') }}?p=players&id={{ player.getId() }}" title="Edit in Admin Panel" target="_blank">
<img src="images/edit.png"/>Edit

View File

@@ -9,7 +9,7 @@
<div class="AttentionSign" style="background-image:url({{ template_path }}/images/content/attentionsign.gif);"></div>
<b>The Following Errors Have Occurred:</b><br/>
{% for error in errors %}
<li>{{ error|striptags('<b>')|raw }}</li>
<li>{{ error|striptags('<b><a>')|raw }}</li>
{% endfor %}
</div>
<div class="BoxFrameHorizontal" style="background-image:url({{ template_path }}/images/content/box-frame-horizontal.gif);"></div>

View File

@@ -235,14 +235,16 @@
{% endif %}
{% if isVice %}
<form action="{{ getLink('guilds') }}?action=invite&guild={{ guild_name|url_encode }}" method="post">
{{ csrf() }}
<td>
{% set button_name = 'Invite Character' %}
{% set button_image = '_sbutton_invitecharacter' %}
{% include('buttons.base.html.twig') %}
</td>
</form>
{% if db.hasTableAndColumns('guild_invites', ['player_id']) %}
<form action="{{ getLink('guilds') }}?action=invite&guild={{ guild_name|url_encode }}" method="post">
{{ csrf() }}
<td>
{% set button_name = 'Invite Character' %}
{% set button_image = '_sbutton_invitecharacter' %}
{% include('buttons.base.html.twig') %}
</td>
</form>
{% endif %}
<form action="{{ getLink('guilds') }}?action=change_rank&guild={{ guild_name|url_encode }}" method="post">
{{ csrf() }}

View File

@@ -0,0 +1,7 @@
Hello {{ account }}!<br/>
<br/>
You requested to resend the verify Email on {{ config.lua.serverName }}!<br/>
<br/>
To verify your email address please click the link below:<br/>
{{ verify_url|raw }}

View File

@@ -90,7 +90,7 @@
</td>
</tr>
{% if setting('core.online_record') %}
{% if setting('core.online_record') and record|length > 0 %}
<tr>
<td class="LabelV150"><b>Online Record:</b></td>
<td>
@@ -101,7 +101,7 @@
<tr>
<td class="LabelV150"><b>Location Datacenter:</b></td>
<td>{{ setting('core.online_datacenter') }} <small>(Server date & time: - {{ "now"|date("d/m/Y H:i:s") }})</small></td>
<td>{{ setting('core.online_datacenter')|raw }} <small>(Server date & time: - {{ "now"|date("d/m/Y H:i:s") }})</small></td>
</tr>
<tr>
<td class="LabelV150"><b>PvP Type:</b></td>
@@ -161,7 +161,7 @@
{% endif %}
<td style="width:70%; text-align:left">
{{ player.name|raw }}{{ player.skull }}
{{ player.name|raw }}{{ player.skull|raw }}
</td>
<td style="width:10%">{{ player.level }}</td>
<td style="width:20%">{{ player.vocation }}</td>

View File

@@ -1,6 +1,9 @@
<style>
.myaac-table tbody tr:nth-child(even) {background: {{ config.lightborder }}}
.myaac-table tbody tr:nth-child(odd) {background: {{ config.darkborder }}}
.myaac-table thead td {background: {{ config.vdarkborder }}; color: #ffffff !important;}
.myaac-table tfoot td {background: {{ config.vdarkborder }}; color: #ffffff !important;}
.myaac-table {border-spacing: 1px;}
.myaac-table > tbody > tr:nth-child(even) {background: {{ config.lightborder }}}
.myaac-table > tbody > tr:nth-child(odd) {background: {{ config.darkborder }}}
.myaac-table > tbody > tr > td {padding: 4px; }
.myaac-table > thead > tr > td {padding: 4px; background: {{ config.vdarkborder }}; color: #ffffff !important;}
.myaac-table > thead > tr > th {padding: 4px; background: {{ config.vdarkborder }}; color: #ffffff !important;}
.myaac-table > tfoot > tr > td {padding: 4px; background: {{ config.vdarkborder }}; color: #ffffff !important;}
</style>

View File

@@ -101,6 +101,10 @@ $twig->addFunction($function);
$function = new TwigFunction('hook', function ($context, $hook, array $params = []) {
global $hooks;
if (config('hooks_debug')) {
note($hook);
}
if(is_string($hook)) {
if (defined($hook)) {
$hook = constant($hook);

View File

@@ -130,6 +130,7 @@
<div style="float: right; margin-top: 20px;" >
{% apply spaceless %}
<form class="MediumButtonForm" action="{{ getLink('account/create') }}" method="post" >
{{ csrf() }}
<div class="MediumButtonBackground" style="background-image:url({{ template_path }}/images/global/buttons/mediumbutton.gif)" onMouseOver="MouseOverBigButton(this);" onMouseOut="MouseOutBigButton(this);">
<div class="MediumButtonOver" style="background-image:url({{ template_path }}/images/global/buttons/mediumbutton-over.gif)" onMouseOver="MouseOverBigButton(this);" onMouseOut="MouseOutBigButton(this);"></div>
<input class="MediumButtonText" type="image" name="Create Account" alt="Create Account" src="{{ template_path }}/images/global/buttons/mediumbutton_createaccount.png" />

View File

@@ -27,24 +27,18 @@ if(isset($config['boxes']))
var loginStatus="<?php echo ($logged ? 'true' : 'false'); ?>";
<?php
if(PAGE !== 'news') {
if(isset($_REQUEST['subtopic'])) {
$tmp = escapeHtml($_REQUEST['subtopic']);
if($tmp === 'accountmanagement') {
$tmp = 'accountmanage';
$tmp = str_replace('/', '_', isset($_REQUEST['subtopic']) ? escapeHtml($_REQUEST['subtopic']) : PAGE);
$exp = explode('/', PAGE);
if(PAGE !== 'account/create' && PAGE !== 'account/lost' && isset($exp[1])) {
if ($exp[0] === 'account' && $exp[1] === 'lost') {
$tmp = 'account_lost';
} elseif ($exp[0] === 'account') {
$tmp = 'account_manage';
} else if ($exp[0] === 'news' && $exp[1] === 'archive') {
$tmp = 'news_archive';
}
}
else {
$tmp = str_replace('/', '_', PAGE);
$exp = explode('/', PAGE);
if(PAGE !== 'account/create' && PAGE !== 'account/lost' && isset($exp[1])) {
if ($exp[0] === 'account') {
$tmp = 'account_manage';
} else if ($exp[0] === 'news' && $exp[1] === 'archive') {
$tmp = 'news_archive';
}
else if (in_array($exp[0], ['characters', 'highscores', 'guilds', 'forum'])) {
$tmp = $exp[0];
}
else if (in_array($exp[0], ['characters', 'highscores', 'guilds', 'forum'])) {
$tmp = $exp[0];
}
}
}
@@ -90,24 +84,24 @@ if(isset($config['boxes']))
// mouse-over and click events of the loginbox
function MouseOverLoginBoxText(source)
{
source.lastChild.style.visibility = "visible";
source.firstChild.style.visibility = "hidden";
source.lastElementChild.style.visibility = "visible";
source.firstElementChild.style.visibility = "hidden";
}
function MouseOutLoginBoxText(source)
{
source.firstChild.style.visibility = "visible";
source.lastChild.style.visibility = "hidden";
source.firstElementChild.style.visibility = "visible";
source.lastElementChild.style.visibility = "hidden";
}
function LoginButtonAction()
{
if(loginStatus == "false") {
if(loginStatus === "false") {
window.location = "<?php echo getLink('account/manage'); ?>";
} else {
window.location = "<?php echo getLink('account/manage'); ?>";
}
}
function LoginstatusTextAction(source) {
if(loginStatus == "false") {
if(loginStatus === "false") {
window.location = "<?php echo getLink('account/create'); ?>";
} else {
window.location = "<?php echo getLink('account/logout'); ?>";
@@ -164,6 +158,10 @@ if(isset($config['boxes']))
function InitializeMenu()
{
for(menuItemName in menu[0]) {
if (!document.getElementById(menuItemName+"_Submenu")) {
continue;
}
if(menu[0][menuItemName] == "0") {
document.getElementById(menuItemName+"_Submenu").style.visibility = "hidden";
document.getElementById(menuItemName+"_Submenu").style.display = "none";
@@ -222,11 +220,11 @@ if(isset($config['boxes']))
// mouse-over effects of menubuttons and submenuitems
function MouseOverMenuItem(source)
{
source.firstChild.style.visibility = "visible";
source.firstElementChild.style.visibility = "visible";
}
function MouseOutMenuItem(source)
{
source.firstChild.style.visibility = "hidden";
source.firstElementChild.style.visibility = "hidden";
}
function MouseOverSubmenuItem(source)
{
@@ -332,7 +330,7 @@ if(isset($config['boxes']))
<div id="LoginBottom" class="Loginstatus" style="background-image:url(<?php echo $template_path; ?>/images/general/box-bottom.gif)" ></div>
</div>
<div-- id='Menu'>
<div id='Menu'>
<div id='MenuTop' style='background-image:url(<?php echo $template_path; ?>/images/general/box-top.gif);'></div>
<?php
@@ -387,7 +385,7 @@ foreach($config['menu_categories'] as $id => $cat) {
?>
</div>
<?php
if($id == MENU_CATEGORY_SHOP || (!setting('core.gifts_system') && $i == $countElements)) {
if ($i == $countElements) {
?>
<div id='MenuBottom' style='background-image:url(<?php echo $template_path; ?>/images/general/box-bottom.gif);'></div>
<?php
@@ -397,6 +395,7 @@ foreach($config['menu_categories'] as $id => $cat) {
<?php
}
?>
</div>
<script type="text/javascript">
InitializePage();
</script>

View File

@@ -1,11 +1,11 @@
function MouseOverBigButton(source) {
if (source?.firstChild?.style) {
source.firstChild.style.visibility = "visible";
if (source?.firstElementChild?.style) {
source.firstElementChild.style.visibility = "visible";
}
}
function MouseOutBigButton(source) {
if (source?.firstChild?.style) {
source.firstChild.style.visibility = "hidden";
if (source?.firstElementChild?.style) {
source.firstElementChild.style.visibility = "hidden";
}
}
function BigButtonAction(path) {