Compare commits

..

86 Commits

Author SHA1 Message Date
slawkens
54265f42e9 New Setting: block create account spam by ip 2026-02-06 20:26:14 +01:00
slawkens
bd87881ad0 Update create.php 2026-02-06 20:20:49 +01:00
slawkens
7a113ee72a Settings: Possibility to navigate tabs through link 2026-01-31 19:26:35 +01:00
slawkens
8d78e7090d Better gallery
Replaced complex gallery with simple script
Slideshow loaded from images/gallery folder
Credits: https://www.w3schools.com/howto/howto_js_slideshow.asp
2026-01-31 17:42:59 +01:00
slawkens
fd457b2fe8 Merge branch 'main' into develop 2026-01-31 16:01:46 +01:00
slawkens
108e83806d Settings: Custom HTML for <head> and <body> 2026-01-31 16:01:34 +01:00
slawkens
85e8d4d9af Merge branch 'main' into develop 2026-01-31 15:50:08 +01:00
slawkens
9d6287ecbc Settings: Use current year for the footer, instead of predefined one 2026-01-31 15:49:25 +01:00
slawkens
16f4cdf40a Merge branch 'main' into develop 2026-01-31 15:36:45 +01:00
slawkens
9fa9ec746c Add give:admin Command
Usage: php aac give:admin slawkens@gmail.com
Parameter: account email, name or id
2026-01-31 15:29:48 +01:00
slawkens
3e7ee12676 Update news.html.twig 2026-01-31 15:07:49 +01:00
slawkens
b435a2fba4 Update CHANGELOG-2.x.md 2026-01-31 12:37:50 +01:00
slawkens
d91de1005b Merge branch 'main' into develop 2026-01-31 12:31:02 +01:00
slawkens
88ea9ceee1 Create AccountBan.php 2026-01-31 12:30:07 +01:00
slawkens
a92428287d Migration: 49 - Fix get proper account id for samples 2026-01-31 12:12:32 +01:00
slawkens
9c8a78a386 Merge branch 'main' into develop 2026-01-31 11:46:03 +01:00
slawkens
ff4b15ad1d Merge branch 'main' into develop 2026-01-30 16:43:48 +01:00
slawkens
eaa8d9346e Fix migration 49.php when there is no session 2026-01-29 20:44:28 +01:00
slawkens
3e2d4d6686 Merge branch 'main' into develop 2026-01-28 21:59:46 +01:00
slawkens
87509ffe16 Refactor OTS_Account save(), fixing premium days on canary 2026-01-21 22:31:13 +01:00
slawkens
8d7c36e3eb Merge branch 'main' into develop 2026-01-21 20:31:48 +01:00
slawkens
1edb4743fe Fix phpstan php version matrix 2026-01-21 20:20:55 +01:00
slawkens
e3efbdc5a8 Add php 8.5 to phpstan workflow 2026-01-17 19:34:48 +01:00
slawkens
d4cc47e341 Add tfs-0.3 to github workflow cypress 2026-01-17 19:21:50 +01:00
Slawomir Boczek
5040a93031 Feature/refactor account lost (#326)
* [WIP] Account Lost refactor

* [WIP] Refactor account/lost

* Update form.html.twig

* Use myaac-table class for tables

* Set $title to 'Lost Account'

* Remove duplicated code - extract lostAccountCooldown function

* [WIP] Add csrfProtect()

* Refactor code, better $error messages

* Formatting

* Refactor

Add missing password check
Formatting

* [WIP] Refactor

* [WIP] Refactor account lost

* [WIP] Refactor account lost - fixes

* [WIP] Account lost refactor

* Fixes
* Add account lost hooks for password strength plugin
2026-01-17 18:59:01 +01:00
slawkens
173b1ace88 Update CHANGELOG-2.x.md 2026-01-17 00:19:35 +01:00
Slawomir Boczek
276aa600e2 Feature/ots player rewrite (#348)
* [WIP] Rewrite OTS_Player class

* Fix exception on load a non existing player

* Fix for servers that don't have the cap & conditions columns

* Fix created column on player save

* Update OTS_Player.php

* Add Monk Sample + fixes

* Move FAQ creation to import_base_data + cleanup
2026-01-16 23:18:03 +01:00
slawkens
8103f5e70f Merge branch 'main' into develop 2026-01-15 21:47:07 +01:00
slawkens
8632cd3191 Add setting core.vocations for backward compatibility 2026-01-14 19:44:56 +01:00
slawkens
8b6f160a0f Fix php cache get return type 2026-01-12 18:59:51 +01:00
slawkens
c3036e7d49 Don't show account name for server that don't have it
Admin Panel -> Accounts Editor
2026-01-04 16:22:07 +01:00
slawkens
0c4edf625c Fix for servers with promotion column (mostly tfs 0.3+) 2026-01-04 15:30:23 +01:00
slawkens
c28dc29391 Use develop branch for github workflows 2026-01-04 13:33:44 +01:00
slawkens
2db4f6a57b Update online.html.twig 2026-01-04 13:19:58 +01:00
slawkens
9bfd0242af Make vocation a bit smaller
To fit into the theme
2026-01-04 13:18:09 +01:00
slawkens
3ea2b68561 Update CHANGELOG-2.x.md 2026-01-04 13:17:03 +01:00
Slawomir Boczek
dcdaa5ef43 Feature/get top players skills (#347)
* Add skills to getTopPlayers

* Add example top-5

* Extract getSkillIdByName($name)
2026-01-04 13:13:38 +01:00
slawkens
7289cce826 Update CHANGELOG-2.x.md 2026-01-04 13:13:14 +01:00
slawkens
af9d4c2aeb Update CHANGELOG-2.x.md 2026-01-04 13:04:01 +01:00
Slawomir Boczek
a66edfad31 Restore vocations.xml loading + support for Monk (#345)
* Restore vocations.xml loading

For better handling of vocations
Monk is supported now

* New images for vocations (+ added Monk)

* Fix online.html.twig cause of merge
2026-01-04 13:00:34 +01:00
slawkens
89a35b5335 Merge branch 'main' into develop 2026-01-04 12:41:11 +01:00
slawkens
55da00520d Admin Panel: save menu collapse state 2026-01-03 22:04:49 +01:00
slawkens
efef16ee86 Extract script.ajax-setup.html.twig 2026-01-03 22:01:58 +01:00
slawkens
2f0b67f840 Merge branch 'main' into develop 2026-01-03 20:40:56 +01:00
slawkens
ca2e3bb576 Merge branch 'main' into develop 2026-01-03 13:21:22 +01:00
slawkens
e0e0e46701 Move forum show_board code to Twig 2026-01-02 13:30:35 +01:00
slawkens
61bcdc0c37 Merge branch 'main' into develop 2026-01-01 13:23:12 +01:00
slawkens
f966dff5a8 Convert switch to match 2025-12-28 15:50:10 +01:00
Slawomir Boczek
402f3bb9b0 [WIP] Add access option to Menus (#340)
* [WIP] Add access option to Menus

Thanks @joelslamospersson for idea

* Add notice about Guest*

* Add access column into schema.sql

* Remove spectrum.js from project

Was used in Menus, replaced by html "color" input

* Block access to page if not required Access by Menus
2025-12-26 12:59:49 +01:00
slawkens
e98de451d8 Merge branch 'main' into develop 2025-12-22 20:04:31 +01:00
slawkens
4fffaf6aff Merge branch 'main' into develop 2025-12-18 14:33:45 +01:00
slawkens
c44c9f9cf4 Add type hints and return types to cache classes 2025-12-18 14:33:07 +01:00
slawkens
ccfd6f1a87 Add PHP to cache engine list in settings 2025-12-18 14:23:25 +01:00
slawkens
96b8e00f49 Refactor PHP cache to store expiration and improve typing
Cache entries now store both the value and expiration timestamp in the file, allowing for more reliable expiration checks. Method signatures have been updated with type hints.
2025-12-18 14:22:42 +01:00
slawkens
11cb1cf97e Save db cache only if it has changed 2025-12-18 11:53:06 +01:00
slawkens
c5d3d3a25f Merge branch 'main' into develop 2025-12-14 10:21:33 +01:00
slawkens
e1197515f3 Merge branch 'main' into develop 2025-11-23 10:13:00 +01:00
slawkens
eebfc600cb Detect "deletion" column in guilds show 2025-11-18 00:15:32 +01:00
slawkens
9a99018dce Merge branch 'main' into develop 2025-11-13 20:08:38 +01:00
slawkens
6479546c22 Update CHANGELOG-2.x.md 2025-10-31 16:23:41 +01:00
slawkens
effb23f367 Create CHANGELOG-2.x.md 2025-10-31 15:32:49 +01:00
slawkens
08657c1599 Fix migration 47.php (convert IPs) 2025-10-31 15:25:55 +01:00
slawkens
1379c93439 Create 47.php 2025-10-31 07:00:11 +01:00
slawkens
19b1cfdd34 Merge branch 'main' into develop 2025-10-31 06:56:34 +01:00
slawkens
e719725841 Merge branch 'main' into develop 2025-05-09 13:45:54 +02:00
slawkens
bb3e90110d Merge branch 'main' into develop 2025-05-09 13:14:12 +02:00
slawkens
2f0758e351 Update schema.sql 2025-04-26 06:17:58 +02:00
slawkens
6667c8c364 Merge branch 'main' into develop 2025-04-26 06:17:38 +02:00
slawkens
c13a540878 Merge branch 'main' into develop 2025-04-18 13:58:42 +02:00
slawkens
869ec035d9 Merge branch 'main' into develop 2025-04-04 21:09:12 +02:00
slawkens
9d696d31d8 Merge branch 'main' into develop 2025-04-04 20:08:24 +02:00
slawkens
8cc4caf587 Merge branch 'main' into develop 2025-04-01 07:43:57 +02:00
slawkens
e1d1c7d5db Merge branch 'main' into develop 2025-03-31 22:21:16 +02:00
slawkens
320733c2c1 Merge branch 'main' into develop 2025-03-31 19:51:21 +02:00
slawkens
c1809a98d1 Merge branch 'main' into develop 2025-03-30 07:11:15 +02:00
slawkens
46ed541015 Merge branch 'main' into develop 2025-03-16 20:54:40 +01:00
slawkens
29207361b7 Merge branch 'main' into develop 2025-03-16 12:39:32 +01:00
slawkens
25013ae91b Merge branch 'main' into develop 2025-03-15 23:09:14 +01:00
slawkens
5d630ba9dd Fix the second "Save" button -> addition to previous commit 2025-03-15 22:49:43 +01:00
slawkens
feadf1314d Fix: add possibility to remove all menu items 2025-03-15 22:49:37 +01:00
slawkens
08b8a716d4 Fix the second "Save" button -> addition to previous commit 2025-03-10 13:04:57 +01:00
slawkens
cc26b5c744 Fix: add possibility to remove all menu items 2025-03-10 10:48:19 +01:00
Slawomir Boczek
cb6e9a6a88 Feature/twig hooks filters (#258)
* feat: Hooks filters

* Cleanup
2025-03-09 21:39:37 +01:00
slawkens
4adb0758c5 Set version to 2.0-dev 2025-03-09 21:26:24 +01:00
Slawomir Boczek
7312383f73 Account actions rework on ip (Use single column for IP - VARCHAR(45)) (#289)
* Account actions rework on ip (Use single column for IP - VARCHAR(45))

* No foreach needed here
2025-03-09 21:18:12 +01:00
slawkens
3c1210fefa Nothing important, just better code style 2025-03-03 20:07:54 +01:00
102 changed files with 2539 additions and 4557 deletions

View File

@@ -1,9 +1,9 @@
name: Cypress
on:
pull_request:
branches: [main]
branches: [develop]
push:
branches: [main]
branches: [develop]
jobs:
cypress:
@@ -23,7 +23,7 @@ jobs:
fail-fast: false
matrix:
php-versions: [ '8.1', '8.2', '8.3', '8.4', '8.5' ]
ots: ['tfs-1.4', 'canary-3.1.2'] # TODO: add 'tfs-master' (actually doesn't work cause AAC doesn't support reading .env configuration)
ots: ['tfs-1.4', 'canary-3.1.2', 'tfs-0.3'] # TODO: add 'tfs-master' (actually doesn't work cause AAC doesn't support reading .env configuration)
name: Cypress (PHP ${{ matrix.php-versions }}, ${{ matrix.ots }})
steps:
- name: 📌 MySQL Start & init & show db
@@ -58,6 +58,14 @@ jobs:
ref: master
path: ots
- name: Checkout TFS 0.3
uses: actions/checkout@v4
if: matrix.ots == 'tfs-0.3'
with:
repository: otland/tfs-old-svn
ref: 0.3
path: ots
- name: Checkout Canary
uses: actions/checkout@v4
if: matrix.ots == 'canary-3.1.2'
@@ -67,9 +75,15 @@ jobs:
path: ots
- name: Import OTS Schema
if: matrix.ots != 'tfs-0.3'
run: |
mysql -uroot -proot myaac < ots/schema.sql
- name: Import OTS Schema (TFS 0.3)
if: matrix.ots == 'tfs-0.3'
run: |
mysql -uroot -proot myaac < ots/schemas/mysql.sql
- name: Rename config.lua
run: mv ots/config.lua.dist ots/config.lua
@@ -109,6 +123,33 @@ jobs:
regex: false
include: 'ots/config.lua'
- name: Replace mysqlPass (TFS 0.3.6pl1)
uses: jacobtomlinson/gha-find-replace@v3
if: matrix.ots == 'tfs-0.3'
with:
find: 'sqlType = "sqlite"'
replace: 'sqlType = "mysql"'
regex: false
include: 'ots/config.lua'
- name: Replace mysqlPass (TFS 0.3.6pl1)
uses: jacobtomlinson/gha-find-replace@v3
if: matrix.ots == 'tfs-0.3'
with:
find: 'sqlPass = ""'
replace: 'sqlPass = "root"'
regex: false
include: 'ots/config.lua'
- name: Replace mysqlDatabase (Canary)
uses: jacobtomlinson/gha-find-replace@v3
if: matrix.ots == 'tfs-0.3'
with:
find: 'sqlDatabase = "theforgottenserver"'
replace: 'sqlDatabase = "myaac"'
regex: false
include: 'ots/config.lua'
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:

View File

@@ -1,9 +1,9 @@
name: PHP Linting
on:
pull_request:
branches: [main]
branches: [develop]
push:
branches: [main]
branches: [develop]
jobs:
phplint:

View File

@@ -2,9 +2,9 @@ name: "PHPStan"
on:
pull_request:
branches: [main]
branches: [develop]
push:
branches: [main]
branches: [develop]
jobs:
tests:
@@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: [ '8.1', '8.2', '8.3', '8.4' ]
php-versions: [ '8.1', '8.2', '8.3', '8.4', '8.5' ]
steps:
- name: "Checkout"
uses: "actions/checkout@v4"

21
CHANGELOG-2.x.md Normal file
View File

@@ -0,0 +1,21 @@
## [2.0-dev - x.x.2025]
### Added
* Add an "access" option to Menus (#340)
* Possibility to hide menus for unauthorized users
* Add the possibility to fetch skills in the getTopPlayers function (#347)
### Changed
* Better handling of vocations: (#345)
* Load from vocations.xml (No need to manually set)
* Support for Monk vocation
* 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)
* Admin Panel: save menu collapse state (https://github.com/slawkens/myaac/commit/55da00520df7463a1d1ca41931df1598e9f2ffeb)
### Internal
* Refactor account/lost pages (#326)
* Refactor OTS_Player to support more distros (#348)
* Refactor PHP cache to store expiration and improve typing (https://github.com/slawkens/myaac/commit/96b8e00f4999f8b4c4c97b54b97d91c6fd7df298)
* Move forum show_board code to Twig (https://github.com/slawkens/myaac/commit/e0e0e467012a5fb9979cc4387af6bad1d4540279)
* Save db cache only if it has changed (https://github.com/slawkens/myaac/commit/11cb1cf97e74f3bccf59360e1efb800a426b3d43)

View File

@@ -9,6 +9,7 @@
*/
use MyAAC\Models\Account as AccountModel;
use MyAAC\Models\AccountAction;
use MyAAC\Models\Player;
defined('MYAAC') or die('Direct access not allowed!');
@@ -182,39 +183,7 @@ else if (isset($_REQUEST['search'])) {
$account->setName($name);
}
if ($hasTypeColumn) {
$account->setCustomField('type', $group);
} elseif ($hasGroupColumn) {
$account->setCustomField('group_id', $group);
}
if ($hasSecretColumn) {
$account->setCustomField('secret', $secret);
}
$account->setCustomField('key', $key);
$account->setEMail($email);
if (HAS_ACCOUNT_COINS) {
$account->setCustomField('coins', $t_coins);
}
if (HAS_ACCOUNT_COINS_TRANSFERABLE || HAS_ACCOUNT_TRANSFERABLE_COINS) {
$account->setCustomField(ACCOUNT_COINS_TRANSFERABLE_COLUMN, $t_coins_transferable);
}
$lastDay = 0;
if($p_days != 0 && $p_days != OTS_Account::GRATIS_PREMIUM_DAYS) {
$lastDay = time();
} else if ($lastDay != 0) {
$lastDay = 0;
}
$account->setPremDays($p_days);
$account->setLastLogin($lastDay);
if ($hasPointsColumn) {
$account->setCustomField('premium_points', $p_points);
}
$account->setRLName($rl_name);
$account->setLocation($rl_loca);
@@ -222,9 +191,18 @@ else if (isset($_REQUEST['search'])) {
$account->setCountry($rl_country);
}
$account->setCustomField('created', $created);
$account->setWebFlags($web_flags);
$account->setCustomField('web_lastlogin', $web_lastlogin);
if (!isCanary()) {
$lastDay = 0;
if($p_days != 0 && $p_days != OTS_Account::GRATIS_PREMIUM_DAYS) {
$lastDay = time();
}
$account->setLastLogin($lastDay);
}
$account->setPremDays($p_days);
if (isset($password)) {
if (USE_ACCOUNT_SALT) {
@@ -238,6 +216,34 @@ else if (isset($_REQUEST['search'])) {
}
$account->save();
if ($hasTypeColumn) {
$account->setCustomField('type', $group);
} elseif ($hasGroupColumn) {
$account->setCustomField('group_id', $group);
}
if ($hasSecretColumn) {
$account->setCustomField('secret', $secret);
}
$account->setCustomField('key', $key);
if (HAS_ACCOUNT_COINS) {
$account->setCustomField('coins', $t_coins);
}
if (HAS_ACCOUNT_COINS_TRANSFERABLE || HAS_ACCOUNT_TRANSFERABLE_COINS) {
$account->setCustomField(ACCOUNT_COINS_TRANSFERABLE_COLUMN, $t_coins_transferable);
}
if ($hasPointsColumn) {
$account->setCustomField('premium_points', $p_points);
}
$account->setCustomField('created', $created);
$account->setCustomField('web_lastlogin', $web_lastlogin);
echo_success('Account saved at: ' . date('G:i'));
}
}
@@ -481,9 +487,8 @@ else if (isset($_REQUEST['search'])) {
</thead>
<tbody>
<?php
$accountActions = \MyAAC\Models\AccountAction::where('account_id', $account->getId())->orderByDesc('date')->get();
$accountActions = AccountAction::where('account_id', $account->getId())->orderByDesc('date')->get();
foreach ($accountActions as $i => $log):
$log->ip = ($log->ip != 0 ? long2ip($log->ip) : inet_ntop($log->ipv6));
?>
<tr>
<td><?php echo $i + 1; ?></td>
@@ -631,6 +636,7 @@ else if (isset($_REQUEST['search'])) {
</div>
</form>
</div>
<?php if (USE_ACCOUNT_NAME): ?>
<div class="col-6 col-lg-12">
<form action="<?php echo $admin_base; ?>" method="post">
<?php csrf(); ?>
@@ -641,6 +647,7 @@ else if (isset($_REQUEST['search'])) {
</div>
</form>
</div>
<?php endif; ?>
<div class="col-6 col-lg-12">
<form action="<?php echo $admin_base; ?>" method="post">
<?php csrf(); ?>

View File

@@ -23,6 +23,7 @@ if (!hasFlag(FLAG_CONTENT_MENUS) && !superAdmin()) {
}
$pluginThemes = Plugins::getThemes();
$groups = new OTS_Groups_List();
if (isset($_POST['template'])) {
$template = $_POST['template'];
@@ -32,6 +33,8 @@ if (isset($_POST['template'])) {
$post_menu_link = $_POST['menu_link'] ?? [];
$post_menu_blank = $_POST['menu_blank'] ?? [];
$post_menu_color = $_POST['menu_color'] ?? [];
$post_menu_access = $_POST['menu_access'] ?? [];
if (count($post_menu) != count($post_menu_link)) {
echo 'Menu count is not equal menu links. Something went wrong when sending form.';
return;
@@ -50,6 +53,7 @@ if (isset($_POST['template'])) {
'link' => $post_menu_link[$category][$i],
'blank' => $post_menu_blank[$category][$i] == 'on' ? 1 : 0,
'color' => str_replace('#', '', $post_menu_color[$category][$i]),
'access' => $post_menu_access[$category][$i],
'category' => $category,
'ordering' => $i
]);
@@ -122,7 +126,7 @@ if (isset($_POST['template'])) {
?>
<?php
$menus = Menu::query()
->select('name', 'link', 'blank', 'color', 'category', 'ordering')
->select('name', 'link', 'access', 'blank', 'color', 'category', 'ordering')
->where('enabled', 1)
->where('template', $template)
->orderBy('ordering')
@@ -151,11 +155,34 @@ if (isset($_POST['template'])) {
foreach ($menus[$id] as $menu):
$color = (empty($menu['color']) ? ($cat['default_links_color'] ?? ($config['menu_default_links_color'] ?? ($config['menu_default_color'] ?? '#ffffff'))) : '#' . $menu['color']);
?>
<li class="ui-state-default" id="list-<?php echo $id ?>-<?php echo $i ?>"><label>Name:</label> <input type="text" name="menu[<?php echo $id ?>][]" value="<?php echo escapeHtml($menu['name']); ?>"/>
<label>Link:</label> <input type="text" name="menu_link[<?php echo $id ?>][]" value="<?php echo $menu['link'] ?>"/>
<input type="hidden" name="menu_blank[<?php echo $id ?>][]" value="0"/>
<label><input class="blank-checkbox" type="checkbox" <?php echo($menu['blank'] == 1 ? 'checked' : '') ?>/><span title="Open in New Window">New Window</span></label>
<input class="color-picker" type="text" name="menu_color[<?php echo $id ?>][]" value="<?php echo $color; ?>"/>
<li class="ui-state-default" id="list-<?php echo $id ?>-<?php echo $i ?>">
<label class="label_menu_name">Name: <input type="text" name="menu[<?php echo $id ?>][]" class="form-control menu-name" value="<?php echo escapeHtml($menu['name']); ?>"/>
</label>
<label class="label_menu_link">Link: <input type="text" name="menu_link[<?= $id ?>][]" class="form-control menu-link" value="<?php echo $menu['link'] ?>"/>
</label>
<br/>
<div class="menu-options-row">
<label>Access:
<select name="menu_access[<?= $id ?>][]" class="form-control menu-access">
<option value="0" <?= ($menu['access'] == 0 ? 'selected' : ''); ?>>Guest*</option>
<?php foreach ($groups->getGroups() as $group): ?>
<option value="<?= $group->getId(); ?>" <?= ($menu['access'] == $group->getId() ? 'selected' : ''); ?>><?= ucfirst($group->getName()); ?></option>
<?php endforeach; ?>
</select>
</label>
<label>Color: <input class="menu-color" type="color" name="menu_color[<?php echo $id ?>][]" value="<?php echo $color; ?>"/>
</label>
<input type="hidden" name="menu_blank[<?php echo $id ?>][]" class="menu-blank" value="0"/>
<label><input type="checkbox" class="menu-blank-checkbox" <?php echo($menu['blank'] == 1 ? 'checked' : '') ?>/><span title="Open in New Window">New Window</span></label>
</div>
<a class="remove-button" id="remove-button-<?php echo $id ?>-<?php echo $i ?>"><i class="fas fa-trash"></a></i></li>
<?php $i++; $last_id[$id] = $i;
endforeach;

View File

@@ -10,6 +10,7 @@
use MyAAC\Forum;
use MyAAC\Models\Player;
use MyAAC\Server\XML\Vocations;
defined('MYAAC') or die('Direct access not allowed!');
@@ -34,6 +35,7 @@ $skills = array(
$hasBlessingsColumn = $db->hasColumn('players', 'blessings');
$hasBlessingColumn = $db->hasColumn('players', 'blessings1');
$hasLookAddons = $db->hasColumn('players', 'lookaddons');
$hasCapColumn = $db->hasColumn('players', 'cap');
$skull_type = array("None", "Yellow", "Green", "White", "Red", "Black", "Orange");
?>
@@ -166,8 +168,11 @@ else if (isset($_REQUEST['search'])) {
$town = $_POST['town'];
verify_number($town, 'Town', 11);
if ($hasCapColumn) {
$capacity = $_POST['capacity'];
verify_number($capacity, 'Capacity', 11);
}
$sex = $_POST['sex'];
verify_number($sex, 'Sex', 1);
@@ -237,7 +242,30 @@ else if (isset($_REQUEST['search'])) {
$player->setGroup($groups->getGroup($group));
$player->setLevel($level);
$player->setExperience($experience);
if ($db->hasColumn('players', 'promotion')) {
$promotion = 0;
$vocationOriginal = Vocations::getOriginal($vocation);
if ($vocation != $vocationOriginal) {
$tmpId = $vocationOriginal;
while($promoted = Vocations::getPromoted($tmpId)) {
$promotion++;
$tmpId = $promoted;
if ($promoted == $vocation) {
break;
}
}
$vocation = $vocationOriginal;
}
$player->setPromotion($promotion);
}
$player->setVocation($vocation);
$player->setHealth($health);
$player->setHealthMax($health_max);
$player->setMagLevel($magic_level);
@@ -249,16 +277,20 @@ else if (isset($_REQUEST['search'])) {
$player->setLookHead($look_head);
$player->setLookLegs($look_legs);
$player->setLookType($look_type);
if ($hasLookAddons)
if ($hasLookAddons) {
$player->setLookAddons($look_addons);
if ($db->hasColumn('players', 'offlinetraining_time'))
$player->setCustomField('offlinetraining_time', $offlinetraining);
}
$player->setPosX($pos_x);
$player->setPosY($pos_y);
$player->setPosZ($pos_z);
$player->setSoul($soul);
$player->setTownId($town);
if ($hasCapColumn) {
$player->setCap($capacity);
}
$player->setSex($sex);
$player->setLastLogin($lastlogin);
$player->setLastLogout($lastlogout);
@@ -275,23 +307,11 @@ else if (isset($_REQUEST['search'])) {
if ($hasBlessingsColumn)
$player->setBlessings($blessings);
if ($hasBlessingColumn) {
for ($i = 1; $i <= $bless_count; $i++) {
$a = 'blessing' . $i;
$player->setCustomField('blessings' . $i, ${'blessing' . $i} ? '1' : '0');
}
}
$player->setBalance($balance);
if ($db->hasColumn('players', 'stamina'))
$player->setStamina($stamina);
if ($db->hasColumn('players', 'deletion'))
$player->setCustomField('deletion', $deleted ? '1' : '0');
else
$player->setCustomField('deleted', $deleted ? '1' : '0');
$player->setCustomField('hide', $hide ? '1' : '0');
$player->setCustomField('created', $created);
if (isset($comment))
$player->setCustomField('comment', $comment);
$player->setDeleted($deleted ? '1' : '0');
foreach ($_POST['skills'] as $skill => $value) {
$player->setSkill($skill, $value);
@@ -300,6 +320,24 @@ else if (isset($_REQUEST['search'])) {
$player->setSkillTries($skill, $value);
}
$player->save();
if ($db->hasColumn('players', 'offlinetraining_time')) {
$player->setCustomField('offlinetraining_time', $offlinetraining);
}
if ($hasBlessingColumn) {
for ($i = 1; $i <= $bless_count; $i++) {
$a = 'blessing' . $i;
$player->setCustomField('blessings' . $i, ${'blessing' . $i} ? '1' : '0');
}
}
$player->setCustomField('hide', $hide ? '1' : '0');
$player->setCustomField('created', $created);
if (isset($comment)) {
$player->setCustomField('comment', $comment);
}
echo_success('Player saved at: ' . date('G:i'));
$player->load($id);
}
@@ -531,10 +569,12 @@ else if (isset($_REQUEST['search'])) {
</div>
</div>
<div class="form-group row">
<?php if($hasCapColumn): ?>
<div class="col-12 col-sm-12 col-lg-6">
<label for="capacity" class="control-label">Capacity:</label>
<input type="text" class="form-control" id="capacity" name="capacity" autocomplete="off" size="3" maxlength="11" value="<?php echo $player->getCap(); ?>"/>
</div>
<?php endif; ?>
<div class="col-12 col-sm-12 col-lg-6">
<label for="soul" class="control-label">Soul:</label>
<input type="text" class="form-control" id="soul" name="soul" autocomplete="off" size="3" maxlength="10" value="<?php echo $player->getSoul(); ?>"/>

View File

@@ -19,14 +19,14 @@
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">
<?php $hooks->trigger(HOOK_ADMIN_HEAD_END); ?>
</head>
<body class="sidebar-mini ">
<body class="sidebar-mini <?= (session('admin.menu-collapse') ? 'sidebar-collapse' : ''); ?>">
<?php $hooks->trigger(HOOK_ADMIN_BODY_START); ?>
<?php if ($logged && admin()) { ?>
<div class="wrapper">
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" data-widget="pushmenu" href="#"><i class="fas fa-bars"></i></a>
<a class="nav-link sidebar-toggle" data-widget="pushmenu" href="#"><i class="fas fa-bars"></i></a>
</li>
<li class="nav-item d-none d-sm-inline-block">
<a href="<?php echo ADMIN_URL; ?>" class="nav-link">Home</a>
@@ -198,6 +198,7 @@ if ($logged && admin()) {
<script src="<?php echo BASE_URL; ?>tools/js/datatables.bs.min.js"></script>
<?php } ?>
<script src="<?php echo BASE_URL; ?>tools/js/adminlte.min.js"></script>
<?php $twig->display('admin.menu-collapse.html.twig'); ?>
<?php $hooks->trigger(HOOK_ADMIN_BODY_END); ?>
</body>
</html>

View File

@@ -0,0 +1,23 @@
<?php
const MYAAC_ADMIN = true;
const IGNORE_SET_LAST_VISIT = true;
require '../../common.php';
require SYSTEM . 'functions.php';
require SYSTEM . 'init.php';
require SYSTEM . 'login.php';
if(!admin()) {
http_response_code(500);
die('You are not logged in. Probably session expired. Please login again.');
}
if (!isset($_POST['collapse'])) {
http_response_code(500);
die('Something went wrong.');
}
csrfProtect();
setSession('admin.menu-collapse', $_POST['collapse'] == 'true');

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.8';
const DATABASE_VERSION = 46;
const MYAAC_VERSION = '2.0-dev';
const DATABASE_VERSION = 50;
const TABLE_PREFIX = 'myaac_';
define('START_TIME', microtime(true));
define('MYAAC_OS', stripos(PHP_OS, 'WIN') === 0 ? 'WINDOWS' : (strtoupper(PHP_OS) === 'DARWIN' ? 'MAC' : 'LINUX'));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 33 KiB

BIN
images/monk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -3,6 +3,7 @@ defined('MYAAC') or die('Direct access not allowed!');
use MyAAC\Models\Changelog;
use MyAAC\Models\Config;
use MyAAC\Models\FAQ;
use MyAAC\Models\ForumBoard;
use MyAAC\Models\Gallery;
use MyAAC\Models\NewsCategory;
@@ -56,13 +57,10 @@ if (NewsCategory::count() === 0) {
}
}
if (Gallery::count() === 0) {
Gallery::create([
'comment' => 'Demon',
'image' => 'images/gallery/demon.jpg',
'thumb' => 'images/gallery/demon_thumb.gif',
'author' => 'MyAAC',
'ordering' => 0,
if(FAQ::count() == 0) {
FAQ::create([
'question' => 'What is this?',
'answer' => 'This is website for OTS powered by MyAAC.',
]);
}

View File

@@ -1,11 +1,11 @@
CREATE TABLE IF NOT EXISTS `myaac_account_actions`
(
`id` int NOT NULL AUTO_INCREMENT,
`account_id` int NOT NULL,
`ip` int unsigned NOT NULL DEFAULT 0,
`ipv6` binary(16) NOT NULL DEFAULT 0,
`ip` varchar(45) NOT NULL DEFAULT '',
`date` int NOT NULL DEFAULT 0,
`action` varchar(255) NOT NULL DEFAULT '',
KEY (`account_id`)
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;
CREATE TABLE IF NOT EXISTS `myaac_account_emails_verify`
@@ -102,6 +102,7 @@ CREATE TABLE IF NOT EXISTS `myaac_menu`
`template` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`link` varchar(255) NOT NULL,
`access` tinyint NOT NULL DEFAULT 0,
`blank` tinyint NOT NULL DEFAULT 0,
`color` varchar(6) NOT NULL DEFAULT '',
`category` int NOT NULL DEFAULT 1,
@@ -197,18 +198,6 @@ CREATE TABLE IF NOT EXISTS `myaac_pages`
UNIQUE (`name`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;
CREATE TABLE IF NOT EXISTS `myaac_gallery`
(
`id` int NOT NULL AUTO_INCREMENT,
`comment` varchar(255) NOT NULL DEFAULT '',
`image` varchar(255) NOT NULL,
`thumb` varchar(255) NOT NULL,
`author` varchar(50) NOT NULL DEFAULT '',
`ordering` int NOT NULL DEFAULT 0,
`hide` tinyint NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;
CREATE TABLE IF NOT EXISTS `myaac_settings`
(
`id` int NOT NULL AUTO_INCREMENT,

View File

@@ -2,8 +2,6 @@
define('MYAAC_INSTALL', true);
use MyAAC\DataLoader;
use MyAAC\Models\FAQ as ModelsFAQ;
use MyAAC\Plugins;
require_once '../../common.php';
@@ -25,34 +23,9 @@ if(isset($config['installed']) && $config['installed'] && !isset($_SESSION['save
require SYSTEM . 'init.php';
if ($db->hasTable('players')) {
$deleted = 'deleted';
if ($db->hasColumn('players', 'deletion'))
$deleted = 'deletion';
$time = time();
function insert_sample_if_not_exist($p)
{
global $db, $success, $deleted, $time;
$query = $db->query('SELECT `id` FROM `players` WHERE `name` = ' . $db->quote($p['name']));
if ($query->rowCount() == 0) {
if (!query("INSERT INTO `players` (`id`, `name`, `group_id`, `account_id`, `level`, `vocation`, `health`, `healthmax`, `experience`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `maglevel`, `mana`, `manamax`, `manaspent`, `soul`, `town_id`, `posx`, `posy`, `posz`, `conditions`, `cap`, `sex`, `lastlogin`, `lastip`, `save`, `lastlogout`, `balance`, `$deleted`, `created`, `hide`, `comment`) VALUES (null, " . $db->quote($p['name']) . ", 1, " . getSession('account') . ", " . $p['level'] . ", " . $p['vocation_id'] . ", " . $p['health'] . ", " . $p['healthmax'] . ", " . $p['experience'] . ", 118, 114, 38, 57, " . $p['looktype'] . ", 0, " . $p['mana'] . ", " . $p['manamax'] . ", 0, " . $p['soul'] . ", 1, 1000, 1000, 7, '', " . $p['cap'] . ", 1, " . $time . ", 2130706433, 1, " . $time . ", 0, 0, " . $time . ", 1, '');"))
$success = false;
}
}
$success = true;
insert_sample_if_not_exist(array('name' => 'Rook Sample', 'level' => 1, 'vocation_id' => 0, 'health' => 150, 'healthmax' => 150, 'experience' => 0, 'looktype' => 130, 'mana' => 0, 'manamax' => 0, 'soul' => 100, 'cap' => 400));
insert_sample_if_not_exist(array('name' => 'Sorcerer Sample', 'level' => 8, 'vocation_id' => 1, 'health' => 185, 'healthmax' => 185, 'experience' => 4200, 'looktype' => 130, 'mana' => 90, 'manamax' => 90, 'soul' => 100, 'cap' => 470));
insert_sample_if_not_exist(array('name' => 'Druid Sample', 'level' => 8, 'vocation_id' => 2, 'health' => 185, 'healthmax' => 185, 'experience' => 4200, 'looktype' => 130, 'mana' => 90, 'manamax' => 90, 'soul' => 100, 'cap' => 470));
insert_sample_if_not_exist(array('name' => 'Paladin Sample', 'level' => 8, 'vocation_id' => 3, 'health' => 185, 'healthmax' => 185, 'experience' => 4200, 'looktype' => 129, 'mana' => 90, 'manamax' => 90, 'soul' => 100, 'cap' => 470));
insert_sample_if_not_exist(array('name' => 'Knight Sample', 'level' => 8, 'vocation_id' => 4, 'health' => 185, 'healthmax' => 185, 'experience' => 4200, 'looktype' => 131, 'mana' => 90, 'manamax' => 90, 'soul' => 100, 'cap' => 470));
if ($success) {
success($locale['step_database_imported_players']);
}
}
// add player samples
require_once SYSTEM . 'migrations/49.php';
$up();
DataLoader::setLocale($locale);
DataLoader::load();
@@ -61,10 +34,6 @@ DataLoader::load();
require_once SYSTEM . 'migrations/17.php';
$up();
// update config.highscores_ids_hidden
require_once SYSTEM . 'migrations/20.php';
$up();
// add z_polls tables
require_once SYSTEM . 'migrations/22.php';
$up();
@@ -83,13 +52,6 @@ $up();
require_once SYSTEM . 'migrations/45.php';
$up();
if(ModelsFAQ::count() == 0) {
ModelsFAQ::create([
'question' => 'What is this?',
'answer' => 'This is website for OTS powered by MyAAC.',
]);
}
$hooks->trigger(HOOK_INSTALL_FINISH);
$db->setClearCacheAfter(true);

View File

@@ -5,8 +5,6 @@ $deprecatedConfig = [
'genders',
'template',
'template_allow_change',
'vocations_amount',
'vocations',
'client',
'session_prefix',
'friendly_urls',

View File

@@ -17,6 +17,8 @@ use MyAAC\Models\Guild;
use MyAAC\Models\House;
use MyAAC\Models\Pages;
use MyAAC\Models\Player;
use MyAAC\Models\PlayerDeath;
use MyAAC\Models\PlayerKillers;
use MyAAC\News;
use MyAAC\Plugins;
use MyAAC\Settings;
@@ -515,7 +517,12 @@ function template_place_holder($type): string
$ret .= $debugBarRenderer->renderHead();
}
}
elseif ($type === 'head_end') {
$ret .= setting('core.html_head');
}
elseif ($type === 'body_start') {
$ret .= setting('core.html_body');
$ret .= $twig->render('browsehappy.html.twig');
if (admin()) {
@@ -526,6 +533,8 @@ function template_place_holder($type): string
}
}
elseif($type === 'body_end') {
$ret .= setting('core.html_footer');
$ret .= template_ga_code();
if (isset($debugBar)) {
$ret .= $debugBarRenderer->render();
@@ -1132,11 +1141,44 @@ function csrfProtect(): void
}
}
function getTopPlayers($limit = 5, $skill = 'level') {
function getSkillIdByName(string $name): int|null
{
$skills = [
'level' => POT::SKILL_LEVEL,
'experience' => POT::SKILL_LEVEL,
'magic' => POT::SKILL_MAGIC,
'maglevel' => POT::SKILL_MAGIC,
'balance' => SKILL_BALANCE,
'frags' => SKILL_FRAGS,
'club' => POT::SKILL_CLUB,
'sword' => POT::SKILL_SWORD,
'axe' => POT::SKILL_AXE,
'dist' => POT::SKILL_DIST,
'distance' => POT::SKILL_DIST,
'shield' => POT::SKILL_SHIELD,
'shielding' => POT::SKILL_SHIELD,
'fish' => POT::SKILL_FISH,
'fishing' => POT::SKILL_FISH,
];
return $skills[$name] ?? null;
}
function getTopPlayers($limit = 5, $skill = POT::SKILL_LEVEL)
{
global $db;
if ($skill === 'level') {
$skill = 'experience';
$skillOriginal = $skill;
if (is_string($skill)) {
$skill = getSkillIdByName($skill);
}
if (!is_numeric($skill)) {
throw new RuntimeException("getTopPlayers: Invalid skill: $skillOriginal");
}
return Cache::remember("top_{$limit}_{$skill}", 2 * 60, function () use ($db, $limit, $skill) {
@@ -1157,15 +1199,64 @@ function getTopPlayers($limit = 5, $skill = 'level') {
$columns[] = 'lookmount';
}
return Player::query()
$query = Player::query()
->select($columns)
->withOnlineStatus()
->notDeleted()
->where('group_id', '<', setting('core.highscores_groups_hidden'))
->whereNotIn('id', setting('core.highscores_ids_hidden'))
->where('account_id', '!=', 1)
->orderByDesc($skill)
->limit($limit)
->orderByDesc('value');
if ($limit > 0) {
$query->limit($limit);
}
if ($skill >= POT::SKILL_FIRST && $skill <= POT::SKILL_LAST) { // skills
if ($db->hasColumn('players', 'skill_fist')) {// tfs 1.0
$skill_ids = array(
POT::SKILL_FIST => 'skill_fist',
POT::SKILL_CLUB => 'skill_club',
POT::SKILL_SWORD => 'skill_sword',
POT::SKILL_AXE => 'skill_axe',
POT::SKILL_DIST => 'skill_dist',
POT::SKILL_SHIELD => 'skill_shielding',
POT::SKILL_FISH => 'skill_fishing',
);
$query
->addSelect($skill_ids[$skill] . ' as value')
->orderByDesc($skill_ids[$skill] . '_tries');
} else {
$query
->join('player_skills', 'player_skills.player_id', '=', 'players.id')
->where('skillid', $skill)
->addSelect('player_skills.value as value');
}
} else if ($skill == SKILL_FRAGS) // frags
{
if ($db->hasTable('player_killers')) {
$query->addSelect(['value' => PlayerKillers::whereColumn('player_killers.player_id', 'players.id')->selectRaw('COUNT(*)')]);
} else {
$query->addSelect(['value' => PlayerDeath::unjustified()->whereColumn('player_deaths.killed_by', 'players.name')->selectRaw('COUNT(*)')]);
}
} else if ($skill == SKILL_BALANCE) // balance
{
$query
->addSelect('players.balance as value');
} else {
if ($skill == POT::SKILL_MAGIC) {
$query
->addSelect('players.maglevel as value', 'players.maglevel')
->orderByDesc('manaspent');
} else { // level
$query
->addSelect('players.level as value', 'players.experience')
->orderByDesc('experience');
}
}
return $query
->get()
->map(function ($e, $i) {
$row = $e->toArray();
@@ -1717,8 +1808,8 @@ function getAccountIdentityColumn(): string
function isCanary(): bool
{
$vipSystemEnabled = configLua('vipSystemEnabled');
return isset($vipSystemEnabled);
$dataPackDirectory = configLua('dataPackDirectory');
return isset($dataPackDirectory);
}
function getStatusUptimeReadable(int $uptime): string

View File

@@ -14,6 +14,7 @@ use MyAAC\CsrfToken;
use MyAAC\Hooks;
use MyAAC\Plugins;
use MyAAC\Models\Town;
use MyAAC\Server\XML\Vocations;
use MyAAC\Settings;
defined('MYAAC') or die('Direct access not allowed!');
@@ -214,3 +215,5 @@ if (count($towns) <= 0) {
config(['towns', $towns]);
unset($towns);
new Vocations();

View File

@@ -12,6 +12,9 @@
* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU Lesser General Public License, Version 3
*/
use MyAAC\Models\Account as AccountModel;
use MyAAC\Models\AccountAction;
/**
* OTServ account abstraction.
*
@@ -40,7 +43,11 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
*/
private $data = array('email' => '', 'rlname' => '','location' => '', 'country' => '','web_flags' => 0, 'lastday' => 0, 'premdays' => 0, 'created' => 0);
public static $cache = array();
private array $columns = ['password', 'email', 'rlname', 'location', 'country', 'web_flags', 'created'];
private array $optionalColumns = ['name', 'number', 'lastday', 'premdays', 'premium_ends_at', 'premend'];
public static array $cache = [];
const GRATIS_PREMIUM_DAYS = 65535;
/**
@@ -325,27 +332,50 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
*/
public function save()
{
if( !isset($this->data['id']) )
{
if (!isset($this->data['id'])) {
throw new E_OTS_NotLoaded();
}
$field = 'lastday';
if($this->db->hasColumn('accounts', 'premend')) { // othire
$field = 'premend';
if(!isset($this->data['premend'])) {
$this->data['premend'] = 0;
}
}
else if($this->db->hasColumn('accounts', 'premium_ends_at')) {
$field = 'premium_ends_at';
if(!isset($this->data['premium_ends_at'])) {
$this->data['premium_ends_at'] = 0;
$defaultValues = [
'premium_ends_at' => 0,
'lastday' => 0,
'premend' => 0,
'premdays' => 0,
];
foreach ($defaultValues as $key => $value) {
if (!isset($this->data[$key])) {
$this->data[$key] = $value;
}
}
// UPDATE query on database
$this->db->exec('UPDATE `accounts` SET ' . ($this->db->hasColumn('accounts', 'name') ? '`name` = ' . $this->db->quote($this->data['name']) . ',' : '') . '`password` = ' . $this->db->quote($this->data['password']) . ', `email` = ' . $this->db->quote($this->data['email']) . ', `rlname` = ' . $this->db->quote($this->data['rlname']) . ', `location` = ' . $this->db->quote($this->data['location']) . ', `country` = ' . $this->db->quote($this->data['country']) . ', `web_flags` = ' . (int) $this->data['web_flags'] . ', ' . ($this->db->hasColumn('accounts', 'premdays') ? '`premdays` = ' . (int) $this->data['premdays'] . ',' : '') . '`' . $field . '` = ' . (int) $this->data[$field] . ' WHERE `id` = ' . $this->data['id']);
$columns = $this->columns;
foreach ($this->optionalColumns as $column) {
if ($this->db->hasColumn('accounts', $column)) {
$columns[] = $column;
}
}
$values = [];
foreach ($columns as $column) {
$value = $this->data[$column];
$values[$column] = $value;
}
// updates existing player
if( isset($this->data['id']) ) {
AccountModel::where('id', $this->data['id'])->update($values);
}
// creates new player
else {
$values['created'] = time();
$account = AccountModel::create($values);
// ID of new player
$this->data['id'] = $account->id;
}
}
/**
@@ -504,11 +534,17 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
* @since 0.7.5
* @throws E_OTS_NotLoaded If account is not loaded.
*/
public function setPremDays($premdays)
public function setPremDays($premdays): void
{
$this->data['premdays'] = (int) $premdays;
$this->data['premend'] = time() + ($premdays * 24 * 60 * 60);
$this->data['premium_ends_at'] = time() + ($premdays * 24 * 60 * 60);
$premiumTimeInSeconds = time() + ($premdays * 24 * 60 * 60);
$this->data['premend'] = $premiumTimeInSeconds;
$this->data['premium_ends_at'] = $premiumTimeInSeconds;
if (isCanary()) {
$this->data['lastday'] = $premiumTimeInSeconds;
}
}
public function setRLName($name)
@@ -1007,26 +1043,16 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
public function logAction($action)
{
$ip = get_browser_real_ip();
if(!str_contains($ip, ":")) {
$ipv6 = '0';
}
else {
$ipv6 = $ip;
$ip = '';
AccountAction::create([
'account_id' => $this->getId(),
'ip' => get_browser_real_ip(),
'date' => time(),
'action' => $action,
]);
}
return $this->db->exec('INSERT INTO `' . TABLE_PREFIX . 'account_actions` (`account_id`, `ip`, `ipv6`, `date`, `action`) VALUES (' . $this->db->quote($this->getId()).', ' . ($ip == '' ? '0' : $this->db->quote(ip2long($ip))) . ', (' . ($ipv6 == '0' ? $this->db->quote('') : $this->db->quote(inet_pton($ipv6))) . '), UNIX_TIMESTAMP(NOW()), ' . $this->db->quote($action).')');
}
public function getActionsLog($limit1, $limit2)
{
$actions = array();
foreach($this->db->query('SELECT `ip`, `ipv6`, `date`, `action` FROM `' . TABLE_PREFIX . 'account_actions` WHERE `account_id` = ' . $this->data['id'] . ' ORDER by `date` DESC LIMIT ' . $limit1 . ', ' . $limit2 . '')->fetchAll() as $a)
$actions[] = array('ip' => $a['ip'], 'ipv6' => $a['ipv6'], 'date' => $a['date'], 'action' => $a['action']);
return $actions;
public function getActionsLog($limit) {
return AccountAction::where('account_id', $this->data['id'])->orderByDesc('date')->limit($limit)->get()->toArray();
}
/**
* Returns players iterator.

View File

@@ -26,6 +26,7 @@ use MyAAC\Cache\Cache;
*/
class OTS_DB_MySQL extends OTS_Base_DB
{
private bool $hasCacheChanged = false;
private array $has_table_cache = [];
private array $has_column_cache = [];
private array $get_column_info_cache = [];
@@ -164,7 +165,7 @@ class OTS_DB_MySQL extends OTS_Base_DB
$cache->delete('database_columns_info');
$cache->delete('database_checksum');
}
else {
else if ($this->hasCacheChanged) {
$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);
@@ -228,6 +229,8 @@ class OTS_DB_MySQL extends OTS_Base_DB
private function hasTableInternal($name): bool
{
$this->hasCacheChanged = true;
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);
}
@@ -241,6 +244,8 @@ class OTS_DB_MySQL extends OTS_Base_DB
}
private function hasColumnInternal($table, $column): bool {
$this->hasCacheChanged = true;
return $this->hasTable($table) && ($this->has_column_cache[$table . '.' . $column] = count($this->query('SHOW COLUMNS FROM `' . $table . "` LIKE " . $this->quote($column))->fetchAll()) > 0);
}
@@ -272,11 +277,14 @@ class OTS_DB_MySQL extends OTS_Base_DB
return false;
}
$this->hasCacheChanged = true;
$formatResult = function ($result) {
return [
'field' => $result['Field'],
'type' => $result['Type'],
'null' => strtolower($result['Null']),
'key' => strtolower($result['Key'] ?? ''),
'default' => $result['Default'],
'extra' => $result['Extra'],
];

View File

@@ -1,20 +1,6 @@
<?php
$__load = array();
/*
'loss_experience' => NULL,
'loss_items' => NULL,
'guild_info' => NULL,
'skull_type' => NULL,
'skull_time' => NULL,
'blessings' => NULL,
'direction' => NULL,
'stamina' => NULL,
'world_id' => NULL,
'online' => NULL,
'deletion' => NULL,
'promotion' => NULL,
'marriage' => NULL
);*/
use MyAAC\Models\Player as PlayerModel;
/**#@+
* @version 0.0.1
@@ -109,6 +95,10 @@ class OTS_Player extends OTS_Row_DAO
POT::SKILL_FISH => array('value' => 0, 'tries' => 0)
);
private array $columns = ['name', 'account_id', 'group_id', 'sex', 'vocation', 'experience', 'level', 'maglevel', 'health', 'healthmax', 'mana', 'manamax', 'manaspent', 'soul', 'lookbody', 'lookfeet', 'lookhead', 'looklegs', 'looktype', 'posx', 'posy', 'posz', 'lastlogin', 'lastlogout', 'lastip', 'town_id', 'balance', 'created', 'comment', 'hide'];
private array $optionalColumns = ['cap', 'skull', 'skull_type', 'skull_time', 'loss_experience', 'loss_mana', 'loss_skills', 'loss_items', 'loss_containers', 'guildnick', 'rank_id', 'promotion', 'direction', 'blessings', 'stamina', 'lookaddons', 'save', 'conditions', 'world_id', 'online', 'deletion', 'deleted', 'marriage'];
private static array $playersOnline;
/**
* Magic PHP5 method.
@@ -133,90 +123,14 @@ class OTS_Player extends OTS_Row_DAO
*/
public function load($id, $fields = null, $load_skills = true)
{
global $__load;
if(!isset($__load['loss_experience']))
{
$loss = '';
if($this->db->hasColumn('players', 'loss_experience')) {
$loss = ', `loss_experience`, `loss_mana`, `loss_skills`';
$columns = $this->columns;
foreach ($this->optionalColumns as $column) {
if ($this->db->hasColumn('players', $column)) {
$columns[] = $column;
}
$__load['loss_experience'] = $loss;
}
if(!isset($__load['loss_items']))
{
$loss_items = '';
if($this->db->hasColumn('players', 'loss_items')) {
$loss_items = ', `loss_items`, `loss_containers`';
}
$__load['loss_items'] = $loss_items;
}
if(!isset($__load['guild_info']))
{
$guild_info = '';
if(!$this->db->hasTable('guild_members') && $this->db->hasColumn('players', 'guildnick')) {
$guild_info = ', `guildnick`, `rank_id`';
}
$__load['guild_info'] = $guild_info;
}
if(!isset($__load['skull_type']))
{
$skull_type = 'skull';
if($this->db->hasColumn('players', 'skull_type')) {
$skull_type = 'skull_type';
}
$__load['skull_type'] = $skull_type;
}
if(!isset($__load['skull_time']))
{
$skull_time = 'skulltime';
if($this->db->hasColumn('players', 'skull_time')) {
$skull_time = 'skull_time';
}
$__load['skull_time'] = $skull_time;
}
if(!isset($__load['blessings'])) {
$__load['blessings'] = $this->db->hasColumn('players', 'blessings');
}
if(!isset($__load['direction'])) {
$__load['direction'] = $this->db->hasColumn('players', 'direction');
}
if(!isset($__load['stamina'])) {
$__load['stamina'] = $this->db->hasColumn('players', 'stamina');
}
if(!isset($__load['world_id'])) {
$__load['world_id'] = $this->db->hasColumn('players', 'world_id');
}
if(!isset($__load['online'])) {
$__load['online'] = $this->db->hasColumn('players', 'online');
}
if(!isset($__load['deletion'])) {
$__load['deletion'] = $this->db->hasColumn('players', 'deletion');
}
if(!isset($__load['promotion'])) {
$__load['promotion'] = $this->db->hasColumn('players', 'promotion');
}
if(!isset($__load['marriage'])) {
$__load['marriage'] = $this->db->hasColumn('players', 'marriage');
}
if(isset($fields)) { // load only what we wish
if(in_array('promotion', $fields)) {
if(!$this->db->hasColumn('players', 'promotion')) {
unset($fields[array_search('promotion', $fields)]);
}
}
if(in_array('deleted', $fields)) {
if($this->db->hasColumn('players', 'deletion')) {
unset($fields[array_search('deleted', $fields)]);
@@ -224,21 +138,21 @@ class OTS_Player extends OTS_Row_DAO
}
}
if(in_array('online', $fields)) {
if(!$this->db->hasColumn('players', 'online')) {
unset($fields[array_search('online', $fields)]);
$columns = [];
foreach ($fields as $field) {
if ($this->db->hasColumn('players', $field)) {
$columns[] = $field;
}
}
$this->data = $this->db->query('SELECT ' . implode(', ', $fields) . ' FROM `players` WHERE `id` = ' . (int)$id)->fetch();
}
else {
// SELECT query on database
$this->data = $this->db->query('SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`' . ($this->db->hasColumn('players', 'lookaddons') ? ', `lookaddons`' : '') . ', `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `save`, `conditions`, `' . $__load['skull_time'] . '` as `skulltime`, `' . $__load['skull_type'] . '` as `skull`' . $__load['guild_info'] . ', `town_id`' . $__load['loss_experience'] . $__load['loss_items'] . ', `balance`' . ($__load['blessings'] ? ', `blessings`' : '') . ($__load['direction'] ? ', `direction`' : '') . ($__load['stamina'] ? ', `stamina`' : '') . ($__load['world_id'] ? ', `world_id`' : '') . ($__load['online'] ? ', `online`' : '') . ', `' . ($__load['deletion'] ? 'deletion' : 'deleted') . '`' . ($__load['promotion'] ? ', `promotion`' : '') . ($__load['marriage'] ? ', `marriage`' : '') . ', `comment`, `created`, `hide` FROM `players` WHERE `id` = ' . (int)$id)->fetch();
}
array_unshift($columns, 'id');
$query = PlayerModel::where('id', $id)->first($columns);
$this->data = $query ? $query->toArray() : [];
// loads skills
if( $this->isLoaded() && $load_skills)
{
if( $this->isLoaded() && $load_skills) {
if($this->db->hasColumn('players', 'skill_fist')) {
$skill_ids = array(
@@ -318,153 +232,65 @@ class OTS_Player extends OTS_Row_DAO
*/
public function save()
{
$skull_type = 'skull';
if($this->db->hasColumn('players', 'skull_type')) {
$skull_type = 'skull_type';
$defaultValues = [
'cap' => 0,
'skull' => 0,
'skull_type' => 0,
'skull_time' => 0,
'loss_experience' => 100,
'loss_mana' => 100,
'loss_skills' => 100,
'loss_items' => 100,
'loss_containers' => 100,
'guildnick' => '',
'rank_id' => 0,
'promotion' => 0,
'direction' => 0,
'blessings' => 0,
'stamina' => 0,
'lookaddons' => 0,
'save' => 1,
'conditions' => '',
'town_id' => 1,
'world_id' => 1,
'online' => 0,
'deletion' => 0,
'deleted' => 0,
'marriage' => 0,
];
foreach ($defaultValues as $key => $value) {
if (!isset($this->data[$key])) {
$this->data[$key] = $value;
}
}
$skull_time = 'skulltime';
if($this->db->hasColumn('players', 'skull_time')) {
$skull_time = 'skull_time';
$columns = $this->columns;
foreach ($this->optionalColumns as $column) {
if ($this->db->hasColumn('players', $column)) {
$columns[] = $column;
}
}
if(!isset($this->data['loss_experience']))
$this->data['loss_experience'] = 100;
$values = [];
foreach ($columns as $column) {
$value = $this->data[$column];
if(!isset($this->data['loss_mana']))
$this->data['loss_mana'] = 100;
if(!isset($this->data['loss_skills']))
$this->data['loss_skills'] = 100;
if(!isset($this->data['loss_items']))
$this->data['loss_items'] = 10;
if(!isset($this->data['loss_containers']))
$this->data['loss_containers'] = 100;
if(!isset($this->data['guildnick']))
$this->data['guildnick'] = '';
if(!isset($this->data['rank_id']))
$this->data['rank_id'] = 0;
if(!isset($this->data['promotion']))
$this->data['promotion'] = 0;
if(!isset($this->data['direction']))
$this->data['direction'] = 0;
if(!isset($this->data['conditions']))
$this->data['conditions'] = '';
if(!isset($this->data['town_id']))
$this->data['town_id'] = 1;
$values[$column] = $value;
}
// updates existing player
if( isset($this->data['id']) )
{
$loss = '';
if($this->db->hasColumn('players', 'loss_experience')) {
$loss = ', `loss_experience` = ' . $this->data['loss_experience'] . ', `loss_mana` = ' . $this->data['loss_mana'] . ', `loss_skills` = ' . $this->data['loss_skills'];
}
$loss_items = '';
if($this->db->hasColumn('players', 'loss_items')) {
$loss_items = ', `loss_items` = ' . $this->data['loss_items'] . ', `loss_containers` = ' . $this->data['loss_containers'];
}
$guild_info = '';
if(!$this->db->hasTable('guild_members') && $this->db->hasColumn('players', 'guildnick')) {
$guild_info = ', `guildnick` = ' . $this->db->quote($this->data['guildnick']) . ', ' . $this->db->fieldName('rank_id') . ' = ' . $this->data['rank_id'];
}
$direction = '';
if($this->db->hasColumn('players', 'direction')) {
$direction = ', `direction` = ' . $this->db->quote($this->data['direction']);
}
$blessings = '';
if($this->db->hasColumn('players', 'blessings')) {
$blessings = ', `blessings` = ' . $this->db->quote($this->data['blessings']);
}
$stamina = '';
if($this->db->hasColumn('players', 'stamina')) {
$stamina = ', `stamina` = ' . $this->db->quote($this->data['stamina']);
}
$lookaddons = '';
if($this->db->hasColumn('players', 'lookaddons')) {
$lookaddons = ', `lookaddons` = ' . $this->db->quote($this->data['lookaddons']);
}
// UPDATE query on database
$this->db->query('UPDATE ' . $this->db->tableName('players') . ' SET ' . $this->db->fieldName('name') . ' = ' . $this->db->quote($this->data['name']) . ', ' . $this->db->fieldName('account_id') . ' = ' . $this->data['account_id'] . ', ' . $this->db->fieldName('group_id') . ' = ' . $this->data['group_id'] . ', ' . $this->db->fieldName('sex') . ' = ' . $this->data['sex'] . ', ' . $this->db->fieldName('vocation') . ' = ' . $this->data['vocation'] . ', ' . $this->db->fieldName('experience') . ' = ' . $this->data['experience'] . ', ' . $this->db->fieldName('level') . ' = ' . $this->data['level'] . ', ' . $this->db->fieldName('maglevel') . ' = ' . $this->data['maglevel'] . ', ' . $this->db->fieldName('health') . ' = ' . $this->data['health'] . ', ' . $this->db->fieldName('healthmax') . ' = ' . $this->data['healthmax'] . ', ' . $this->db->fieldName('mana') . ' = ' . $this->data['mana'] . ', ' . $this->db->fieldName('manamax') . ' = ' . $this->data['manamax'] . ', ' . $this->db->fieldName('manaspent') . ' = ' . $this->data['manaspent'] . ', ' . $this->db->fieldName('soul') . ' = ' . $this->data['soul'] . ', ' . $this->db->fieldName('lookbody') . ' = ' . $this->data['lookbody'] . ', ' . $this->db->fieldName('lookfeet') . ' = ' . $this->data['lookfeet'] . ', ' . $this->db->fieldName('lookhead') . ' = ' . $this->data['lookhead'] . ', ' . $this->db->fieldName('looklegs') . ' = ' . $this->data['looklegs'] . ', ' . $this->db->fieldName('looktype') . ' = ' . $this->data['looktype'] . $lookaddons . ', ' . $this->db->fieldName('posx') . ' = ' . $this->data['posx'] . ', ' . $this->db->fieldName('posy') . ' = ' . $this->data['posy'] . ', ' . $this->db->fieldName('posz') . ' = ' . $this->data['posz'] . ', ' . $this->db->fieldName('cap') . ' = ' . $this->data['cap'] . ', ' . $this->db->fieldName('lastlogin') . ' = ' . $this->data['lastlogin'] . ', ' . $this->db->fieldName('lastlogout') . ' = ' . $this->data['lastlogout'] . ', ' . $this->db->fieldName('lastip') . ' = ' . $this->db->quote($this->data['lastip']) . ', ' . $this->db->fieldName('save') . ' = ' . (int) $this->data['save'] . ', ' . $this->db->fieldName('conditions') . ' = ' . $this->db->quote($this->data['conditions']) . ', `' . $skull_time . '` = ' . $this->data['skulltime'] . ', `' . $skull_type . '` = ' . (int) $this->data['skull'] . $guild_info . ', ' . $this->db->fieldName('town_id') . ' = ' . $this->data['town_id'] . $loss . $loss_items . ', ' . $this->db->fieldName('balance') . ' = ' . $this->data['balance'] . $blessings . $stamina . $direction . ' WHERE ' . $this->db->fieldName('id') . ' = ' . $this->data['id']);
if( isset($this->data['id']) ) {
PlayerModel::where('id', $this->data['id'])->update($values);
}
// creates new player
else
{
$loss = '';
$loss_data = '';
if($this->db->hasColumn('players', 'loss_experience')) {
$loss = ', `loss_experience`, `loss_mana`, `loss_skills`';
$loss_data = ', ' . $this->data['loss_experience'] . ', ' . $this->data['loss_mana'] . ', ' . $this->data['loss_skills'];
}
else {
$values['created'] = time();
$loss_items = '';
$loss_items_data = '';
if($this->db->hasColumn('players', 'loss_items')) {
$loss_items = ', `loss_items`, `loss_containers`';
$loss_items_data = ', ' . $this->data['loss_items'] . ', ' . $this->data['loss_containers'];
}
$player = PlayerModel::create($values);
$guild_info = '';
$guild_info_data = '';
if(!$this->db->hasTable('guild_members') && $this->db->hasColumn('players', 'guildnick')) {
$guild_info = ', `guildnick`, `rank_id`';
$guild_info_data = ', ' . $this->db->quote($this->data['guildnick']) . ', ' . $this->data['rank_id'];
}
$promotion = '';
$promotion_data = '';
if($this->db->hasColumn('players', 'promotion')) {
$promotion = ', `promotion`';
$promotion_data = ', ' . $this->data['promotion'];
}
$direction = '';
$direction_data = '';
if($this->db->hasColumn('players', 'direction')) {
$direction = ', `direction`';
$direction_data = ', ' . $this->data['direction'];
}
$blessings = '';
$blessings_data = '';
if($this->db->hasColumn('players', 'blessings')) {
$blessings = ', `blessings`';
$blessings_data = ', ' . $this->data['blessings'];
}
$stamina = '';
$stamina_data = '';
if($this->db->hasColumn('players', 'stamina')) {
$stamina = ', `stamina`';
$stamina_data = ', ' . $this->data['stamina'];
}
$lookaddons = '';
$lookaddons_data = '';
if($this->db->hasColumn('players', 'lookaddons')) {
$lookaddons = ', `lookaddons`';
$lookaddons_data = ', ' . $this->data['lookaddons'];
}
// INSERT query on database
$this->db->query('INSERT INTO `players` (`name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`' . $lookaddons . ', `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `save`, `conditions`, `' . $skull_time . '`, `' . $skull_type . '`' . $guild_info . ', `town_id`' . $loss . $loss_items . ', `balance`' . $blessings . $stamina . $direction . ', `created`' . $promotion . ', `comment`) VALUES (' . $this->db->quote($this->data['name']) . ', ' . $this->data['account_id'] . ', ' . $this->data['group_id'] . ', ' . $this->data['sex'] . ', ' . $this->data['vocation'] . ', ' . $this->data['experience'] . ', ' . $this->data['level'] . ', ' . $this->data['maglevel'] . ', ' . $this->data['health'] . ', ' . $this->data['healthmax'] . ', ' . $this->data['mana'] . ', ' . $this->data['manamax'] . ', ' . $this->data['manaspent'] . ', ' . $this->data['soul'] . ', ' . $this->data['lookbody'] . ', ' . $this->data['lookfeet'] . ', ' . $this->data['lookhead'] . ', ' . $this->data['looklegs'] . ', ' . $this->data['looktype'] . $lookaddons_data . ', ' . $this->data['posx'] . ', ' . $this->data['posy'] . ', ' . $this->data['posz'] . ', ' . $this->data['cap'] . ', ' . $this->data['lastlogin'] . ', ' . $this->data['lastlogout'] . ', ' . $this->data['lastip'] . ', ' . (int) $this->data['save'] . ', ' . $this->db->quote($this->data['conditions']) . ', ' . $this->data['skulltime'] . ', ' . (int) $this->data['skull'] . $guild_info_data . ', ' . $this->data['town_id'] . $loss_data . $loss_items_data . ', ' . $this->data['balance'] . $blessings_data . $stamina_data . $direction_data . ', ' . time() . $promotion_data . ', "")');
// ID of new group
$this->data['id'] = $this->db->lastInsertId();
// ID of new player
$this->data['id'] = $player->id;
}
// updates skills - doesn't matter if we have just created character - trigger inserts new skills
@@ -490,7 +316,7 @@ class OTS_Player extends OTS_Row_DAO
$set .= ',';
}
$skills = $this->db->query('UPDATE `players` SET ' . $set . ' WHERE `id` = ' . $this->data['id']);
$this->db->query('UPDATE `players` SET ' . $set . ' WHERE `id` = ' . $this->data['id']);
}
else if($this->db->hasTable('player_skills')) {
foreach($this->skills as $id => $skill)
@@ -748,21 +574,25 @@ class OTS_Player extends OTS_Row_DAO
public function isDeleted()
{
$field = 'deleted';
$column = 'deleted';
if($this->db->hasColumn('players', 'deletion'))
$field = 'deletion';
$column = 'deletion';
if( !isset($this->data[$field]) )
if( !isset($this->data[$column]) )
{
throw new E_OTS_NotLoaded();
}
return $this->data[$field] > 0;
return $this->data[$column] > 0;
}
public function setDeleted($deleted)
{
$this->data['deleted'] = (int) $deleted;
$column = 'deleted';
if($this->db->hasColumn('players', 'deletion'))
$column = 'deletion';
$this->data[$column] = (int) $deleted;
}
public function isOnline()
@@ -852,13 +682,7 @@ class OTS_Player extends OTS_Row_DAO
throw new E_OTS_NotLoaded();
}
if(isset($this->data['promotion'])) {
global $config;
if((int)$this->data['promotion'] > 0)
return ($this->data['vocation'] + ($this->data['promotion'] * $config['vocations_amount']));
}
return $this->data['vocation'];
return \OTS_Toolbox::getVocationFromPromotion($this->data['vocation'], $this->data['promotion'] ?? 0);
}
@@ -1574,12 +1398,7 @@ class OTS_Player extends OTS_Row_DAO
*/
public function getCap()
{
if( !isset($this->data['cap']) )
{
throw new E_OTS_NotLoaded();
}
return $this->data['cap'];
return $this->data['cap'] ?? 0;
}
/**
@@ -1792,12 +1611,12 @@ class OTS_Player extends OTS_Row_DAO
*/
public function getSkullTime()
{
if( !isset($this->data['skulltime']) )
{
throw new E_OTS_NotLoaded();
$column = 'skulltime';
if($this->db->hasColumn('players', 'skull_time')) {
$column = 'skull_time';
}
return $this->data['skulltime'];
return $this->data[$column] ?? 0;
}
/**
@@ -1811,7 +1630,12 @@ class OTS_Player extends OTS_Row_DAO
*/
public function setSkullTime($skulltime)
{
$this->data['skulltime'] = (int) $skulltime;
$column = 'skulltime';
if($this->db->hasColumn('players', 'skull_time')) {
$column = 'skull_time';
}
$this->data[$column] = (int) $skulltime;
}
/**
@@ -3250,6 +3074,10 @@ class OTS_Player extends OTS_Row_DAO
return 0;
}
public function setData(array $data): void{
$this->data = $data;
}
/**
* Magic PHP5 method.
*

View File

@@ -13,6 +13,8 @@
* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU Lesser General Public License, Version 3
*/
use MyAAC\Server\XML\Vocations;
/**
* Toolbox for common operations.
*
@@ -110,14 +112,21 @@ class OTS_Toolbox
$list->setFilter($filter);
return $list;
}
public static function getVocationName($id, $promotion = 0): string
public static function getVocationFromPromotion($id, $promotion = 0): int
{
if($promotion > 0) {
$id = ($id + ($promotion * config('vocations_amount')));
for ($i = 0; $i < $promotion; $i++) {
if ($_id = Vocations::getPromoted($id)) {
$id = $_id;
}
}
}
return config('vocations')[$id] ?? 'Unknown';
return $id;
}
public static function getVocationName($id, $promotion = 0): string {
return config('vocations')[self::getVocationFromPromotion($id, $promotion)] ?? 'Unknown';
}
}

View File

@@ -1,5 +1,6 @@
<?php
use MyAAC\Models\Player as PlayerModel;
use MyAAC\Settings;
function updateHighscoresIdsHidden(): void
@@ -10,12 +11,22 @@ function updateHighscoresIdsHidden(): void
return;
}
$query = $db->query("SELECT `id` FROM `players` WHERE (`name` = " . $db->quote("Rook Sample") . " OR `name` = " . $db->quote("Sorcerer Sample") . " OR `name` = " . $db->quote("Druid Sample") . " OR `name` = " . $db->quote("Paladin Sample") . " OR `name` = " . $db->quote("Knight Sample") . " OR `name` = " . $db->quote("Account Manager") . ") ORDER BY `id`;");
$players = PlayerModel::where('name', 'Rook Sample')
->orWhere('name', 'Sorcerer Sample')
->orWhere('name', 'Druid Sample')
->orWhere('name', 'Paladin Sample')
->orWhere('name', 'Knight Sample')
->orWhere('name', 'Monk Sample')
->orWhere('name', 'Account Manager')
->orderBy('id')
->select('id')
->get();
$highscores_ignored_ids = array();
if ($query->rowCount() > 0) {
foreach ($query->fetchAll() as $result)
$highscores_ignored_ids[] = $result['id'];
$highscores_ignored_ids = [];
if (count($players) > 0) {
foreach ($players as $result) {
$highscores_ignored_ids[] = $result->id;
}
} else {
$highscores_ignored_ids[] = 0;
}

42
system/migrations/47.php Normal file
View File

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

16
system/migrations/48.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
/**
* @var OTS_DB_MySQL $db
*/
$up = function () use ($db) {
if (!$db->hasColumn(TABLE_PREFIX . 'menu', 'access')) {
$db->addColumn(TABLE_PREFIX . 'menu', 'access', 'TINYINT NOT NULL DEFAULT 0 AFTER `link`');
}
};
$down = function () use ($db) {
if ($db->hasColumn(TABLE_PREFIX . 'menu', 'access')) {
$db->dropColumn(TABLE_PREFIX . 'menu', 'access');
}
};

91
system/migrations/49.php Normal file
View File

@@ -0,0 +1,91 @@
<?php
/**
* @var OTS_DB_MySQL $db
*/
use MyAAC\Models\Account as AccountModel;
$time = time();
$accountId = getSession('account') ?? 1;
if (!defined('MYAAC_INSTALL')) {
$accountModel = AccountModel::where('web_flags', 3)->first();
if ($accountModel) {
$accountId = $accountModel->id;
}
}
function insert_sample_if_not_exist($p): void
{
global $time, $accountId;
$player = new OTS_Player();
$player->find($p['name']);
if (!$player->isLoaded()) {
$player->setData([
'name' => $p['name'],
'group_id' => 1,
'account_id' => $accountId,
'level' => $p['level'],
'vocation' => $p['vocation_id'],
'health' => $p['health'],
'healthmax' => $p['healthmax'],
'experience' => $p['experience'],
'lookbody' => 118,
'lookfeet' => 114,
'lookhead' => 38,
'looklegs' => 57,
'looktype' => $p['looktype'],
'maglevel' => 0,
'mana' => $p['mana'],
'manamax' => $p['manamax'],
'manaspent' => 0,
'soul' => $p['soul'],
'town_id' => 1,
'posx' => 1000,
'posy' => 1000,
'posz' => 7,
'conditions' => '',
'cap' => $p['cap'],
'sex' => 1,
'lastlogin' => $time,
'lastip' => 2130706433,
'save' => 1,
'lastlogout' => $time,
'balance' => 0,
'created' => $time,
'hide' => 1,
'comment' => '',
]);
$player->save();
}
}
$up = function () use ($db) {
if (!$db->hasTable('players')) {
return;
}
insert_sample_if_not_exist(['name' => 'Rook Sample', 'level' => 1, 'vocation_id' => 0, 'health' => 150, 'healthmax' => 150, 'experience' => 0, 'looktype' => 130, 'mana' => 0, 'manamax' => 0, 'soul' => 100, 'cap' => 400]);
insert_sample_if_not_exist(['name' => 'Sorcerer Sample', 'level' => 8, 'vocation_id' => 1, 'health' => 185, 'healthmax' => 185, 'experience' => 4200, 'looktype' => 130, 'mana' => 90, 'manamax' => 90, 'soul' => 100, 'cap' => 470]);
insert_sample_if_not_exist(['name' => 'Druid Sample', 'level' => 8, 'vocation_id' => 2, 'health' => 185, 'healthmax' => 185, 'experience' => 4200, 'looktype' => 130, 'mana' => 90, 'manamax' => 90, 'soul' => 100, 'cap' => 470]);
insert_sample_if_not_exist(['name' => 'Paladin Sample', 'level' => 8, 'vocation_id' => 3, 'health' => 185, 'healthmax' => 185, 'experience' => 4200, 'looktype' => 129, 'mana' => 90, 'manamax' => 90, 'soul' => 100, 'cap' => 470]);
insert_sample_if_not_exist(['name' => 'Knight Sample', 'level' => 8, 'vocation_id' => 4, 'health' => 185, 'healthmax' => 185, 'experience' => 4200, 'looktype' => 131, 'mana' => 90, 'manamax' => 90, 'soul' => 100, 'cap' => 470]);
insert_sample_if_not_exist(['name' => 'Monk Sample', 'level' => 8, 'vocation_id' => 9, 'health' => 185, 'healthmax' => 185, 'experience' => 4200, 'looktype' => 128, 'mana' => 90, 'manamax' => 90, 'soul' => 100, 'cap' => 470]);
if (defined('MYAAC_INSTALL')) {
global $locale;
success($locale['step_database_imported_players']);
}
require_once __DIR__ . '/20.php';
updateHighscoresIdsHidden();
};
$down = function () {
// nothing
};

View File

@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS `myaac_gallery`
(
`id` int NOT NULL AUTO_INCREMENT,
`comment` varchar(255) NOT NULL DEFAULT '',
`image` varchar(255) NOT NULL,
`thumb` varchar(255) NOT NULL,
`author` varchar(50) NOT NULL DEFAULT '',
`ordering` int NOT NULL DEFAULT 0,
`hide` tinyint NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;

16
system/migrations/50.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
/**
* @var OTS_DB_MySQL $db
*/
$up = function () use ($db) {
if ($db->hasTable(TABLE_PREFIX . 'gallery')) {
$db->dropTable(TABLE_PREFIX . 'gallery');
}
};
$down = function () use ($db) {
if (!$db->hasTable(TABLE_PREFIX . 'gallery')) {
$db->query(file_get_contents(__DIR__ . '/50-gallery.sql'));
}
};

View File

@@ -0,0 +1,29 @@
<?php
/**
* Example of using getTopPlayers() function
* to display the best players for each skill
*/
defined('MYAAC') or die('Direct access not allowed!');
$skills = [
'magic', 'level',
'balance', 'frags',
POT::SKILL_FIST, POT::SKILL_CLUB,
POT::SKILL_SWORD, POT::SKILL_AXE,
POT::SKILL_DISTANCE, POT::SKILL_SHIELD,
POT::SKILL_FISH
];
foreach ($skills as $skill) {?>
<ul>
<?php
echo '<strong>' . ucwords(is_string($skill) ? $skill : getSkillName($skill)) . '</strong>';
foreach (getTopPlayers(5, $skill) as $player) {?>
<li><?= $player['rank'] . '. ' . $player['name'] . ' - ' . $player['value']; ?></li>
<?php
}
?>
</ul>
<?php
}

View File

@@ -10,6 +10,7 @@
*/
use MyAAC\CreateCharacter;
use MyAAC\Models\AccountAction;
use MyAAC\Models\AccountEmailVerify;
defined('MYAAC') or die('Direct access not allowed!');
@@ -44,6 +45,16 @@ $errors = array();
$save = isset($_POST['save']) && $_POST['save'] == 1;
if($save)
{
$cooldown = setting('core.account_create_ip_block_cooldown');;
if ($cooldown > 0) {
$accountAction = AccountAction::where('ip', get_browser_real_ip())->where('action', 'Account created.')->where('date', '>=', time() - ($cooldown * 60))->first();
if ($accountAction) {
$minute = ($cooldown > 1 ? 'minutes' : 'minute');
$errors['account'] = "You have to wait $cooldown $minute before creating another account.";
}
}
if(!config('account_login_by_email')) {
if(USE_ACCOUNT_NAME) {
$account_name = $_POST['account'];
@@ -140,7 +151,7 @@ if($save)
'country' => $country,
'password' => $password,
'password_confirm' => $password_confirm,
'accept_rules' => isset($_POST['accept_rules']) ? $_POST['accept_rules'] === 'true' : false,
'accept_rules' => isset($_POST['accept_rules']) && $_POST['accept_rules'] === 'true',
);
if (!config('account_login_by_email')) {
@@ -192,6 +203,21 @@ if($save)
$new_account->setPassword(encrypt($password));
$new_account->setEMail($email);
$settingAccountPremiumDays = setting('core.account_premium_days');
if($settingAccountPremiumDays && $settingAccountPremiumDays > 0) {
$new_account->setPremDays($settingAccountPremiumDays);
if (!isCanary()) {
$lastDay = 0;
if($settingAccountPremiumDays != 0 && $settingAccountPremiumDays != OTS_Account::GRATIS_PREMIUM_DAYS) {
$lastDay = time();
}
$new_account->setLastLogin($lastDay);
}
}
$new_account->save();
$hooks->trigger(HOOK_ACCOUNT_CREATE_AFTER_SAVED, ['account' => $new_account]);
@@ -206,22 +232,6 @@ if($save)
$new_account->setCustomField('country', $country);
}
$settingAccountPremiumDays = setting('core.account_premium_days');
if($settingAccountPremiumDays && $settingAccountPremiumDays > 0) {
if($db->hasColumn('accounts', 'premend')) { // othire
$new_account->setCustomField('premend', time() + $settingAccountPremiumDays * 86400);
}
else { // rest
if ($db->hasColumn('accounts', 'premium_ends_at')) { // TFS 1.4+
$new_account->setCustomField('premium_ends_at', time() + $settingAccountPremiumDays * (60 * 60 * 24));
}
else {
$new_account->setCustomField('premdays', $settingAccountPremiumDays);
$new_account->setCustomField('lastday', time());
}
}
}
$accountDefaultPremiumPoints = setting('core.account_premium_points');
if($accountDefaultPremiumPoints > 0) {
$new_account->setCustomField('premium_points', $accountDefaultPremiumPoints);

View File

@@ -9,540 +9,11 @@
* @link https://my-aac.org
*/
defined('MYAAC') or die('Direct access not allowed!');
$title = 'Lost Account Interface';
$title = 'Lost Account';
if(!setting('core.mail_enabled'))
{
echo '<b>Account maker is not configured to send e-mails, you can\'t use Lost Account Interface. Contact with admin to get help.</b>';
if(!setting('core.mail_enabled')) {
echo "<b>Account maker is not configured to send e-mails, you can't use Lost Account Interface. Contact with admin to get help.</b>";
return;
}
$action_type = isset($_REQUEST['action_type']) ? $_REQUEST['action_type'] : '';
if($action == '')
{
$twig->display('account.lost.form.html.twig');
}
else if($action == 'step1' && $action_type == '') {
$twig->display('account.lost.noaction.html.twig');
}
elseif($action == 'step1' && $action_type == 'email')
{
$nick = stripslashes($_REQUEST['nick']);
if(Validator::characterName($nick))
{
$player = new OTS_Player();
$account = new OTS_Account();
$player->find($nick);
if($player->isLoaded())
$account = $player->getAccount();
if($account->isLoaded())
{
if($account->getCustomField('email_next') < time())
echo 'Please enter e-mail to account with this character.<BR>
<form action="' . getLink('account/lost') . '?action=sendcode" method=post>
<input type=hidden name="character">
<table cellspacing=1 cellpadding=4 border=0 width=100%>
<TR><TD BGCOLOR="'.$config['vdarkborder'].'" class="white"><B>Please enter e-mail to account</B></TD></TR>
<TR><TD BGCOLOR="'.$config['darkborder'].'">
Character: <INPUT TYPE=text NAME="nick" VALUE="'.$nick.'" SIZE="40" readonly="readonly"><BR>
E-mail to account:<INPUT TYPE=text NAME="email" VALUE="" SIZE="40"><BR>
</TD></TR>
</TABLE>
<BR>
<TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
' . $twig->render('buttons.submit.html.twig') . '</div>
</TD></TR></FORM></TABLE></TABLE>';
else
{
$insec = (int)$account->getCustomField('email_next') - time();
$minutesleft = floor($insec / 60);
$secondsleft = $insec - ($minutesleft * 60);
$timeleft = $minutesleft.' minutes '.$secondsleft.' seconds';
echo 'Account of selected character (<b>'.$nick.'</b>) received e-mail in last '.ceil(setting('core.mail_lost_account_interval') / 60).' minutes. You must wait '.$timeleft.' before you can use Lost Account Interface again.';
}
}
else
echo 'Player or account of player <b>' . $nick . '</b> doesn\'t exist.';
}
else
echo 'Invalid player name format. If you have other characters on account try with other name.';
echo '<BR /><TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
<a href="' . getLink('account/lost') . '" border="0"><IMG SRC="'.$template_path.'/images/global/buttons/sbutton_back.gif" NAME="Back" ALT="Back" BORDER=0 WIDTH=120 HEIGHT=18></a></div>
</TD></TR></FORM></TABLE></TABLE>';
}
elseif($action == 'sendcode')
{
$email = $_REQUEST['email'];
$nick = stripslashes($_REQUEST['nick']);
if(Validator::characterName($nick))
{
$player = new OTS_Player();
$account = new OTS_Account();
$player->find($nick);
if($player->isLoaded())
$account = $player->getAccount();
if($account->isLoaded())
{
if($account->getCustomField('email_next') < time())
{
if($account->getEMail() == $email)
{
$newcode = generateRandomString(30, true, false, true);
$mailBody = '
You asked to reset your ' . $config['lua']['serverName'] . ' password.<br/>
<p>Account name: '.$account->getName().'</p>
<br />
To do so, please click this link:
<p><a href="' . getLink('account/lost') . '?action=checkcode&code='.$newcode.'&character='.urlencode($nick).'">' . getLink('account/lost') . '?action=checkcode&code='.$newcode.'&character='.urlencode($nick).'</a></p>
<p>or open page: <i>' . getLink('account/lost') . '?action=checkcode</i> and in field "code" write <b>'.$newcode.'</b></p>
<br/>
<p>If you did not request a password change, you may ignore this message and your password will remain unchanged.';
$account_mail = $account->getCustomField('email');
if(_mail($account_mail, $config['lua']['serverName'].' - Recover your account', $mailBody))
{
$account->setCustomField('email_code', $newcode);
$account->setCustomField('email_next', (time() + setting('core.mail_lost_account_interval')));
echo '<br />Details about steps required to recover your account has been sent to <b>' . $account_mail . '</b>. You should receive this email within 15 minutes. Please check your inbox/spam directory.';
}
else
{
$account->setCustomField('email_next', (time() + 60));
echo '<br /><p class="error">An error occurred while sending email! Try again later or contact with admin. For Admin: More info can be found in system/logs/mailer-error.log</p>';
}
}
else
echo 'Invalid e-mail to account of character <b>'.$nick.'</b>. Try again.';
}
else
{
$insec = (int)$account->getCustomField('email_next') - time();
$minutesleft = floor($insec / 60);
$secondsleft = $insec - ($minutesleft * 60);
$timeleft = $minutesleft.' minutes '.$secondsleft.' seconds';
echo 'Account of selected character (<b>'.$nick.'</b>) received e-mail in last '.ceil(setting('core.mail_lost_account_interval') / 60).' minutes. You must wait '.$timeleft.' before you can use Lost Account Interface again.';
}
}
else
echo 'Player or account of player <b>'.$nick.'</b> doesn\'t exist.';
}
else
echo 'Invalid player name format. If you have other characters on account try with other name.';
echo '<BR /><TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
<a href="' . getLink('account/lost') . '?action=step1&action_type=email&nick='.urlencode($nick).'" border="0"><IMG SRC="'.$template_path.'/images/global/buttons/sbutton_back.gif" NAME="Back" ALT="Back" BORDER=0 WIDTH=120 HEIGHT=18></a></div>
</TD></TR></FORM></TABLE></TABLE>';
}
elseif($action == 'step1' && $action_type == 'reckey')
{
$nick = stripslashes($_REQUEST['nick']);
if(Validator::characterName($nick))
{
$player = new OTS_Player();
$account = new OTS_Account();
$player->find($nick);
if($player->isLoaded())
$account = $player->getAccount();
if($account->isLoaded())
{
$account_key = $account->getCustomField('key');
if(!empty($account_key))
{
echo 'If you enter right recovery key you will see form to set new e-mail and password to account. To this e-mail will be send your new password and account name.<BR>
<FORM ACTION="' . getLink('account/lost') . '?action=step2" METHOD=post>
<TABLE CELLSPACING=1 CELLPADDING=4 BORDER=0 WIDTH=100%>
<TR><TD BGCOLOR="'.$config['vdarkborder'].'" class="white"><B>Please enter your recovery key</B></TD></TR>
<TR><TD BGCOLOR="'.$config['darkborder'].'">
Character name:&nbsp;<INPUT TYPE=text NAME="nick" VALUE="'.$nick.'" SIZE="40" readonly="readonly"><BR />
Recovery key:&nbsp;&nbsp;&nbsp;&nbsp;<INPUT TYPE=text NAME="key" VALUE="" SIZE="40"><BR>
</TD></TR>
</TABLE>
<BR>
<TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
' . $twig->render('buttons.submit.html.twig') . '</div>
</TD></TR></FORM></TABLE></TABLE>';
}
else
echo 'Account of this character has no recovery key!';
}
else
echo 'Player or account of player <b>'.$nick.'</b> doesn\'t exist.';
}
else
echo 'Invalid player name format. If you have other characters on account try with other name.';
echo '<BR /><TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
<a href="' . getLink('account/lost') . '" border="0"><IMG SRC="'.$template_path.'/images/global/buttons/sbutton_back.gif" NAME="Back" ALT="Back" BORDER=0 WIDTH=120 HEIGHT=18></a></div>
</TD></TR></FORM></TABLE></TABLE>';
}
elseif($action == 'step2')
{
$rec_key = trim($_REQUEST['key']);
$nick = stripslashes($_REQUEST['nick']);
if(Validator::characterName($nick))
{
$player = new OTS_Player();
$account = new OTS_Account();
$player->find($nick);
if($player->isLoaded())
$account = $player->getAccount();
if($account->isLoaded())
{
$account_key = $account->getCustomField('key');
if(!empty($account_key))
{
if($account_key == $rec_key)
{
echo '<script type="text/javascript">
function validate_required(field,alerttxt)
{
with (field)
{
if (value==null||value==""||value==" ")
{alert(alerttxt);return false;}
else {return true}
}
}
function validate_email(field,alerttxt)
{
with (field)
{
apos=value.indexOf("@");
dotpos=value.lastIndexOf(".");
if (apos<1||dotpos-apos<2)
{alert(alerttxt);return false;}
else {return true;}
}
}
function validate_form(thisform)
{
with (thisform)
{
if (validate_required(email,"Please enter your e-mail!")==false)
{email.focus();return false;}
if (validate_email(email,"Invalid e-mail format!")==false)
{email.focus();return false;}
if (validate_required(passor,"Please enter password!")==false)
{passor.focus();return false;}
if (validate_required(passor2,"Please repeat password!")==false)
{passor2.focus();return false;}
if (passor2.value!=passor.value)
{alert(\'Repeated password is not equal to password!\');return false;}
}
}
</script>';
echo 'Set new password and e-mail to your account.<BR>
<FORM ACTION="' . getLink('account/lost') . '?action=step3" onsubmit="return validate_form(this)" METHOD=post>
<INPUT TYPE=hidden NAME="character" VALUE="">
<TABLE CELLSPACING=1 CELLPADDING=4 BORDER=0 WIDTH=100%>
<TR><TD BGCOLOR="'.$config['vdarkborder'].'" class="white"><B>Please enter new password and e-mail</B></TD></TR>
<TR><TD BGCOLOR="'.$config['darkborder'].'">
Account of character:&nbsp;&nbsp;<INPUT TYPE=text NAME="nick" VALUE="'.$nick.'" SIZE="40" readonly="readonly"><BR />
New password:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<INPUT id="passor" TYPE=password NAME="passor" VALUE="" SIZE="40"><BR>
Repeat new password:&nbsp;&nbsp;<INPUT id="passor2" TYPE=password NAME="passor" VALUE="" SIZE="40"><BR>
New e-mail address:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<INPUT id="email" TYPE=text NAME="email" VALUE="" SIZE="40"><BR>
<INPUT TYPE=hidden NAME="key" VALUE="'.$rec_key.'">
</TD></TR>
</TABLE>
<BR>
<TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
' . $twig->render('buttons.submit.html.twig') . '</div>
</TD></TR></FORM></TABLE></TABLE>';
}
else
echo 'Wrong recovery key!';
}
else
echo 'Account of this character has no recovery key!';
}
else
echo 'Player or account of player <b>'.$nick.'</b> doesn\'t exist.';
}
else
echo 'Invalid player name format. If you have other characters on account try with other name.';
echo '<BR /><TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
<a href="' . getLink('account/lost') . '?action=step1&action_type=reckey&nick='.urlencode($nick).'" border="0"><IMG SRC="'.$template_path.'/images/global/buttons/sbutton_back.gif" NAME="Back" ALT="Back" BORDER=0 WIDTH=120 HEIGHT=18></a></div>
</TD></TR></FORM></TABLE></TABLE>';
}
elseif($action == 'step3')
{
$rec_key = trim($_REQUEST['key']);
$nick = stripslashes($_REQUEST['nick']);
$new_pass = trim($_REQUEST['passor']);
$new_email = trim($_REQUEST['email']);
if(Validator::characterName($nick))
{
$player = new OTS_Player();
$account = new OTS_Account();
$player->find($nick);
if($player->isLoaded())
$account = $player->getAccount();
if($account->isLoaded())
{
$account_key = $account->getCustomField('key');
if(!empty($account_key))
{
if($account_key == $rec_key)
{
if(Validator::password($new_pass))
{
if(Validator::email($new_email))
{
$account->setEMail($new_email);
$tmp_new_pass = $new_pass;
if(USE_ACCOUNT_SALT)
{
$salt = generateRandomString(10, false, true, true);
$tmp_new_pass = $salt . $new_pass;
}
$account->setPassword(encrypt($tmp_new_pass));
$account->save();
if(USE_ACCOUNT_SALT)
$account->setCustomField('salt', $salt);
echo 'Your account name, new password and new e-mail.<BR>
<FORM ACTION="' . getLink('account/manage') . '" onsubmit="return validate_form(this)" METHOD=post>
<INPUT TYPE=hidden NAME="character" VALUE="">
<TABLE CELLSPACING=1 CELLPADDING=4 BORDER=0 WIDTH=100%>
<TR><TD BGCOLOR="'.$config['vdarkborder'].'" class="white"><B>Your account name, new password and new e-mail</B></TD></TR>
<TR><TD BGCOLOR="'.$config['darkborder'].'">
Account name:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b>'.$account->getName().'</b><BR>
New password:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b>'.$new_pass.'</b><BR>
New e-mail address:&nbsp;<b>'.$new_email.'</b><BR>';
if($account->getCustomField('email_next') < time())
{
$mailBody = '
<h3>Your account name and new password!</h3>
<p>Changed password and e-mail to your account in Lost Account Interface on server <a href="'.BASE_URL.'"><b>'.$config['lua']['serverName'].'</b></a></p>
<p>Account name: <b>'.$account->getName().'</b></p>
<p>New password: <b>'.$new_pass.'</b></p>
<p>E-mail: <b>'.$new_email.'</b> (this e-mail)</p>
<br />
<p><u>It\'s automatic e-mail from OTS Lost Account System. Do not reply!</u></p>';
if(_mail($account->getCustomField('email'), $config['lua']['serverName']." - New password to your account", $mailBody))
{
echo '<br /><small>Sent e-mail with your account name and password to new e-mail. You should receive this e-mail in 15 minutes. You can login now with new password!</small>';
}
else
{
echo '<br /><p class="error">An error occurred while sending email! You will not receive e-mail with this informations. For Admin: More info can be found in system/logs/mailer-error.log</p>';
}
}
else
{
echo '<br /><small>You will not receive e-mail with this informations.</small>';
}
echo '<INPUT TYPE=hidden NAME="account_login" VALUE="'.$account->getId().'">
<INPUT TYPE=hidden NAME="password_login" VALUE="'.$new_pass.'">
</TD></TR></TABLE><BR>
<TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
<INPUT TYPE=image NAME="Login" ALT="Login" SRC="'.$template_path.'/images/global/buttons/sbutton_login.gif" BORDER=0 WIDTH=120 HEIGHT=18></div>
</TD></TR></FORM></TABLE></TABLE>';
}
else
echo Validator::getLastError();
}
else
echo Validator::getLastError();
}
else
echo 'Wrong recovery key!';
}
else
echo 'Account of this character has no recovery key!';
}
else
echo 'Player or account of player <b>'.$nick.'</b> doesn\'t exist.';
}
else
echo 'Invalid player name format. If you have other characters on account try with other name.';
echo '<BR /><TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
<a href="' . getLink('account/lost') . '?action=step1&action_type=reckey&nick='.urlencode($nick).'" border="0"><IMG SRC="'.$template_path.'/images/global/buttons/sbutton_back.gif" NAME="Back" ALT="Back" BORDER=0 WIDTH=120 HEIGHT=18></a></div>
</TD></TR></FORM></TABLE></TABLE>';
}
elseif($action == 'checkcode')
{
$code = trim($_REQUEST['code']);
$character = stripslashes(trim($_REQUEST['character']));
if(empty($code) || empty($character))
echo 'Please enter code from e-mail and name of one character from account. Then press Submit.<BR>
<FORM ACTION="' . getLink('account/lost') . '?action=checkcode" METHOD=post>
<TABLE CELLSPACING=1 CELLPADDING=4 BORDER=0 WIDTH=100%>
<TR><TD BGCOLOR="'.$config['vdarkborder'].'" class="white"><B>Code & character name</B></TD></TR>
<TR><TD BGCOLOR="'.$config['darkborder'].'">
Your code:&nbsp;<INPUT TYPE=text NAME="code" VALUE="" SIZE="40")><BR />
Character:&nbsp;<INPUT TYPE=text NAME="character" VALUE="" SIZE="40")><BR />
</TD></TR>
</TABLE>
<BR>
<TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
' . $twig->render('buttons.submit.html.twig') . '</div>
</TD></TR></FORM></TABLE></TABLE>';
else
{
$player = new OTS_Player();
$account = new OTS_Account();
$player->find($character);
if($player->isLoaded())
$account = $player->getAccount();
if($account->isLoaded())
{
if($account->getCustomField('email_code') == $code)
{
echo '<script type="text/javascript">
function validate_required(field,alerttxt)
{
with (field)
{
if (value==null||value==""||value==" ")
{alert(alerttxt);return false;}
else {return true}
}
}
function validate_form(thisform)
{
with (thisform)
{
if (validate_required(passor,"Please enter password!")==false)
{passor.focus();return false;}
if (validate_required(passor2,"Please repeat password!")==false)
{passor2.focus();return false;}
if (passor2.value!=passor.value)
{alert(\'Repeated password is not equal to password!\');return false;}
}
}
</script>
Please enter new password to your account and repeat to make sure you remember password.<BR>
<FORM ACTION="' . getLink('account/lost') . '?action=setnewpassword" onsubmit="return validate_form(this)" METHOD=post>
<INPUT TYPE=hidden NAME="character" VALUE="'.$character.'">
<INPUT TYPE=hidden NAME="code" VALUE="'.$code.'">
<TABLE CELLSPACING=1 CELLPADDING=4 BORDER=0 WIDTH=100%>
<TR><TD BGCOLOR="'.$config['vdarkborder'].'" class="white"><B>Code & account name</B></TD></TR>
<TR><TD BGCOLOR="'.$config['darkborder'].'">
New password:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<INPUT TYPE=password ID="passor" NAME="passor" VALUE="" SIZE="40")><BR />
Repeat new password:&nbsp;<INPUT TYPE=password ID="passor2" NAME="passor2" VALUE="" SIZE="40")><BR />
</TD></TR>
</TABLE>
<BR>
<TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
' . $twig->render('buttons.submit.html.twig') . '</div>
</TD></TR></FORM></TABLE></TABLE>';
}
else
$error= 'Wrong code to change password.';
}
else
$error = 'Account of this character or this character doesn\'t exist.';
}
if(!empty($error))
echo '<span style="color: red"><b>'.$error.'</b></span><br />Please enter code from e-mail and name of one character from account. Then press Submit.<BR>
<FORM ACTION="' . getLink('account/lost') . '?action=checkcode" METHOD=post>
<TABLE CELLSPACING=1 CELLPADDING=4 BORDER=0 WIDTH=100%>
<TR><TD BGCOLOR="'.$config['vdarkborder'].'" class="white"><B>Code & character name</B></TD></TR>
<TR><TD BGCOLOR="'.$config['darkborder'].'">
Your code:&nbsp;<INPUT TYPE=text NAME="code" VALUE="" SIZE="40")><BR />
Character:&nbsp;<INPUT TYPE=text NAME="character" VALUE="" SIZE="40")><BR />
</TD></TR>
</TABLE>
<BR>
<TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
' . $twig->render('buttons.submit.html.twig') . '</div>
</TD></TR></FORM></TABLE></TABLE>';
}
elseif($action == 'setnewpassword')
{
$newpassword = $_REQUEST['passor'];
$code = $_REQUEST['code'];
$character = stripslashes($_REQUEST['character']);
echo '';
if(empty($code) || empty($character) || empty($newpassword))
echo '<span style="color: red"><b>Error. Try again.</b></span><br />Please enter code from e-mail and name of one character from account. Then press Submit.<BR>
<BR><FORM ACTION="' . getLink('account/lost') . '?action=checkcode" METHOD=post>
<TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
<INPUT TYPE=image NAME="Back" ALT="Back" SRC="'.$template_path.'/images/global/buttons/sbutton_back.gif" BORDER=0 WIDTH=120 HEIGHT=18></div>
</TD></TR></FORM></TABLE></TABLE>';
else
{
$player = new OTS_Player();
$account = new OTS_Account();
$player->find($character);
if($player->isLoaded())
$account = $player->getAccount();
if($account->isLoaded())
{
if($account->getCustomField('email_code') == $code)
{
if(Validator::password($newpassword))
{
$tmp_new_pass = $newpassword;
if(USE_ACCOUNT_SALT)
{
$salt = generateRandomString(10, false, true, true);
$tmp_new_pass = $salt . $newpassword;
$account->setCustomField('salt', $salt);
}
$account->setPassword(encrypt($tmp_new_pass ));
$account->save();
$account->setCustomField('email_code', '');
echo 'New password to your account is below. Now you can login.<BR>
<INPUT TYPE=hidden NAME="character" VALUE="'.$character.'">
<TABLE CELLSPACING=1 CELLPADDING=4 BORDER=0 WIDTH=100%>
<TR><TD BGCOLOR="'.$config['vdarkborder'].'" class="white"><B>Changed password</B></TD></TR>
<TR><TD BGCOLOR="'.$config['darkborder'].'">
New password:&nbsp;<b>'.$newpassword.'</b><BR />
Account name:&nbsp;&nbsp;&nbsp;<i>(Already on your e-mail)</i><BR />';
$mailBody = '
<h3>Your account name and password!</h3>
<p>Changed password to your account in Lost Account Interface on server <a href="'.BASE_URL.'"><b>'.$config['lua']['serverName'].'</b></a></p>
<p>Account name: <b>'.$account->getName().'</b></p>
<p>New password: <b>'.$newpassword.'</b></p>
<br />
<p><u>It\'s automatic e-mail from OTS Lost Account System. Do not reply!</u></p>';
if(_mail($account->getCustomField('email'), $config['lua']['serverName']." - Your new password", $mailBody))
{
echo '<br /><small>New password work! Sent e-mail with your password and account name. You should receive this e-mail in 15 minutes. You can login now with new password!';
}
else
{
echo '<br /><p class="error">New password work! An error occurred while sending email! You will not receive e-mail with new password. For Admin: More info can be found in system/logs/mailer-error.log';
}
echo '</TD></TR>
</TABLE>
<BR>
<TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
<FORM ACTION="' . getLink('account/manage') . '" METHOD=post>
<INPUT TYPE=image NAME="Login" ALT="Login" SRC="'.$template_path.'/images/global/buttons/sbutton_login.gif" BORDER=0 WIDTH=120 HEIGHT=18></div>
</TD></TR></FORM></TABLE></TABLE>';
}
else
$error= Validator::getLastError();
}
else
$error= 'Wrong code to change password.';
}
else
$error = 'Account of this character or this character doesn\'t exist.';
}
if(!empty($error))
echo '<span style="color: red"><b>'.$error.'</b></span><br />Please enter code from e-mail and name of one character from account. Then press Submit.<BR>
<FORM ACTION="' . getLink('account/lost') . '?action=checkcode" METHOD=post>
<TABLE CELLSPACING=1 CELLPADDING=4 BORDER=0 WIDTH=100%>
<TR><TD BGCOLOR="'.$config['vdarkborder'].'" class="white"><B>Code & character name</B></TD></TR>
<TR><TD BGCOLOR="'.$config['darkborder'].'">
Your code:&nbsp;<INPUT TYPE=text NAME="code" VALUE="" SIZE="40")><BR />
Character:&nbsp;<INPUT TYPE=text NAME="character" VALUE="" SIZE="40")><BR />
</TD></TR>
</TABLE>
<BR>
<TABLE CELLSPACING=0 CELLPADDING=0 BORDER=0 WIDTH=100%><TR><TD><div style="text-align:center">
' . $twig->render('buttons.submit.html.twig') . '</div>
</TD></TR></FORM></TABLE></TABLE>';
}
$twig->display('account/lost/form.html.twig');

View File

@@ -0,0 +1,18 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
function lostAccountWriteCooldown(string $nick, int $time): void
{
global $twig;
$inSec = $time - time();
$minutesLeft = floor($inSec / 60);
$secondsLeft = $inSec - ($minutesLeft * 60);
$timeLeft = "$minutesLeft minutes $secondsLeft seconds";
$timeRounded = ceil(setting('core.mail_lost_account_interval') / 60);
$twig->display('error_box.html.twig', [
'errors' => ["Account of selected character (<b>" . escapeHtml($nick) . "</b>) received e-mail in last $timeRounded minutes. You must wait $timeLeft before you can use Lost Account Interface again."]
]);
}

View File

@@ -0,0 +1,51 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
csrfProtect();
$title = 'Lost Account';
$code = $_REQUEST['code'] ?? '';
$character = $_REQUEST['character'] ?? '';
if(empty($code) || empty($character)) {
$twig->display('account/lost/check-code.html.twig', [
'code' => $code,
'characters' => $character,
]);
}
else {
$player = new OTS_Player();
$account = new OTS_Account();
$player->find($character);
if($player->isLoaded()) {
$account = $player->getAccount();
}
if($account->isLoaded()) {
if($account->getCustomField('email_code') == $code) {
$twig->display('account/lost/check-code.finish.html.twig', [
'character' => $character,
'code' => $code,
]);
}
else {
$error = 'Wrong code to change password.';
}
}
else {
$error = "Account of this character or this character doesn't exist.";
}
}
if(!empty($error)) {
$twig->display('error_box.html.twig', [
'errors' => [$error],
]);
echo '<br/>';
$twig->display('account/lost/check-code.html.twig', [
]);
}

View File

@@ -0,0 +1,75 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
csrfProtect();
require __DIR__ . '/../base.php';
$title = 'Lost Account';
$email = $_POST['email'] ?? '';
$nick = $_POST['nick'] ?? '';
$player = new OTS_Player();
$account = new OTS_Account();
$player->find($nick);
if($player->isLoaded()) {
$account = $player->getAccount();
}
if($account->isLoaded()) {
if($account->getCustomField('email_next') < time()) {
if($account->getEMail() == $email) {
$newCode = generateRandomString(30, true, false, true);
$mailBody = $twig->render('mail.account.lost.code.html.twig', [
'newCode' => $newCode,
'account' => $account,
'nick' => $nick,
]);
$accountEMail = $account->getCustomField('email');
if(_mail($accountEMail, configLua('serverName') . ' - Recover your account', $mailBody)) {
$account->setCustomField('email_code', $newCode);
$account->setCustomField('email_next', (time() + setting('core.mail_lost_account_interval')));
$twig->display('success.html.twig', [
'title' => 'Email has been sent',
'description' => 'Details about steps required to recover your account has been sent to <b>' . $accountEMail . '</b>. You should receive this email within 15 minutes. Please check your inbox/spam directory.',
'custom_buttons' => '',
]);
$twig->display('account.back_button.html.twig', [
'new_line' => true,
'center' => true,
'action' => getLink('news'),
]);
return;
}
$account->setCustomField('email_next', (time() + 60));
error('An error occurred while sending email! Try again later or contact with admin. For Admin: More info can be found in system/logs/mailer-error.log</p>');
}
else {
$errors[] = 'Invalid e-mail to account of character <b>' . escapeHtml($nick) . '</b>. Try again.';
}
}
else {
lostAccountWriteCooldown($nick, (int)$account->getCustomField('email_next'));
}
}
else {
$errors[] = "Player or account of player <b>" . escapeHtml($nick) . "</b> doesn't exist.";
}
if (!empty($errors)) {
$twig->display('error_box.html.twig', [
'errors' => $errors,
]);
}
$twig->display('account.back_button.html.twig', [
'new_line' => true,
'center' => true,
'action' => getLink('account/lost/step-1') . '?action=email&nick=' . urlencode($nick),
]);

View File

@@ -0,0 +1,128 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
csrfProtect();
$title = 'Lost Account';
$newPassword = $_POST['password'] ?? '';
$passwordRepeat = $_POST['password_repeat'] ?? '';
$code = $_POST['code'] ?? '';
$character = $_POST['character'] ?? '';
if(empty($code) || empty($character)) {
$errors[] = 'Please enter code from e-mail and name of one character from account.';
$twig->display('error_box.html.twig', [
'errors' => $errors,
]);
$twig->display('account/lost/check-code.html.twig', [
'code' => $code,
'character' => $character,
]);
$twig->display('account.back_button.html.twig', [
'new_line' => true,
'center' => true,
'action' => getLink('account/lost/check-code')
]);
return;
}
if (empty($newPassword) || empty($passwordRepeat)) {
$errors[] = 'Please enter both passwords.';
$twig->display('error_box.html.twig', [
'errors' => $errors,
]);
$twig->display('account/lost/check-code.finish.html.twig', [
'character' => $character,
'code' => $code,
]);
return;
}
$player = new OTS_Player();
$account = new OTS_Account();
$player->find($character);
if($player->isLoaded()) {
$account = $player->getAccount();
}
$passwordFailed = false;
if($account->isLoaded()) {
if($account->getCustomField('email_code') == $code) {
if ($newPassword == $passwordRepeat) {
if (Validator::password($newPassword)) {
$hooks->trigger(HOOK_ACCOUNT_LOST_EMAIL_SET_NEW_PASSWORD_POST);
if (empty($errors)) {
$tmp_new_pass = $newPassword;
if (USE_ACCOUNT_SALT) {
$salt = generateRandomString(10, false, true, true);
$tmp_new_pass = $salt . $newPassword;
$account->setCustomField('salt', $salt);
}
$account->setPassword(encrypt($tmp_new_pass));
$account->save();
$account->setCustomField('email_code', '');
$mailBody = $twig->render('mail.account.lost.new-password.html.twig', [
'account' => $account,
'newPassword' => $newPassword,
]);
$statusMsg = '';
if (_mail($account->getCustomField('email'), configLua('serverName') . ' - Your new password', $mailBody)) {
$statusMsg = '<br /><small>New password work! Sent e-mail with your password and account name. You should receive this e-mail in 15 minutes. You can login now with new password!';
} else {
$statusMsg = '<br /><p class="error">New password work! An error occurred while sending email! You will not receive e-mail with new password. For Admin: More info can be found in system/logs/mailer-error.log';
}
$twig->display('account/lost/finish.new-password.html.twig', [
'statusMsg' => $statusMsg,
'newPassword' => $newPassword,
]);
}
} else {
$passwordFailed = true;
$errors[] = Validator::getLastError();
}
}
else {
$passwordFailed = true;
$errors[] = 'Passwords are not the same!';
}
}
else {
$errors[] = 'Wrong code to change password.';
}
}
else {
$errors[] = "Account of this character or this character doesn't exist.";
}
if(!empty($errors)) {
$twig->display('error_box.html.twig', [
'errors' => $errors,
]);
echo '<br/>';
$template = 'account/lost/check-code.html.twig';
if($passwordFailed) {
$template = 'account/lost/check-code.finish.html.twig';
}
$twig->display($template, [
'code' => $code,
'character' => $character,
]);
}

View File

@@ -0,0 +1,36 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
require __DIR__ . '/../base.php';
csrfProtect();
$title = 'Lost Account';
$nick = $_REQUEST['nick'] ?? '';
if($account->isLoaded()) {
if($account->getCustomField('email_next') < time()) {
$twig->display('account/lost/email.html.twig', [
'nick' => $nick,
]);
}
else {
lostAccountWriteCooldown($nick, (int)$account->getCustomField('email_next'));
}
}
else {
$errors[] = "Player or account of player <b>" . escapeHtml($nick) . "</b> doesn't exist.";
}
if (!empty($errors)) {
$twig->display('error_box.html.twig', [
'errors' => $errors,
]);
}
$twig->display('account.back_button.html.twig', [
'new_line' => true,
'center' => true,
'action' => getLink('account/lost'),
]);

View File

@@ -0,0 +1,38 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
csrfProtect();
$title = 'Lost Account';
$nick = $_REQUEST['nick'] ?? '';
$key = $_REQUEST['key'] ?? '';
if($account->isLoaded()) {
$account_key = $account->getCustomField('key');
if(!empty($account_key)) {
$twig->display('account/lost/recovery-key.step-1.html.twig', [
'nick' => $nick,
'key' => $key,
]);
}
else {
$errors[] = 'Account of this character has no recovery key!';
}
}
else {
$errors[] = "Player or account of player <b>" . escapeHtml($nick) . "</b> doesn't exist.";
}
if (!empty($errors)) {
$twig->display('error_box.html.twig', [
'errors' => $errors,
]);
}
$twig->display('account.back_button.html.twig', [
'new_line' => true,
'center' => true,
'action' => getLink('account/lost'),
]);

View File

@@ -0,0 +1,49 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
csrfProtect();
$title = 'Lost Account';
$key = $_REQUEST['key'] ?? '';
$nick = $_REQUEST['nick'] ?? '';
$player = new OTS_Player();
$account = new OTS_Account();
$player->find($nick);
if($player->isLoaded()) {
$account = $player->getAccount();
}
if($account->isLoaded()) {
$accountKey = $account->getCustomField('key');
if(!empty($accountKey)) {
if($accountKey == $key) {
$twig->display('account/lost/recovery-key.step-2.html.twig', [
'nick' => $nick,
'key' => $key,
]);
}
else {
$errors[] = 'Wrong recovery key!';
}
}
else {
$errors[] = 'Account of this character has no recovery key!';
}
}
else {
$errors[] = "Player or account of player <b>" . escapeHtml($nick) . "</b> doesn't exist.";
}
if (!empty($errors)) {
$twig->display('error_box.html.twig', [
'errors' => $errors,
]);
}
$twig->display('account.back_button.html.twig', [
'new_line' => true,
'center' => true,
'action' => getLink('account/lost/step-1') . '?action=recovery-key&nick=' . urlencode($nick) . '&key=' . urlencode($key),
]);

View File

@@ -0,0 +1,117 @@
<?php
use MyAAC\Models\Account as AccountModel;
defined('MYAAC') or die('Direct access not allowed!');
csrfProtect();
$title = 'Lost Account';
$key = $_POST['key'];
$nick = $_POST['nick'] ?? '';
$newPassword = $_POST['password'] ?? '';
$passwordRepeat = $_POST['password_repeat'] ?? '';
$newEmail = $_POST['email'] ?? '';
$player = new OTS_Player();
$account = new OTS_Account();
$player->find($nick);
if($player->isLoaded()) {
$account = $player->getAccount();
}
if($account->isLoaded()) {
$accountKey = $account->getCustomField('key');
if(!empty($accountKey)) {
if($accountKey == $key) {
if(Validator::password($newPassword)) {
if ($newPassword == $passwordRepeat) {
if (Validator::email($newEmail)) {
$emailExists = AccountModel::where('email', $newEmail)->count() > 0;
if (!$emailExists) {
$hooks->trigger(HOOK_ACCOUNT_LOST_RECOVERY_KEY_STEP_3_POST);
if (empty($errors)) {
$account->setEMail($newEmail);
$tmp_new_pass = $newPassword;
if (USE_ACCOUNT_SALT) {
$salt = generateRandomString(10, false, true, true);
$tmp_new_pass = $salt . $newPassword;
}
$account->setPassword(encrypt($tmp_new_pass));
$account->save();
if (USE_ACCOUNT_SALT) {
$account->setCustomField('salt', $salt);
}
$statusMsg = '';
if ($account->getCustomField('email_next') < time()) {
$mailBody = $twig->render('mail.account.lost.new-email.html.twig', [
'account' => $account,
'newPassword' => $newPassword,
'newEmail' => $newEmail,
]);
if (_mail($account->getCustomField('email'), configLua('serverName') . ' - New password to your account', $mailBody)) {
$statusMsg = '<br /><small>Sent e-mail with your account name and password to new e-mail. You should receive this e-mail in 15 minutes. You can login now with new password!</small>';
} else {
$statusMsg = '<br /><p class="error">An error occurred while sending email! You will not receive e-mail with this informations. For Admin: More info can be found in system/logs/mailer-error.log</p>';
}
} else {
$statusMsg = '<br /><small>You will not receive e-mail with this informations.</small>';
}
$twig->display('account/lost/finish.new-email.html.twig', [
'statusMsg' => $statusMsg,
'account' => $account,
'newPassword' => $newPassword,
'newEmail' => $newEmail,
]);
return;
}
}
else {
$errors[] = 'This email is already registered!';
}
} else {
$errors[] = Validator::getLastError();
}
}
else {
$errors[] = 'Passwords are not the same!';
}
}
else {
$errors[] = Validator::getLastError();
}
}
else {
$errors[] = 'Wrong recovery key!';
}
}
else {
$errors[] = 'Account of this character has no recovery key!';
}
}
else {
$errors[] = "Player or account of player <b>" . escapeHtml($nick) . "</b> doesn't exist.";
}
if (!empty($errors)) {
$twig->display('error_box.html.twig', [
'errors' => $errors,
]);
}
$twig->display('account.back_button.html.twig', [
'new_line' => true,
'center' => true,
'action' => getLink('account/lost/recovery-key/step-2') . '?nick=' . urlencode($nick) . '&key=' . urlencode($key),
]);

View File

@@ -0,0 +1,26 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
csrfProtect();
$title = 'Lost Account';
$nick = $_REQUEST['nick'] ?? '';
$player = new OTS_Player();
$account = new OTS_Account();
$player->find($nick);
if($player->isLoaded()) {
$account = $player->getAccount();
}
if (ACTION == 'email') {
require __DIR__ . '/email/step-1.php';
}
else if (ACTION == 'recovery-key') {
require __DIR__ . '/recovery-key/step-1.php';
}
else {
$twig->display('account/lost/no-action.html.twig');
}

View File

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

View File

@@ -452,10 +452,8 @@ WHERE killers.death_id = '".$death['id']."' ORDER BY killers.final_hit DESC, kil
if($query->rowCount() > 0) {
echo 'Did you mean:<ul>';
foreach($query as $player) {
if(isset($player['promotion'])) {
if((int)$player['promotion'] > 0)
$player['vocation'] += ($player['promotion'] * $config['vocations_amount']);
}
$player['vocation'] = OTS_Toolbox::getVocationFromPromotion($player['vocation'], $player['promotion'] ?? 0);
echo '<li>' . getPlayerLink($player['name']) . ' (<small><strong>level ' . $player['level'] . ', ' . $config['vocations'][$player['vocation']] . '</strong></small>)</li>';
}
echo '</ul>';

View File

@@ -42,35 +42,12 @@ for($i = 0; $i < $threads_count['threads_count'] / setting('core.forum_threads_p
$links_to_pages .= '<b>'.($i + 1).' </b>';
}
echo '<a href="' . getLink('forum') . '">Boards</a> >> <b>'.escapeHtml($sections[$section_id]['name']).'</b>';
if($logged && (!$sections[$section_id]['closed'] || Forum::isModerator())) {
echo '<br /><br />
<a href="' . getLink('forum') . '?action=new_thread&section_id='.$section_id.'"><img src="images/forum/topic.gif" border="0" /></a>';
}
echo '<br /><br />Page: '.$links_to_pages.'<br />';
$last_threads = $db->query("SELECT `players`.`id` as `player_id`, `players`.`name`, `" . FORUM_TABLE_PREFIX . "forum`.`first_post`, `" . FORUM_TABLE_PREFIX . "forum`.`post_text`, `" . FORUM_TABLE_PREFIX . "forum`.`post_topic`, `" . FORUM_TABLE_PREFIX . "forum`.`id`, `" . FORUM_TABLE_PREFIX . "forum`.`last_post`, `" . FORUM_TABLE_PREFIX . "forum`.`replies`, `" . FORUM_TABLE_PREFIX . "forum`.`views`, `" . FORUM_TABLE_PREFIX . "forum`.`post_date` FROM `players`, `" . FORUM_TABLE_PREFIX . "forum` WHERE `players`.`id` = `" . FORUM_TABLE_PREFIX . "forum`.`author_guid` AND `" . FORUM_TABLE_PREFIX . "forum`.`section` = ".$section_id." AND `" . FORUM_TABLE_PREFIX . "forum`.`first_post` = `" . FORUM_TABLE_PREFIX . "forum`.`id` ORDER BY `" . FORUM_TABLE_PREFIX . "forum`.`last_post` DESC LIMIT ".setting('core.forum_threads_per_page')." OFFSET ".($_page * setting('core.forum_threads_per_page')))->fetchAll(PDO::FETCH_ASSOC);
if(isset($last_threads[0])) {
echo '<table width="100%">
<tr bgcolor="'.$config['vdarkborder'].'" align="center">
<td class="white">
<span style="font-size: 10px"><b>Thread</b></span></td>
<td><span style="font-size: 10px"><b>Thread Starter</b></span></td>
<td><span style="font-size: 10px"><b>Replies</b></span></td>
<td><span style="font-size: 10px"><b>Views</b></span></td>
<td><span style="font-size: 10px"><b>Last Post</b></span></td>
</tr>';
$threads = [];
if(count($last_threads) > 0) {
$player = new OTS_Player();
foreach($last_threads as $thread) {
echo '<tr bgcolor="' . getStyle($number_of_rows++) . '"><td>';
if(Forum::isModerator()) {
echo '<a href="' . getLink('forum') . '?action=move_thread&id=' . $thread['id'] . '" title="Move Thread"><img src="images/icons/arrow_right.gif"/></a>';
$twig->display('forum.remove_post.html.twig', ['post' => $thread]);
}
$player->load($thread['player_id']);
if(!$player->isLoaded()) {
throw new RuntimeException('Forum error: Player not loaded.');
@@ -79,28 +56,29 @@ if(isset($last_threads[0])) {
$player_account = $player->getAccount();
$canEditForum = $player_account->hasFlag(FLAG_CONTENT_FORUM) || $player_account->isAdmin();
echo '<a href="' . getForumThreadLink($thread['id']) . '">'.htmlspecialchars($thread['post_topic']). '</a><br /><small>'.($canEditForum ? substr(strip_tags($thread['post_text']), 0, 50) : htmlspecialchars(substr($thread['post_text'], 0, 50))).'...</small></td><td>' . getPlayerLink($thread['name']) . '</td><td>'.(int) $thread['replies'].'</td><td>'.(int) $thread['views'].'</td><td>';
$thread['link'] = getForumThreadLink($thread['id']);
$thread['post_shortened'] = ($canEditForum ? substr(strip_tags($thread['post_text']), 0, 50) : htmlspecialchars(substr($thread['post_text'], 0, 50)));
$thread['player'] = $player;
$thread['player_link'] = getPlayerLink($thread['name']);
if($thread['last_post'] > 0) {
$last_post = $db->query("SELECT `players`.`name`, `" . FORUM_TABLE_PREFIX . "forum`.`post_date` FROM `players`, `" . FORUM_TABLE_PREFIX . "forum` WHERE `" . FORUM_TABLE_PREFIX . "forum`.`first_post` = ".(int) $thread['id']." AND `players`.`id` = `" . FORUM_TABLE_PREFIX . "forum`.`author_guid` ORDER BY `post_date` DESC LIMIT 1")->fetch();
if(isset($last_post['name'])) {
echo date('d.m.y H:i:s', $last_post['post_date']) . '<br />by ' . getPlayerLink($last_post['name']);
}
else {
echo 'No posts.';
}
}
else {
echo date('d.m.y H:i:s', $thread['post_date']) . '<br />by ' . getPlayerLink($thread['name']);
}
echo '</td></tr>';
$last_post['player_link'] = getPlayerLink($last_post['name']);
$thread['latest_post'] = $last_post;
}
echo '</table>';
if($logged && (!$sections[$section_id]['closed'] || Forum::isModerator())) {
echo '<br /><a href="' . getLink('forum') . '?action=new_thread&section_id=' . $section_id . '"><img src="images/forum/topic.gif" border="0" /></a>';
$threads[] = $thread;
}
}
else {
echo '<h3>No threads in this board.</h3>';
}
$twig->display('forum.show_board.html.twig', [
'threads' => $threads,
'section_id' => $section_id,
'section_name' => $sections[$section_id]['name'],
'links_to_pages' => $links_to_pages,
'is_moderator' => Forum::isModerator(),
'closed' => $sections[$section_id]['closed'],
]);

View File

@@ -9,316 +9,25 @@
*/
use MyAAC\Cache\Cache;
use MyAAC\Models\Gallery as ModelsGallery;
defined('MYAAC') or die('Direct access not allowed!');
$title = 'Gallery';
$canEdit = hasFlag(FLAG_CONTENT_GALLERY) || superAdmin();
if($canEdit) {
if(function_exists('imagecreatefrompng')) {
if (!empty($action)) {
if ($action == 'delete' || $action == 'edit' || $action == 'hide' || $action == 'moveup' || $action == 'movedown')
$id = $_REQUEST['id'];
const ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (isset($_REQUEST['comment']))
$comment = stripslashes($_REQUEST['comment']);
$images = Cache::remember('gallery', 5 * 60, function () {
$images = glob(BASE . GALLERY_DIR . '*.*');
if (isset($_REQUEST['image']))
$image = $_REQUEST['image'];
$images = array_filter($images, function ($image) {
$ext = pathinfo($image, PATHINFO_EXTENSION);
if (isset($_REQUEST['author']))
$author = $_REQUEST['author'];
$errors = array();
if ($action == 'add') {
if (Gallery::add($comment, $image, $author, $errors))
$comment = $image = $author = '';
} else if ($action == 'delete') {
Gallery::delete($id, $errors);
} else if ($action == 'edit') {
if (isset($id) && !isset($name)) {
$tmp = Gallery::get($id);
$comment = $tmp['comment'];
$image = $tmp['image'];
$author = $tmp['author'];
} else {
Gallery::update($id, $comment, $image, $author);
$action = $comment = $image = $author = '';
}
} else if ($action == 'hide') {
Gallery::toggleHide($id, $errors);
} else if ($action == 'moveup') {
Gallery::move($id, -1, $errors);
} else if ($action == 'movedown') {
Gallery::move($id, 1, $errors);
}
if (!empty($errors))
$twig->display('error_box.html.twig', array('errors' => $errors));
}
if(!isset($_GET['image'])) {
$twig->display('gallery.form.html.twig', array(
'link' => getLink('gallery/' . ($action == 'edit' ? 'edit' : 'add')),
'action' => $action,
'id' => isset($id) ? $id : null,
'comment' => isset($comment) ? $comment : null,
'image' => isset($image) ? $image : null,
'author' => isset($author) ? $author : null
));
}
}
else
echo 'You cannot edit/add gallery items as it seems your PHP installation doesnt have GD support enabled. Visit <a href="http://be2.php.net/manual/en/image.installation.php">PHP Manual</a> for more info.';
}
if(isset($_GET['image']))
{
$image = $db->query('SELECT * FROM `' . TABLE_PREFIX . 'gallery` WHERE `id` = ' . $db->quote($_GET['image']) . ' ORDER by `ordering` LIMIT 1;');
if($image->rowCount() == 1)
$image = $image->fetch();
else
{
echo 'Image with this id does not exists.';
return;
}
$previous_image = $db->query('SELECT `id` FROM `' . TABLE_PREFIX . 'gallery` WHERE `id` = ' . $db->quote($image['id'] - 1) . ' ORDER by `ordering`;');
if($previous_image->rowCount() == 1)
$previous_image = $previous_image->fetch();
else
$previous_image = NULL;
$next_image = $db->query('SELECT `id` FROM `' . TABLE_PREFIX . 'gallery` WHERE `id` = ' . $db->quote($image['id'] + 1) . ' ORDER by `ordering`;');
if($next_image->rowCount() == 1)
$next_image = $next_image->fetch();
else
$next_image = NULL;
$twig->display('gallery.get.html.twig', array(
'previous' => $previous_image ? $previous_image['id'] : null,
'next' => $next_image ? $next_image['id'] : null,
'image' => $image
));
return;
}
$images = Cache::remember('gallery_' . ($canEdit ? '1' : '0'), 60, function () use ($db, $canEdit) {
return $db->query('SELECT `id`, `comment`, `image`, `author`, `thumb`' .
($canEdit ? ', `hide`, `ordering`' : '') .
' FROM `' . TABLE_PREFIX . 'gallery`' .
(!$canEdit ? ' WHERE `hide` != 1' : '') .
' ORDER BY `ordering`;')->fetchAll(PDO::FETCH_ASSOC);
return (in_array($ext, ALLOWED_EXTENSIONS) && !str_contains($image, '_thumb'));
});
$last = count($images);
if(!$last)
{
?>
There are no images added to gallery yet.
<?php
return;
}
return array_map(function ($image) {
return basename($image);
}, $images);
});
$twig->display('gallery.html.twig', array(
$twig->display('gallery.html.twig', [
'images' => $images,
'last' => $last,
'canEdit' => $canEdit
));
class Gallery
{
static public function add($comment, $image, $author, &$errors)
{
global $db;
if(isset($comment[0]) && isset($image[0]) && isset($author[0]))
{
$query =
$db->query(
'SELECT `ordering`' .
' FROM `' . TABLE_PREFIX . 'gallery`' .
' ORDER BY `ordering`' . ' DESC LIMIT 1'
);
$ordering = 0;
if($query->rowCount() > 0) {
$query = $query->fetch();
$ordering = $query['ordering'] + 1;
}
$pathinfo = pathinfo($image);
$extension = strtolower($pathinfo['extension']);
$thumb_filename = GALLERY_DIR . $pathinfo['filename'] . '_thumb.' . $extension;
$filename = GALLERY_DIR . $pathinfo['filename'] . '.' . $extension;
if($db->insert(TABLE_PREFIX . 'gallery', array(
'comment' => $comment,
'image' => $filename, 'author' => $author,
'thumb' => $thumb_filename,
'ordering' => $ordering))) {
if(self::generateThumb($db->lastInsertId(), $image, $errors))
self::resize($image, 650, 500, $filename, $errors);
}
}
else
$errors[] = 'Please fill all inputs.';
return !count($errors);
}
static public function get($id) {
return ModelsGallery::find($id)->toArray();
}
static public function update($id, $comment, $image, $author) {
$pathinfo = pathinfo($image);
$extension = strtolower($pathinfo['extension']);
$filename = GALLERY_DIR . $pathinfo['filename'] . '.' . $extension;
if(ModelsGallery::where('id', $id)->update([
'comment' => $comment,
'image' => $filename,
'author' => $author
])) {
if(self::generateThumb($id, $image, $errors))
self::resize($image, 650, 500, $filename, $errors);
}
}
static public function delete($id, &$errors)
{
if(isset($id))
{
$row = ModelsGallery::find($id);
if($row)
if (!$row->delete()) {
$errors[] = 'Fail during delete Gallery';
}
else
$errors[] = 'Image with id ' . $id . ' does not exists.';
}
else
$errors[] = 'id not set';
return !count($errors);
}
static public function toggleHide($id, &$errors)
{
if(isset($id))
{
$row = ModelsGallery::find($id);
if($row) {
$row->hide = $row->hide == 1 ? 0 : 1;
if (!$row->save()) {
$errors[] = 'Fail during toggle hide Gallery';
}
} else
$errors[] = 'Image with id ' . $id . ' does not exists.';
}
else
$errors[] = 'id not set';
return !count($errors);
}
static public function move($id, $i, &$errors)
{
global $db;
$query = self::get($id);
if($query !== false)
{
$ordering = $query['ordering'] + $i;
$old_record = $db->select(TABLE_PREFIX . 'gallery', array('ordering' => $ordering));
if($old_record !== false) {
ModelsGallery::where('ordering', $ordering)->update([
'ordering' => $query['ordering'],
]);
}
ModelsGallery::where('id', $id)->update([
'ordering' => $ordering,
]);
}
else
$errors[] = 'Image with id ' . $id . ' does not exists.';
return !count($errors);
}
static public function resize($file, $new_width, $new_height, $new_file, &$errors)
{
$pathinfo = pathinfo($file);
$extension = strtolower($pathinfo['extension']);
switch ($extension)
{
case 'gif': // GIF
$image = imagecreatefromgif($file);
break;
case 'jpg': // JPEG
case 'jpeg':
$image = imagecreatefromjpeg($file);
break;
case 'png': // PNG
$image = imagecreatefrompng($file);
break;
default:
$errors[] = 'Unsupported file format.';
return false;
}
$width = imagesx($image);
$height = imagesy($image);
// create a new temporary image
$tmp_img = imagecreatetruecolor($new_width, $new_height);
// copy and resize old image into new image
imagecopyresized($tmp_img, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
// save thumbnail into a file
switch($extension)
{
case 'gif':
imagegif($tmp_img, $new_file);
break;
case 'jpg':
case 'jpeg':
imagejpeg($tmp_img, $new_file);
break;
case 'png':
imagepng($tmp_img, $new_file);
break;
}
return true;
}
static public function generateThumb($id, $file, &$errors)
{
$pathinfo = pathinfo($file);
$extension = strtolower($pathinfo['extension']);
$thumb_filename = GALLERY_DIR . $pathinfo['filename'] . '_thumb.' . $extension;
if(!self::resize($file, 170, 110, $thumb_filename, $errors))
return false;
if(isset($id))
{
$row = ModelsGallery::find($id);
if($row) {
$row->thumb = $thumb_filename;
$row->save();
} else
$errors[] = 'Image with id ' . $id . ' does not exists.';
}
else
$errors[] = 'id not set';
return !count($errors);
}
}

View File

@@ -13,6 +13,7 @@ use MyAAC\Cache\Cache;
use MyAAC\Models\Player;
use MyAAC\Models\PlayerDeath;
use MyAAC\Models\PlayerKillers;
use MyAAC\Server\XML\Vocations;
defined('MYAAC') or die('Direct access not allowed!');
$title = 'Highscores';
@@ -35,24 +36,20 @@ if(!is_numeric($page) || $page < 1 || $page > PHP_INT_MAX) {
$query = Player::query();
$configVocations = config('vocations');
$configVocationsAmount = config('vocations_amount');
$vocationId = null;
if($vocation !== 'all') {
foreach($configVocations as $id => $name) {
if(strtolower($name) == $vocation) {
$vocationId = $id;
$add_vocs = [$id];
$filterVocations = [$id];
if ($id !== 0) {
$i = $id + $configVocationsAmount;
while (isset($configVocations[$i])) {
$add_vocs[] = $i;
$i += $configVocationsAmount;
}
while($tmpVoc = Vocations::getPromoted($id)) {
$id = $tmpVoc;
$filterVocations[] = $tmpVoc;
}
$query->whereIn('players.vocation', $add_vocs);
$query->whereIn('players.vocation', $filterVocations);
break;
}
}
@@ -325,4 +322,5 @@ $twig->display('highscores.html.twig', [
'page' => $page,
'baseLink' => $baseLink,
'updatedAt' => $updatedAt,
'baseVocations' => Vocations::getBase(true),
]);

View File

@@ -12,6 +12,7 @@
use MyAAC\Cache\Cache;
use MyAAC\Models\ServerConfig;
use MyAAC\Models\ServerRecord;
use MyAAC\Server\XML\Vocations;
defined('MYAAC') or die('Direct access not allowed!');
$title = 'Who is online?';
@@ -56,15 +57,14 @@ $cached = Cache::remember("online_$order", setting('core.online_cache_ttl') * 60
$vocations = array_map(function ($name) {
return 0;
}, setting('core.vocations'));
}, config('vocations'));
if($db->hasTable('players_online')) // tfs 1.0
$playersOnline = $db->query('SELECT `accounts`.`country`, `players`.`name`, `players`.`level`, `players`.`vocation`' . $outfit . ', `' . $skull_time . '` as `skulltime`, `' . $skull_type . '` as `skull` FROM `accounts`, `players`, `players_online` WHERE `players`.`id` = `players_online`.`player_id` AND `accounts`.`id` = `players`.`account_id` ORDER BY ' . $orderSql);
else
$playersOnline = $db->query('SELECT `accounts`.`country`, `players`.`name`, `players`.`level`, `players`.`vocation`' . $outfit . ', ' . $promotion . ' `' . $skull_time . '` as `skulltime`, `' . $skull_type . '` as `skull` FROM `accounts`, `players` WHERE `players`.`online` > 0 AND `accounts`.`id` = `players`.`account_id` ORDER BY ' . $orderSql);
$settingVocations = setting('core.vocations');
$settingVocationsAmount = setting('core.vocations_amount');
$configVocations = config('vocations');
$players = [];
foreach($playersOnline as $player) {
@@ -81,22 +81,19 @@ $cached = Cache::remember("online_$order", setting('core.online_cache_ttl') * 60
}
}
if(isset($player['promotion'])) {
if((int)$player['promotion'] > 0)
$player['vocation'] += ($player['promotion'] * $settingVocationsAmount);
}
$player['vocation'] = OTS_Toolbox::getVocationFromPromotion($player['vocation'], $player['promotion'] ?? 0);
$players[] = array(
'name' => getPlayerLink($player['name']),
'player' => $player,
'level' => $player['level'],
'vocation' => $settingVocations[$player['vocation']],
'vocation' => $configVocations[$player['vocation']],
'skull' => $skull,
'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'],
);
$vocations[($player['vocation'] > $settingVocationsAmount ? $player['vocation'] - $settingVocationsAmount : $player['vocation'])]++;
$vocations[Vocations::getOriginal($player['vocation'])]++;
}
$record = '';
@@ -142,6 +139,7 @@ $twig->display('online.html.twig', array(
'vocations' => $cached['vocations'],
'vocs' => $cached['vocations'], // deprecated, to be removed
'order' => $order,
'baseVocations' => Vocations::getBase(false),
));
// search bar

View File

@@ -8,6 +8,7 @@
* @link https://my-aac.org
*/
use MyAAC\Models\Menu;
use MyAAC\Models\Pages;
use MyAAC\Plugins;
@@ -331,7 +332,20 @@ else {
}
}
if (!$found) {
$tmpPageOriginal = $page;
$pagesWithDynamicPart = ['characters', 'forum', 'highscores', 'monsters'];
foreach ($pagesWithDynamicPart as $_page) {
if (str_contains($page, $_page)) {
$tmpPageOriginal = $_page;
}
}
$themeMenu = Menu::select(['name'])
->where('template', $template_name)
->where('link', $tmpPageOriginal)
->where('access', '>', $logged_access);
if (!$found || $themeMenu->count() >= 1) {
$page = '404';
$file = SYSTEM . 'pages/404.php';
}

View File

@@ -156,7 +156,7 @@ return [
'footer' => [
'name' => 'Custom Text',
'type' => 'textarea',
'desc' => 'Text displayed in the footer.<br/>For example: <i>' . escapeHtml('<br/>') . 'Your Server &copy; 2023. All rights reserved.</i>',
'desc' => 'Text displayed in the footer.<br/>For example: <i>' . escapeHtml('<br/>') . 'Your Server &copy; ' . date("Y") . '. All rights reserved.</i>',
'default' => '',
],
'footer_load_time' => [
@@ -219,7 +219,14 @@ return [
'cache_engine' => [
'name' => 'Cache Engine',
'type' => 'options',
'options' => ['auto' => 'Auto', 'file' => 'Files', 'apc' => 'APC', 'apcu' => 'APCu', 'disable' => 'Disable'],
'options' => [
'auto' => 'Auto',
'file' => 'Files',
'apc' => 'APC',
'apcu' => 'APCu',
'php' => 'PHP',
'disable' => 'Disable',
],
'desc' => 'Auto is most reasonable. It will detect the best cache engine',
'default' => 'auto',
'is_config' => true,
@@ -251,6 +258,28 @@ return [
'desc' => 'Allow MyAAC to report anonymous usage statistics to developers? The data is sent only once per 30 days and is fully confidential. It won\'t affect the performance of your website',
'default' => true,
],
[
'type' => 'section',
'title' => 'Custom HTML',
],
'html_head' => [
'name' => 'HTML Head',
'type' => 'textarea',
'desc' => escapeHtml('These scripts will be printed in the <head> section. Can be, for example, Google Analytics code.'),
'default' => '',
],
'html_body' => [
'name' => 'HTML Body',
'type' => 'textarea',
'desc' => escapeHtml('These scripts will be printed just below the opening <body> tag.'),
'default' => '',
],
'html_footer' => [
'name' => 'HTML Footer',
'type' => 'textarea',
'desc' => escapeHtml('These scripts will be printed above the closing </body> tag.'),
'default' => '',
],
[
'type' => 'category',
'title' => 'Game',
@@ -312,20 +341,31 @@ return [
},
],
],
/**
* @deprecated
* To be removed in v3.0
*/
'vocations_amount' => [
'name' => 'Vocations Amount',
'hidden' => true,
'type' => 'number',
'desc' => 'How much basic vocations your server got (without promotion)',
//'name' => 'Vocations Amount',
//'desc' => 'How many basic vocations your server got (without promotion)',
'default' => 4,
'callbacks' => [
'get' => function () {
return config('vocations_amount');
},
],
],
'vocations' => [
'name' => 'Vocation Names',
'hidden' => true,
'type' => 'textarea',
'desc' => 'Separated by comma. Must be in the same order as in vocations.xml, starting with id: 0.',
//'name' => 'Vocation Names',
//'desc' => 'Separated by comma. Must be in the same order as in vocations.xml, starting with id: 0.',
'default' => 'None, Sorcerer, Druid, Paladin, Knight, Master Sorcerer, Elder Druid,Royal Paladin, Elite Knight',
'callbacks' => [
'get' => function ($value) {
return array_map('trim', explode(',', $value));
'get' => function () {
return config('vocations');
},
],
],
@@ -1704,6 +1744,18 @@ Sent by MyAAC,<br/>
'account_login_ipban_protection', '=', 'true'
]
],
[
'type' => 'section',
'title' => 'Cooldowns',
],
'account_create_ip_block_cooldown' => [
'name' => 'Create Account IP Block Cooldown',
'type' => 'number',
'desc' => 'Block flooding create account per ip. If you still have a problem with account create spam - then its recommended to install the recaptcha plugin.' .
'<br/><strong>In minutes.</strong> 0 to disable.',
'default' => 10,
],
],
'callbacks' => [
'beforeSave' => function(&$settings, &$values) {

View File

@@ -13,8 +13,8 @@ namespace MyAAC\Cache;
class APC
{
private $prefix;
private $enabled;
private string $prefix;
private bool $enabled;
public function __construct($prefix = '')
{
@@ -22,14 +22,14 @@ class APC
$this->enabled = function_exists('apc_fetch');
}
public function set($key, $var, $ttl = 0)
public function set($key, $var, $ttl = 0): void
{
$key = $this->prefix . $key;
apc_delete($key);
apc_store($key, $var, $ttl);
}
public function get($key)
public function get($key): string
{
$tmp = '';
if ($this->fetch($this->prefix . $key, $tmp)) {
@@ -39,18 +39,15 @@ class APC
return '';
}
public function fetch($key, &$var)
{
public function fetch($key, &$var): bool {
return ($var = apc_fetch($this->prefix . $key)) !== false;
}
public function delete($key)
{
public function delete($key): void {
apc_delete($this->prefix . $key);
}
public function enabled()
{
public function enabled(): bool {
return $this->enabled;
}
}

View File

@@ -13,8 +13,8 @@ namespace MyAAC\Cache;
class APCu
{
private $prefix;
private $enabled;
private string $prefix;
private bool $enabled;
public function __construct($prefix = '')
{
@@ -22,14 +22,14 @@ class APCu
$this->enabled = function_exists('apcu_fetch');
}
public function set($key, $var, $ttl = 0)
public function set($key, $var, $ttl = 0): void
{
$key = $this->prefix . $key;
apcu_delete($key);
apcu_store($key, $var, $ttl);
}
public function get($key)
public function get($key): string
{
$tmp = '';
if ($this->fetch($this->prefix . $key, $tmp)) {
@@ -39,18 +39,15 @@ class APCu
return '';
}
public function fetch($key, &$var)
{
public function fetch($key, &$var): bool {
return ($var = apcu_fetch($this->prefix . $key)) !== false;
}
public function delete($key)
{
public function delete($key): void {
apcu_delete($this->prefix . $key);
}
public function enabled()
{
public function enabled(): bool {
return $this->enabled;
}
}

View File

@@ -47,35 +47,15 @@ class Cache
return self::$instance;
}
switch (strtolower($engine)) {
case 'apc':
self::$instance = new APC($prefix);
break;
case 'apcu':
self::$instance = new APCu($prefix);
break;
case 'xcache':
self::$instance = new XCache($prefix);
break;
case 'file':
self::$instance = new File($prefix, CACHE);
break;
case 'php':
self::$instance = new PHP($prefix, CACHE);
break;
case 'auto':
self::$instance = self::generateInstance(self::detect(), $prefix);
break;
default:
self::$instance = new self();
break;
}
self::$instance = match (strtolower($engine)) {
'apc' => new APC($prefix),
'apcu' => new APCu($prefix),
'xcache' => new XCache($prefix),
'file' => new File($prefix, CACHE),
'php' => new PHP($prefix, CACHE),
'auto' => self::generateInstance(self::detect(), $prefix),
default => new self(),
};
return self::$instance;
}
@@ -83,7 +63,7 @@ class Cache
/**
* @return string
*/
public static function detect()
public static function detect(): string
{
if (function_exists('apc_fetch'))
return 'apc';
@@ -98,8 +78,7 @@ class Cache
/**
* @return bool
*/
public function enabled()
{
public function enabled(): bool {
return false;
}

View File

@@ -12,18 +12,22 @@ namespace MyAAC\Cache;
class File
{
private $prefix;
private $dir;
private $enabled;
private string $prefix;
private string $dir;
private bool $enabled;
public function __construct($prefix = '', $dir = '')
{
$this->prefix = $prefix;
$this->dir = $dir;
ensureFolderExists($this->dir);
ensureIndexExists($this->dir);
$this->enabled = (file_exists($this->dir) && is_dir($this->dir) && is_writable($this->dir));
}
public function set($key, $var, $ttl = 0)
public function set($key, $var, $ttl = 0): void
{
$file = $this->_name($key);
file_put_contents($file, $var);
@@ -35,7 +39,7 @@ class File
touch($file, time() + $ttl);
}
public function get($key)
public function get($key): string
{
$tmp = '';
if ($this->fetch($key, $tmp)) {
@@ -45,7 +49,7 @@ class File
return '';
}
public function fetch($key, &$var)
public function fetch($key, &$var): bool
{
$file = $this->_name($key);
if (!file_exists($file) || filemtime($file) < time()) {
@@ -56,7 +60,7 @@ class File
return true;
}
public function delete($key)
public function delete($key): void
{
$file = $this->_name($key);
if (file_exists($file)) {
@@ -64,13 +68,11 @@ class File
}
}
public function enabled()
{
public function enabled(): bool {
return $this->enabled;
}
private function _name($key)
{
private function _name($key): string {
return sprintf('%s%s%s', $this->dir, $this->prefix, sha1($key));
}
}

View File

@@ -12,36 +12,37 @@ namespace MyAAC\Cache;
class PHP
{
private $prefix;
private $dir;
private $enabled;
private string $prefix;
private string $dir;
private bool $enabled;
public function __construct($prefix = '', $dir = '')
{
$this->prefix = $prefix;
$this->dir = $dir;
$this->enabled = (file_exists($this->dir) && is_dir($this->dir) && is_writable($this->dir));
}
public function set($key, $var, $ttl = 0)
{
$var = var_export($var, true);
ensureFolderExists($this->dir);
ensureIndexExists($this->dir);
// Write to temp file first to ensure atomicity
$tmp = $this->dir . "tmp_$key." . uniqid('', true) . '.tmp';
file_put_contents($tmp, '<?php $var = ' . $var . ';', LOCK_EX);
$this->enabled = (file_exists($this->dir) && is_dir($this->dir) && is_writable($this->dir));
}
$file = $this->_name($key);
rename($tmp, $file);
public function set($key, $var, $ttl = 0): void
{
$var = var_export($var, true);
if ($ttl === 0) {
$ttl = 365 * 24 * 60 * 60; // 365 days
}
touch($file, time() + $ttl);
$expires = time() + $ttl;
// Write to temp file first to ensure atomicity
$tmp = $this->dir . "tmp_$key." . uniqid('', true) . '.tmp';
file_put_contents($tmp, "<?php return ['expires' => $expires, 'var' => $var];", LOCK_EX);
$file = $this->_name($key);
rename($tmp, $file);
}
public function get($key)
@@ -54,19 +55,23 @@ class PHP
return '';
}
public function fetch($key, &$var)
public function fetch($key, &$var): bool
{
$file = $this->_name($key);
if (!file_exists($file) || filemtime($file) < time()) {
if (!file_exists($file)) {
return false;
}
@include $file;
$var = isset($var) ? $var : null;
$content = include $file;
if (!isset($content) || $content['expires'] < time()) {
return false;
}
$var = $content['var'];
return true;
}
public function delete($key)
public function delete($key): void
{
$file = $this->_name($key);
if (file_exists($file)) {
@@ -74,13 +79,11 @@ class PHP
}
}
public function enabled()
{
public function enabled(): bool {
return $this->enabled;
}
private function _name($key)
{
private function _name($key): string {
return sprintf('%s%s%s', $this->dir, $this->prefix, sha1($key) . '.php');
}
}

View File

@@ -13,8 +13,8 @@ namespace MyAAC\Cache;
class XCache
{
private $prefix;
private $enabled;
private string $prefix;
private bool $enabled;
public function __construct($prefix = '')
{
@@ -22,14 +22,14 @@ class XCache
$this->enabled = function_exists('xcache_get') && ini_get('xcache.var_size');
}
public function set($key, $var, $ttl = 0)
public function set($key, $var, $ttl = 0): void
{
$key = $this->prefix . $key;
xcache_unset($key);
xcache_set($key, $var, $ttl);
}
public function get($key)
public function get($key): string
{
$tmp = '';
if ($this->fetch($this->prefix . $key, $tmp)) {
@@ -39,7 +39,7 @@ class XCache
return '';
}
public function fetch($key, &$var)
public function fetch($key, &$var): bool
{
$key = $this->prefix . $key;
if (!xcache_isset($key)) {
@@ -50,13 +50,11 @@ class XCache
return true;
}
public function delete($key)
{
public function delete($key): void {
xcache_unset($this->prefix . $key);
}
public function enabled()
{
public function enabled(): bool {
return $this->enabled;
}
}

View File

@@ -0,0 +1,50 @@
<?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 GiveAdminCommand extends Command
{
protected function configure(): void
{
$this->setName('give:admin')
->setDescription('This command adds super admin privileges to selected user')
->addArgument('account', InputArgument::REQUIRED, 'Account E-Mail, name or id');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
require SYSTEM . 'init.php';
$io = new SymfonyStyle($input, $output);
$account = new \OTS_Account();
$accountParam = $input->getArgument('account');
if (str_contains($accountParam, '@')) {
$account->findByEMail($accountParam);
}
else {
if (USE_ACCOUNT_NAME || USE_ACCOUNT_NUMBER) {
$account->find($accountParam);
}
else {
$account->load($accountParam);
}
}
if (!$account->isLoaded()) {
$io->error('Cannot find account mit supplied parameter: ' . $accountParam);
return self::FAILURE;
}
$account->setCustomField('web_flags', 3);
$io->success('Successfully added admin privileges to ' . $accountParam . ' (E-Mail: ' . $account->getEMail() . ')');
return self::SUCCESS;
}
}

View File

@@ -149,7 +149,10 @@ class CreateCharacter
if($db->hasColumn('players', 'direction'))
$player->setDirection($playerSample->getDirection());
if($db->hasColumn('players', 'conditions')) {
$player->setConditions($playerSample->getConditions());
}
$rank = $playerSample->getRank();
if($rank->isLoaded()) {
$player->setRank($playerSample->getRank());
@@ -183,7 +186,11 @@ class CreateCharacter
$player->setLookHead($playerSample->getLookHead());
$player->setLookLegs($playerSample->getLookLegs());
$player->setLookType($playerSample->getLookType());
if($db->hasColumn('players', 'cap')) {
$player->setCap($playerSample->getCap());
}
$player->setBalance(0);
$player->setPosX(0);
$player->setPosY(0);

View File

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

View File

@@ -0,0 +1,18 @@
<?php
namespace MyAAC\Models;
use Illuminate\Database\Eloquent\Model;
class AccountBan extends Model {
protected $table = TABLE_PREFIX . 'account_bans';
public $timestamps = false;
protected $fillable = [
'account_id',
'reason', 'banned_at',
'expires_at', 'banned_by'
];
}

View File

@@ -1,18 +0,0 @@
<?php
namespace MyAAC\Models;
use Illuminate\Database\Eloquent\Model;
class Gallery extends Model {
protected $table = TABLE_PREFIX . 'gallery';
public $timestamps = false;
protected $fillable = [
'comment', 'image', 'thumb',
'author', 'ordering', 'hide',
];
}

View File

@@ -9,6 +9,6 @@ class Menu extends Model {
public $timestamps = false;
protected $fillable = ['template', 'name', 'link', 'blank', 'color', 'category', 'ordering', 'enabled'];
protected $fillable = ['template', 'name', 'link', 'access', 'blank', 'color', 'category', 'ordering', 'enabled'];
}

View File

@@ -23,6 +23,8 @@ class Player extends Model {
public $timestamps = false;
protected $guarded = [];
protected $casts = [
'worldid' => 'integer',
'sex' => 'integer',
@@ -46,14 +48,8 @@ class Player extends Model {
});
}
public function getVocationNameAttribute()
{
$vocation = $this->vocation;
if (isset($this->promotion) && $this->promotion > 0) {
$vocation += ($this->promotion * setting('core.vocations_amount'));
}
return config('vocations')[$vocation] ?? 'Unknown';
public function getVocationNameAttribute() {
return \OTS_Toolbox::getVocationName($this->vocation, $this->promotion ?? 0);
}
public function getIsDeletedAttribute()

View File

@@ -0,0 +1,101 @@
<?php
namespace MyAAC\Server\XML;
use MyAAC\Cache\Cache;
class Vocations
{
private static array $vocations;
private static array $vocationsFrom;
public function __construct()
{
$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
{
if(!class_exists('DOMDocument')) {
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)) {
$file = config('data_path') . 'vocations.xml';
}
if(!$vocationsXML->load($file)) {
throw new \RuntimeException('ERROR: Cannot load <i>vocations.xml</i> - the file is malformed. Check the file with xml syntax validator.');
}
foreach($vocationsXML->getElementsByTagName('vocation') as $vocation) {
$id = $vocation->getAttribute('id');
self::$vocations[$id] = $vocation->getAttribute('name');
$fromVocation = (int) $vocation->getAttribute('fromvoc');
self::$vocationsFrom[$id] = $fromVocation;
}
}
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 {
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

@@ -152,9 +152,10 @@ class Settings implements \ArrayAccess
}
if ($setting['type'] === 'category') {
$title = strtolower(str_replace(' ', '', $setting['title']));
?>
<li class="nav-item">
<a class="nav-link<?= ($i === 0 ? ' active' : ''); ?>" id="home-tab-<?= $i++; ?>" data-toggle="tab" href="#tab-<?= str_replace(' ', '', $setting['title']); ?>" type="button"><?= $setting['title']; ?></a>
<a class="nav-link<?= ($i === 0 ? ' active' : ''); ?>" id="home-tab-<?= $i++; ?>" data-toggle="tab" href="#tab-<?= $title; ?>" type="button"><?= $setting['title']; ?></a>
</li>
<?php
}
@@ -175,8 +176,10 @@ class Settings implements \ArrayAccess
if ($j++ !== 0) { // close previous category
echo '</tbody></table></div>';
}
$title = strtolower(str_replace(' ', '', $setting['title']));
?>
<div class="tab-pane fade show<?= ($j === 1 ? ' active' : ''); ?>" id="tab-<?= str_replace(' ', '', $setting['title']); ?>">
<div class="tab-pane fade show<?= ($j === 1 ? ' active' : ''); ?>" id="tab-<?= $title; ?>">
<?php
continue;
}

View File

@@ -69,6 +69,14 @@ define('HOOK_ACCOUNT_LOGIN_AFTER_PASSWORD', ++$i);
define('HOOK_ACCOUNT_LOGIN_AFTER_REMEMBER_ME', ++$i);
define('HOOK_ACCOUNT_LOGIN_AFTER_PAGE', ++$i);
define('HOOK_ACCOUNT_LOGIN_POST', ++$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_EMAIL_SET_NEW_PASSWORD_POST', ++$i);
define('HOOK_ACCOUNT_LOST_RECOVERY_KEY_STEP_2_AFTER_CHARACTER', ++$i);
define('HOOK_ACCOUNT_LOST_RECOVERY_KEY_STEP_2_AFTER_EMAIL', ++$i);
define('HOOK_ACCOUNT_LOST_RECOVERY_KEY_STEP_2_AFTER_PASSWORD', ++$i);
define('HOOK_ACCOUNT_LOST_RECOVERY_KEY_STEP_2_AFTER_PASSWORD_REPEAT', ++$i);
define('HOOK_ACCOUNT_LOST_RECOVERY_KEY_STEP_3_POST', ++$i);
define('HOOK_ACCOUNT_CREATE_CHARACTER_AFTER', ++$i);
define('HOOK_ACCOUNT_CREATE_CHARACTER_BEFORE_FIRST_TABLE', ++$i);
define('HOOK_ACCOUNT_CREATE_CHARACTER_BEFORE_VOCATIONS', ++$i);

View File

@@ -146,10 +146,10 @@ if($twig_loader) {
function get_template_menus(): array
{
global $template_name;
global $template_name, $logged_access;
$result = Cache::remember('template_menus_' . $template_name, 10 * 60, function () use ($template_name) {
$result = Menu::select(['name', 'link', 'blank', 'color', 'category'])
$result = Menu::select(['name', 'link', 'access', 'blank', 'color', 'category'])
->where('template', $template_name)
->orderBy('category')
->orderBy('ordering')
@@ -163,6 +163,10 @@ function get_template_menus(): array
$menus = [];
foreach($result as $menu) {
if ($menu['access'] > $logged_access) {
continue;
}
if (empty($menu['link'])) {
$menu['link'] = 'news';
}

View File

@@ -1,7 +1,26 @@
{% if new_line is defined and new_line %}
<br/>
{% endif %}
{% set _center = false %}
{% if center is defined and center %}
{% set _center = true %}
{% endif %}
{% if _center %}
<table border="0" cellspacing="1" cellpadding="4" width="100%">
<tbody>
<tr>
<td align="center">
{% endif %}
<form action="{% if action is not defined %}{{ getLink('account/manage') }}{% else %}{{ action }}{% endif %}" method="post">
{{ csrf() }}
{{ include('buttons.back.html.twig') }}
</form>
{% if _center %}
</td>
</tr>
</tbody>
</table>
{% endif %}

View File

@@ -1,36 +0,0 @@
The Lost Account Interface can help you to get back your account name and password. Please enter your character name and select what you want to do.<br/>
<form action="{{ getLink('account/lost') }}?action=step1" method="post">
{{ csrf() }}
<input type="hidden" name="character" value="">
<table cellspacing="1" cellpadding="4" border="0" width="100%">
<tr>
<td bgcolor="{{ config.vdarkborder }}" class="white"><b>Please enter your character name</b></td>
</tr>
<tr>
<td bgcolor="{{ config.darkborder }}">
<input type="text" name="nick" size="40" autofocus/><br>
</td>
</tr>
</table>
<table cellspacing="1" cellpadding="4" border="0" width="100%">
<tr>
<td bgcolor="{{ config.vdarkborder }}" class="white"><b>What do you want?</b></td>
</tr>
<tr>
<td bgcolor="{{ config.darkborder }}">
<input type="radio" name="action_type" id="action_type_email" value="email">
<label for="action_type_email"> Send me new password and my account name to account e-mail adress.</label><br/>
<input type=radio name="action_type" id="action_type_key" value="reckey">
<label for="action_type_key"> I got <b>recovery key</b> and want set new password and e-mail adress to my account.</label><br/>
</td>
</tr>
</table>
<br/>
<table cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td align="center">
{{ include('buttons.submit.html.twig') }}
</td>
</tr>
</table>
</form>

View File

@@ -1,10 +0,0 @@
Please select action.<br/>
<table cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td align="center">
<a href="{{ getLink('account/lost') }}" border="0">
{{ include('buttons.back.html.twig') }}
</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,56 @@
Please enter new password to your account and repeat to make sure you remember password.<BR>
<form action="{{ getLink('account/lost/email/set-new-password') }}" method="post">
{{ csrf() }}
<input type="hidden" name="character" value="{{ character }}">
<input type="hidden" name="code" value="{{ code }}">
<table class="myaac-table" style="width: 100%;">
<thead>
<tr>
<th class="white"><b>Passwords</b></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<table>
<tr>
<td>
<label for="password"><strong>New password:</strong></label>
</td>
<td>
<input type="password" id="password" name="password" value="" size="40">
</td>
</tr>
{{ hook('HOOK_ACCOUNT_LOST_CHECK_CODE_FINISH_AFTER_PASSWORD') }}
<tr>
<td>
<label for="password_repeat"><strong>Repeat the new password:</strong></label>
</td>
<td>
<input type="password" id="password_repeat" name="password_repeat" value="" size="40">
</td>
</tr>
{{ hook('HOOK_ACCOUNT_LOST_CHECK_CODE_FINISH_AFTER_PASSWORD_REPEAT') }}
</table>
</td>
</tr>
</tbody>
</table>
<br/>
<table style="width: 100%">
<tr>
<td>
<div style="text-align: center">
{% set button_name = 'Submit' %}
{% include('buttons.base.html.twig') %}
</div>
</td>
</tr>
</table>
</form>

View File

@@ -0,0 +1,33 @@
Please enter code from e-mail and name of one character from account. Then press Submit.<br/>
<form action="{{ getLink('account/lost/check-code') }}" method="post">
{{ csrf() }}
<table class="myaac-table" style="width: 100%;">
<thead>
<tr>
<th class="white">
<b>Code & character name</b>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
Your code:&nbsp;<input type="text" name="code" value="{{ code }}" size="40"><br/>
Character:&nbsp;<input type="text" name="character" value="{{ character }}" size="40"><br/>
</td>
</tr>
</tbody>
</table>
<br>
<table style="width: 100%">
<tr>
<td align="center">
{% set button_name = 'Submit' %}
{% include('buttons.base.html.twig') %}
</td>
</tr>
</table>
</form>

View File

@@ -0,0 +1,54 @@
Please enter e-mail to account with this character.<br/>
<form action="{{ getLink('account/lost/email/send-code') }}" method="post">
{{ csrf() }}
<input type=hidden name="character">
<table class="myaac-table" style="width: 100%;">
<thead>
<tr>
<th class="white"><b>Please enter e-mail to account</b></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<table>
<tr>
<td>
<label for="nick">Character:</label>
</td>
<td>
<input type=text id="nick" name="nick" value="{{ nick }}" size="40" readonly="readonly">
</td>
</tr>
<tr>
<td>
<label for="name">E-mail to account:</label>
</td>
<td>
<input type="email" id="name" name="email" value="" size="40">
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<br>
<table style="width: 100%">
<tr>
<td>
<div style="text-align:center">
{% set button_name = 'Submit' %}
{% include('buttons.base.html.twig') %}
</div>
</td>
</tr>
</table>
</form>

View File

@@ -0,0 +1,58 @@
Your account name, new password and new e-mail.<br/>
<table class="myaac-table" style="width: 100%;">
<thead>
<tr>
<th class="white">
<b>Your account name, new password and new e-mail</b>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<table>
<tr>
<td>
Account name:
</td>
<td>
<b>{{ account.getName() }}</b>
</td>
</tr>
<tr>
<td>
New password:
</td>
<td>
<b>{{ newPassword }}</b>
</td>
</tr>
<tr>
<td>
New e-mail address:
</td>
<td>
<b>{{ newEmail }}</b>
</td>
</tr>
</table>
{{ statusMsg|raw }}
</td>
</tr>
</tbody>
</table>
<br>
<table style="width: 100%">
<tr>
<td align="center">
<form action="{{ getLink('account/manage') }}" method="post">
{{ include('buttons.login.html.twig') }}
</form>
</td>
</tr>
</table>

View File

@@ -0,0 +1,30 @@
New password to your account is below. Now you can log in.<BR>
<table class="myaac-table" style="width: 100%;">
<thead>
<tr>
<th class="white"><b>Changed password</b></th>
</tr>
</thead>
<tbody>
<tr>
<td>
New password:&nbsp;<b>{{ newPassword }}</b><br/>
Account name:&nbsp;&nbsp;&nbsp;<i>(Already on your e-mail)</i><br/>
{{ statusMsg|raw }}
</td>
</tr>
</tbody>
</table>
<br/>
<table style="width: 100%">
<tr>
<td align="center">
<form action="{{ getLink('account/manage') }}">
{% set button_name = 'Login' %}
{% include('buttons.base.html.twig') %}
</form>
</td>
</tr>
</table>

View File

@@ -0,0 +1,43 @@
The Lost Account Interface can help you to get back your account name and password. Please enter your character name and select what you want to do.<br/>
<form action="{{ getLink('account/lost/step-1') }}" method="post">
{{ csrf() }}
<input type="hidden" name="character" value="">
<table class="myaac-table" style="width: 100%">
<thead>
<tr>
<th class="white"><b>Please enter your character name</b></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<input type="text" name="nick" size="40" autofocus/><br>
</td>
</tr>
</tbody>
</table>
<table style="width: 100%; border-spacing: 1px">
<tr>
<td style="padding: 4px; background: {{ config('vdarkborder') }}" class="white"><b>What do you want?</b></td>
</tr>
<tr>
<td style="padding: 4px; background: {{ config('darkborder') }}">
<input type="radio" name="action" id="action_type_email" value="email">
<label for="action_type_email"> Send me new password and my account name to account e-mail address.</label><br/>
<input type=radio name="action" id="action_type_key" value="recovery-key">
<label for="action_type_key"> I got <b>recovery key</b> and want set new password and e-mail address to my account.</label><br/>
</td>
</tr>
</table>
<br/>
<table style="width: 100%">
<tr>
<td align="center">
{% set button_name = 'Submit' %}
{% include('buttons.base.html.twig') %}
</td>
</tr>
</table>
</form>

View File

@@ -0,0 +1,10 @@
Please select action.<br/>
<table style="width: 100%">
<tr>
<td align="center">
<a href="{{ getLink('account/lost') }}">
{{ include('buttons.back.html.twig') }}
</a>
</td>
</tr>
</table>

View File

@@ -0,0 +1,57 @@
If you enter right recovery key you will see form to set new e-mail and password to account. To this e-mail will be send your new password and account name.<BR>
<form action="{{ getLink('account/lost/recovery-key/step-2') }}" method="post">
{{ csrf() }}
<table class="myaac-table" style="width: 100%;">
<thead>
<tr>
<th class="white">
<b>Please enter your recovery key</b>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<table>
<tr>
<td>
<label for="nick">
<strong>Character name:</strong>
</label>
</td>
<td>
<input type=text id="nick" name="nick" value="{{ nick }}" size="40" readonly="readonly">
</td>
</tr>
<tr>
<td>
<label for="key">
<strong>Recovery key:</strong>
</label>
</td>
<td>
<input type="text" id="key" name="key" value="{{ key }}" size="40">
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<br>
<table style="width: 100%">
<tr>
<td>
<div style="text-align:center">
{% set button_name = 'Submit' %}
{% include('buttons.base.html.twig') %}
</div>
</td>
</tr>
</table>
</form>

View File

@@ -0,0 +1,90 @@
Set new password and e-mail to your account.<br>
<form action="{{ getLink('account/lost/recovery-key/step-3') }}" method="post">
{{ csrf() }}
<input type="hidden" name="key" VALUE="{{ key }}">
<input type="hidden" name="character" value="">
<table class="myaac-table" style="width: 100%">
<thead>
<tr>
<th class="white">
<strong>Please enter new password and e-mail</strong>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<table>
<tr>
<td>
<label for="nick">
<strong>Account of character:</strong>
</label>
</td>
<td>
<input type="text" id="nick" name="nick" value="{{ nick }}" size="40" readonly="readonly">
</td>
</tr>
{{ hook('HOOK_ACCOUNT_LOST_RECOVERY_KEY_STEP_2_AFTER_CHARACTER') }}
<tr>
<td>
<label for="password">
<strong>New password:</strong>
</label>
</td>
<td>
<input type="password" id="password" name="password" value="" size="40">
</td>
</tr>
{{ hook('HOOK_ACCOUNT_LOST_RECOVERY_KEY_STEP_2_AFTER_PASSWORD') }}
<tr>
<td>
<label for="password_repeat">
<strong>Repeat the new password:</strong>
</label>
</td>
<td>
<input type="password" id="password_repeat" name="password_repeat" value="" size="40">
</td>
</tr>
{{ hook('HOOK_ACCOUNT_LOST_RECOVERY_KEY_STEP_2_AFTER_PASSWORD_REPEAT') }}
<tr>
<td>
<label for="email">
<strong>New e-mail address:</strong>
</label>
</td>
<td>
<input type="text" id="email" name="email" value="" size="40">
</td>
</tr>
{{ hook('HOOK_ACCOUNT_LOST_RECOVERY_KEY_STEP_2_AFTER_EMAIL') }}
</table>
</td>
</tr>
</tbody>
</table>
<br>
<table style="width: 100%">
<tr>
<td align="center">
{% set button_name = 'Submit' %}
{% include('buttons.base.html.twig') %}
</td>
</tr>
</table>
</form>

View File

@@ -0,0 +1,33 @@
<link rel="stylesheet" type="text/css" href="{{ constant('BASE_URL') }}tools/css/toastify.min.css">
<script type="text/javascript" src="{{ constant('BASE_URL') }}tools/js/toastify.min.js"></script>
{{ include('script.ajax-setup.html.twig') }}
<script>
$(function () {
/* Store sidebar state */
$('.sidebar-toggle').on('click',function(event) {
event.preventDefault();
const isCollapsed = $('body').hasClass('sidebar-collapse');
$.ajax({
type: 'POST',
url: '{{ constant('ADMIN_URL') }}tools/menu_collapse.php',
data : { collapse: !isCollapsed},
success : function(response) {
},
error : function(response) {
Toastify({
position: 'center',
text: response.responseText,
duration: 3000,
style: {
background: 'red',
},
escapeMarkup: false,
}).showToast();
}
});
});
});
</script>

View File

@@ -2,7 +2,8 @@
<p class="note">You are editing: {{ template }}<br/><br/>
Hint: You can drag menu items.<br/>
Hint: Add links to external sites using: <b>http://</b> or <b>https://</b> prefix.<br/>
Not all templates support blank and colorful links.
Not all templates support blank and colorful links.<br/>
* Guest means everyone will see the link. Player means registered and logged-in user.
</p>
<div class="row text-center">
<div class="col-md-2 col-sm-1"></div>

View File

@@ -14,42 +14,62 @@
colors[{{ cat }}] = '{{ options['default_links_color'] ?? (menuDefaultLinksColor ?? config('menu_default_color')) }}';
{% endfor %}
function confirmRemoveMenuItem(that)
{
let id = $(that).attr("id");
if (confirm('Are you sure, that you want to remove this element?')) {
$('#list-' + id.replace('remove-button-', '')).remove();
}
}
$(function () {
const $sortable = $(".sortable");
$sortable.sortable();
$sortable.disableSelection();
$(".remove-button").on('click', function () {
var id = $(this).attr("id");
$('#list-' + id.replace('remove-button-', '')).remove();
confirmRemoveMenuItem(this);
});
$(".add-button").on('click', function () {
var cat = $(this).attr("id").replace('add-button-', '');
var id = last_id[cat];
let cat = $(this).attr("id").replace('add-button-', '');
let id = last_id[cat];
last_id[cat]++;
const color = colors[cat];
$('#sortable-' + cat).append('<li class="ui-state-default" id="list-' + cat + '-' + id + '"><label>Name:</label> <input type="text" name="menu[' + cat + '][]" value=""/> <label>Link:</label> <input type="text" name="menu_link[' + cat + '][]" value=""/><input type="hidden" name="menu_blank[' + cat + '][]" value="0" /> <label><input class="blank-checkbox" type="checkbox"/><span title="Open in New Window">New Window</span></label> <input class="color-picker" type="text" name="menu_color[' + cat + '][]" value="#' + color + '" /> <a class="remove-button" id="remove-button-' + cat + '-' + id + '"><i class="fas fa-trash"></i></a></li>'); //add input bo
$('#remove-button-' + cat + '-' + id).on('click', function () {
$('#list-' + $(this).attr("id").replace('remove-button-', '')).remove();
});
initializeSpectrum();
let copy = $('.ui-state-default:first').clone();
copy.attr('id', 'list-' + cat + '-' + id);
copy.find('.menu-name').val('').attr('name', 'menu[' + cat + '][]');
copy.find('.menu-link').val('').attr('name', 'menu_link[' + cat + '][]');
copy.find('.menu-access').val('0').attr('name', 'menu_access[' + cat + '][]');
copy.find('.menu-color').val(color).attr('name', 'menu_color[' + cat + '][]');
copy.find('.menu-blank').attr('name', 'menu_blank[' + cat + '][]');
copy.find('.menu-blank-checkbox').prop('checked', false);
copy.find('.remove-button').attr('id', 'remove-button-' + cat + '-' + id);
$('#sortable-' + cat).append(copy);
$('#remove-button-' + cat + '-' + id).on('click', function () {
confirmRemoveMenuItem(this);
});
});
$("#menus-form").on('submit', function (e) {
$('.blank-checkbox:not(:checked)').each(function (i, obj) {
$('.menu-blank-checkbox:not(:checked)').each(function (i, obj) {
$(obj).parent().prev().val("off");
});
$('.blank-checkbox:checked').each(function (i, obj) {
$('.menu-blank-checkbox:checked').each(function (i, obj) {
$(obj).parent().prev().val("on");
});
});
});
</script>
<style type="text/css">
<style>
.sortable {
list-style-type: none;
margin: 0;
@@ -60,24 +80,16 @@
.remove-button, .add-button {
cursor: pointer;
}
</style>
<script type="text/javascript" src="{{ constant('BASE_URL') }}tools/js/spectrum.js"></script>
<link type="text/css" rel="stylesheet" href="{{ constant('BASE_URL') }}tools/css/spectrum.css"/>
<script type="text/javascript">
$(function () {
initializeSpectrum();
});
function initializeSpectrum() {
$(".color-picker").spectrum({
preferredFormat: "hex",
showInput: true,
showPalette: true,
palette: [
['black', 'white', 'blanchedalmond',
'rgb(255, 128, 0);', 'hsv 100 70 50'],
['red', 'yellow', 'green', 'blue', 'violet']
]
});
.ui-sortable-handle {
padding: 10px;
}
</script>
.label_menu_name, .label_menu_link {
width: 45%;
}
.menu-name, .menu-link {
width: 100%;
}
</style>

View File

@@ -83,13 +83,9 @@
<!-- jQuery Form Submit No Refresh + Toastify -->
<link rel="stylesheet" type="text/css" href="{{ constant('BASE_URL') }}tools/css/toastify.min.css">
<script type="text/javascript" src="{{ constant('BASE_URL') }}tools/js/toastify.min.js"></script>
<script>
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
{{ include('script.ajax-setup.html.twig') }}
<script>
const noChangesText = "No changes has been made";
$('form')
@@ -169,3 +165,31 @@
{% endif %}
{% endfor %}
</script>
<script>
$(function () {
if ($('.tab-content').length) {
let url = window.location.href;
let activeTab = url.substring(url.indexOf("#") + 1);
let $selector = $('[href="#' + activeTab + '"]')
if ($selector.length) {
$selector.tab('show');
}
}
let url = location.href.replace(/\/$/, "");
$('a[data-toggle="tab"]').on("click", function() {
let newUrl;
const hash = $(this).attr("href");
if(hash === "#home") {
newUrl = url.split("#")[0];
} else {
newUrl = url.split("#")[0] + hash;
}
history.replaceState(null, null, newUrl);
});
});
</script>

View File

@@ -0,0 +1,57 @@
<a href="{{ getLink('forum') }}">Boards</a> >> <b>{{ section_name }}</b>
{% if (logged and (not closed or is_moderator)) %}
<br/><br/>
<a href="{{ getLink('forum') }}?action=new_thread&section_id={{ section_id }}"><img src="images/forum/topic.gif" border="0" /></a>
{% endif %}
{% if threads|length > 0 %}
<br/><br/>Page: {{ links_to_pages|raw }}<br/>
<table width="100%">
<tr bgcolor=" {{ config.vdarkborder }}" align="center">
<td class="white"><span style="font-size: 10px"><b>Thread</b></span></td>
<td class="white"><span style="font-size: 10px"><b>Thread Starter</b></span></td>
<td class="white"><span style="font-size: 10px"><b>Replies</b></span></td>
<td class="white"><span style="font-size: 10px"><b>Views</b></span></td>
<td class="white"><span style="font-size: 10px"><b>Last Post</b></span></td>
</tr>
{% set i = 0 %}
{% for thread in threads %}
<tr bgcolor="{{ getStyle(i) }}">
<td>
{% if is_moderator %}
<a href="{{ getLink('forum') }}?action=move_thread&id={{ thread.id }}" title="Move Thread"><img src="images/icons/arrow_right.gif"/></a>
{{ include('forum.remove_post.html.twig', {post: thread}) }}
{% endif %}
<a href="{{ thread.link }}">{{ thread.post_topic }}</a><br />
<small>{{ thread.post_shortened|raw }}...</small>
</td>
<td>{{ thread.player_link|raw }}</td>
<td>{{ thread.replies }}</td>
<td>{{ thread.views }}</td>
<td>
{% if thread.last_post > 0 %}
{% if thread.latest_post.name is defined %}
{{ thread.latest_post.post_date|date('d.m.y H:i:s') }}<br />by {{ thread.latest_post.player_link|raw }}
{% else %}
No posts.
{% endif %}
{% else %}
{{ thread.post_date|date('d.m.y H:i:s') }}<br />by {{ thread.player_link|raw }}
{% endif %}
</td>
</tr>
{% set i = i + 1 %}
{% endfor %}
</table>
{% else %}
<h3>No threads in this board.</h3>
{% endif %}
{% if(logged and (not closed or is_moderator)) %}
<br /><a href="{{ getLink('forum') }}?action=new_thread&section_id={{ section_id }}"><img src="images/forum/topic.gif" border="0" /></a>
{% endif %}

View File

@@ -1,33 +0,0 @@
<form method="post" action="{{ link }}">
{{ csrf() }}
{% if action == 'edit' %}
<input type="hidden" name="id" value="{{ id }}" />
{% endif %}
<table width="100%" border="0" cellspacing="1" cellpadding="4">
<tr>
<td bgcolor="{{ config.vdarkborder }}" class="white"><b>{% if action == 'edit' %}Edit{% else %}Add{% endif %} image</b></td>
</tr>
<tr>
<td bgcolor="{{ config.darkborder }}">
<table border="0" cellpadding="1">
<tr>
<td>Comment:</td>
<td><textarea name="comment" maxlength="255" cols="50" rows="5">{% if comment is not null %}{{ comment }}{% endif %}</textarea></td>
<tr/>
<tr>
<td>Image URL:</td>
<td><input name="image" value="{% if image is not null %}{{ image }}{% endif %}" size="50" maxlength="255"/></td>
<tr/>
<tr>
<td>Author:</td>
<td><input name="author" value="{% if author is not null %}{{ author }}{% endif %}" size="50" maxlength="50"/></td>
<tr/>
<tr>
<td colspan="2" align="center"><input type="submit" value="Submit"/>
</tr>
</table>
</td>
</tr>
</table>
</form>
<br/><br/>

View File

@@ -1,15 +0,0 @@
<div style="position: relative; height: 15px; width: 100%;">
{% if next is not null %}
<a style="float: right;" href="{{ getLink('gallery') ~ '/' ~ next }}" >next <img src="images/arrow_right.gif" width=15 height=11 border=0 ></a>
{% endif %}
{% if previous is not null %}
<a style="position: absolute;" href="{{ getLink('gallery') ~ '/' ~ previous }}"><img src="images/arrow_left.gif" width=15 height=11 border=0 > previous</a>
{% endif %}
<div style="position: absolute; width: 80%; margin-left: 10%; margin-right: 10%; text-align: center;">
<a href="{{ getLink('gallery') }}" ><img src="images/arrow_up.gif" width=11 height=15 border=0 > back</a>
</div>
</div>
<div style="position: relative; text-align: center; top: 20px; ">
<img src="{{ image.image }}" />
<div style="margin-top: 15px; margin-bottom: 35px; ">{{ image.comment }}</div>
</div>

View File

@@ -1,38 +1,31 @@
Click on the image to enlarge.<br/><br/>
{% set i = 0 %}
<!-- Slideshow container -->
<div class="slideshow-container">
{% set i = 1 %}
{% for image in images %}
<div class="mySlides fade-effect">
<div class="numbertext">{{ i }} / {{ images|length }}</div>
<img src="{{ constant('GALLERY_DIR') }}{{ image }}" style="width:100%">
</div>
{% set i = i + 1 %}
<table>
<tr>
<td style="height: 120px;" >
<a href="{{ getLink('gallery') ~ '/' ~ image.id }}" >
<img src="{{ image.thumb }}" border="0" />
</a>
</td>
<td>{{ image.comment }}</td>
{% if canEdit %}
<td>
<a href="?subtopic=gallery&action=edit&id={{ image.id }}" title="Edit">
<img src="images/edit.png"/>Edit
</a>
<a id="delete" href="?subtopic=gallery&action=delete&id={{ image.id }}" onclick="return confirm('Are you sure?');" title="Delete">
<img src="images/del.png"/>Delete
</a>
<a href="?subtopic=gallery&action=hide&id={{ image.id }}" title="{% if image.hide != 1 %}Hide{% else %}Show{% endif %}">
<img src="images/{% if image.hide != 1 %}success{% else %}error{% endif %}.png"/>{% if image.hide != 1 %}Hide{% else %}Show{% endif %}
</a>
{% if i != 1 %}
<a href="?subtopic=gallery&action=moveup&id={{ image.id }}" title="Move up">
<img src="images/icons/arrow_up.gif"/>Move up
</a>
{% endif %}
{% if i != last %}
<a href="?subtopic=gallery&action=movedown&id={{ image.id }}" title="Move down">
<img src="images/icons/arrow_down.gif"/>Move down
</a>
{% endif %}
</td>
{% endif %}
</tr>
</table>
{% endfor %}
<!-- Next and previous buttons -->
<a class="prev" onclick="plusSlides(-1)">&#10094;</a>
<a class="next" onclick="plusSlides(1)">&#10095;</a>
</div>
<!-- The dots/circles -->
<div style="text-align:center">
{% set i = 1 %}
{% for image in images %}
<span class="dot" onclick="currentSlide({{ i }})"></span>
{% set i = i + 1 %}
{% endfor %}
</div>
<link rel="stylesheet" type="text/css" href="{{ constant('BASE_URL') }}tools/css/gallery.css" />
<script type="text/javascript" src="{{ constant('BASE_URL') }}tools/js/gallery.js"></script>

View File

@@ -18,7 +18,7 @@
<label for="vocationFilter">Choose a vocation</label>
<select onchange="location = this.value;" id="vocationFilter">
<option value="{{ getLink('highscores') }}/{{ list|urlencode }}" class="size_xs">[ALL]</option>
{% for i in 0..config.vocations_amount %}
{% for i in baseVocations %}
<option value="{{ getLink('highscores') }}/{{ list|urlencode }}/{{ config.vocations[i]|lower|urlencode }}" class="size_xs" {% if vocationId is not null and vocationId == i %}selected{% endif %}>{{ config.vocations[i]}}</option>
{% endfor %}
</select>
@@ -120,7 +120,7 @@
<tr bgcolor="{{ config.lightborder }}">
<td>
<a href="{{ getLink('highscores') }}/{{ list|urlencode }}" class="size_xs">[ALL]</a><br/>
{% for i in 0..config.vocations_amount %}
{% for i in baseVocations %}
<a href="{{ getLink('highscores') }}/{{ list|urlencode }}/{{ config.vocations[i]|lower|urlencode }}" class="size_xs">{{ config.vocations[i]}}</a><br/>
{% endfor %}
</td>

View File

@@ -0,0 +1,10 @@
You asked to reset your {{ config('lua')['serverName'] }} password.<br/>
<p>Account name: {{ account.getName() }}</p>
<br/>
To do so, please click this link:
<p>
<a href="{{ getLink('account/lost/check-code') }}?code={{ newCode }}&character={{ nick|urlencode }}">{{ getLink('account/lost/check-code') }}?code={{ newCode }}&character={{ nick|urlencode }}</a>
</p>
<p>or open page: <i>{{ getLink('account/lost/check-code') }}</i> and in field "code" write <b>{{ newCode }}</b></p>
<br/>
<p>If you did not request a password change, you may ignore this message and your password will remain unchanged.

View File

@@ -0,0 +1,7 @@
<h3>Your account name and new password!</h3>
<p>Changed password and e-mail to your account in Lost Account Interface on server <a href="{{ constant('BASE_URL') }}"><b>{{ config('lua')['serverName'] }}</b></a></p>
<p>Account name: <b>{{ account.getName() }}</b></p>
<p>New password: <b>{{ newPassword }}</b></p>
<p>E-mail: <b>{{ newEmail }}</b> (this e-mail)</p>
<br/>
<p><u>It's automatic e-mail from OTS Lost Account System. Do not reply!</u></p>

View File

@@ -0,0 +1,6 @@
<h3>Your account name and password!</h3>
<p>Changed password to your account in Lost Account Interface on server <a href="{{ constant('BASE_URL') }}"><b>{{ config('lua')['serverName'] }}</b></a></p>
<p>Account name: <b>{{ account.getName() }}</b></p>
<p>New password: <b>{{ newPassword }}</b></p>
<br/>
<p><u>It's automatic e-mail from OTS Lost Account System. Do not reply!</u></p>

View File

@@ -18,35 +18,35 @@
<table width="200" cellspacing="1" cellpadding="0" border="0" align="center" class="myaac-table">
<thead>
<tr>
<td class="white" style="text-align: center;"><strong>Sorcerers</strong></td>
<td class="white" style="text-align: center;"><strong>Druids</strong></td>
<td class="white" style="text-align: center;"><strong>Paladins</strong></td>
<td class="white" style="text-align: center;"><strong>Knights</strong></td>
{% for vocationId in baseVocations %}
<td style="text-align: center;"><strong>{{ config('vocations')[vocationId] }}s</strong></td>
{% endfor %}
</tr>
</thead>
<tr>
<td><img src="images/sorcerer.png" /></td>
<td><img src="images/druid.png" /></td>
<td><img src="images/paladin.png" /></td>
<td><img src="images/knight.png" /></td>
{% for vocationId in baseVocations %}
<td><img src="images/{{ config('vocations')[vocationId]|lower }}.png" width="130" height="190"/></td>
{% endfor %}
</tr>
<tr>
<td style="text-align: center;">{{ vocs[1] }}</td>
<td style="text-align: center;">{{ vocs[2] }}</td>
<td style="text-align: center;">{{ vocs[3] }}</td>
<td style="text-align: center;">{{ vocs[4] }}</td>
{% for vocationId in baseVocations %}
<td style="text-align: center;">{{ vocs[vocationId] }}</td>
{% endfor %}
</tr>
</table>
<div style="text-align: center;">&nbsp;</div>
{% else %}
<table border="0" cellspacing="1" cellpadding="4" width="100%" class="myaac-table">
{% for i in 1..config.vocations_amount %}
{% set i = 0 %}
{% for vocationId in baseVocations %}
<tr>
<td width="25%">{{ config.vocations[i] }}</td>
<td width="75%">{{ vocs[i] }}</td>
<td width="25%">{{ config.vocations[vocationId] }}</td>
<td width="75%">{{ vocs[vocationId] }}</td>
</tr>
{% set i = i + 1 %}
{% endfor %}
</table>
<br/>

View File

@@ -0,0 +1,7 @@
<script>
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
</script>

View File

@@ -2,9 +2,9 @@
<div class="NewsHeadline">
<div class="NewsHeadlineBackground" style="background-image:url({{template_path }}/images/news/newsheadline_background.gif)">
<img src="{{ constant('BASE_URL') }}images/news/icon_{{ icon }}.gif" class="NewsHeadlineIcon" />
<div class="NewsHeadlineDate">{{ date|date(config.news_date_format) }} - </div>
<div class="NewsHeadlineDate">{{ date|date(setting('core.news_date_format')) }} - </div>
<div class="NewsHeadlineText">{{ title }}</div>
{% if author is not empty %}
{% if setting('core.news_author') and author is not empty %}
<div class="NewsHeadlineAuthor"><b>Author: </b><i>{{ author }}</i></div>
{% endif %}
</div>

View File

@@ -1,14 +1,12 @@
<?php
use MyAAC\Models\Gallery;
if(PAGE !== 'news') {
return;
}
$gallery = Gallery::find($config['gallery_image_id_from_database']);
if ($gallery) {
$configGalleryImageThumb = config('gallery_image_thumb');
if (!empty($configGalleryImageThumb)) {
$twig->display('gallery.html.twig', array(
'image' => $gallery->toArray()
'image' => GALLERY_DIR . $configGalleryImageThumb,
));
}

View File

@@ -1,6 +1,6 @@
<div id="GalleryBox" class="Themebox" style="background-image:url({{ template_path }}/images/themeboxes/gallery/gallerybox.gif);">
<a href="?subtopic=gallery&image={{ config['gallery_image_id_from_database'] }}" >
<img id="GalleryContent" class="ThemeboxContent" src="{{ image['thumb'] }}" alt="Screenshot of the Day" />
<a href="{{ getLink('gallery') }}" >
<img id="GalleryContent" class="ThemeboxContent" src="{{ image }}" alt="Screenshot of the Day" />
</a>
<div class="Bottom" style="background-image:url({{ template_path }}/images/general/box-bottom.gif);"></div>
</div>

View File

@@ -15,4 +15,4 @@ network_twitter = "tibia"
background_image = "background-artwork.jpg"
logo_image = "tibia-logo-artwork-top.gif"
gallery_image_id_from_database = 1
gallery_image_thumb = "demon_thumb.gif"

85
tools/css/gallery.css Normal file
View File

@@ -0,0 +1,85 @@
/* Slideshow container */
.slideshow-container {
max-width: 1000px;
position: relative;
margin: auto;
}
/* Hide the images by default */
.mySlides {
display: none;
}
/* Next & previous buttons */
.prev, .next {
cursor: pointer;
position: absolute;
top: 50%;
width: auto;
margin-top: -22px;
padding: 16px;
color: white;
font-weight: bold;
font-size: 18px;
transition: 0.6s ease;
border-radius: 0 3px 3px 0;
user-select: none;
}
/* Position the "next button" to the right */
.next {
right: 0;
border-radius: 3px 0 0 3px;
}
/* On hover, add a black background color with a little bit see-through */
.prev:hover, .next:hover {
background-color: rgba(0,0,0,0.8);
}
/* Caption text */
.text {
color: #f2f2f2;
font-size: 15px;
padding: 8px 12px;
position: absolute;
bottom: 8px;
width: 100%;
text-align: center;
}
/* Number text (1/3 etc) */
.numbertext {
color: #f2f2f2;
font-size: 12px;
padding: 8px 12px;
position: absolute;
top: 0;
}
/* The dots/bullets/indicators */
.dot {
cursor: pointer;
height: 15px;
width: 15px;
margin: 0 2px;
background-color: #bbb;
border-radius: 50%;
display: inline-block;
transition: background-color 0.6s ease;
}
.active, .dot:hover {
background-color: #717171;
}
/* Fading animation */
.fade-effect {
animation-name: fade-effect;
animation-duration: 1.5s;
}
@keyframes fade-effect {
from {opacity: .4}
to {opacity: 1}
}

View File

@@ -1,507 +0,0 @@
/***
Spectrum Colorpicker v1.8.0
https://github.com/bgrins/spectrum
Author: Brian Grinstead
License: MIT
***/
.sp-container {
position:absolute;
top:0;
left:0;
display:inline-block;
*display: inline;
*zoom: 1;
/* https://github.com/bgrins/spectrum/issues/40 */
z-index: 9999994;
overflow: hidden;
}
.sp-container.sp-flat {
position: relative;
}
/* Fix for * { box-sizing: border-box; } */
.sp-container,
.sp-container * {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */
.sp-top {
position:relative;
width: 100%;
display:inline-block;
}
.sp-top-inner {
position:absolute;
top:0;
left:0;
bottom:0;
right:0;
}
.sp-color {
position: absolute;
top:0;
left:0;
bottom:0;
right:20%;
}
.sp-hue {
position: absolute;
top:0;
right:0;
bottom:0;
left:84%;
height: 100%;
}
.sp-clear-enabled .sp-hue {
top:33px;
height: 77.5%;
}
.sp-fill {
padding-top: 80%;
}
.sp-sat, .sp-val {
position: absolute;
top:0;
left:0;
right:0;
bottom:0;
}
.sp-alpha-enabled .sp-top {
margin-bottom: 18px;
}
.sp-alpha-enabled .sp-alpha {
display: block;
}
.sp-alpha-handle {
position:absolute;
top:-4px;
bottom: -4px;
width: 6px;
left: 50%;
cursor: pointer;
border: 1px solid black;
background: white;
opacity: .8;
}
.sp-alpha {
display: none;
position: absolute;
bottom: -14px;
right: 0;
left: 0;
height: 8px;
}
.sp-alpha-inner {
border: solid 1px #333;
}
.sp-clear {
display: none;
}
.sp-clear.sp-clear-display {
background-position: center;
}
.sp-clear-enabled .sp-clear {
display: block;
position:absolute;
top:0px;
right:0;
bottom:0;
left:84%;
height: 28px;
}
/* Don't allow text selection */
.sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button {
-webkit-user-select:none;
-moz-user-select: -moz-none;
-o-user-select:none;
user-select: none;
}
.sp-container.sp-input-disabled .sp-input-container {
display: none;
}
.sp-container.sp-buttons-disabled .sp-button-container {
display: none;
}
.sp-container.sp-palette-buttons-disabled .sp-palette-button-container {
display: none;
}
.sp-palette-only .sp-picker-container {
display: none;
}
.sp-palette-disabled .sp-palette-container {
display: none;
}
.sp-initial-disabled .sp-initial {
display: none;
}
/* Gradients for hue, saturation and value instead of images. Not pretty... but it works */
.sp-sat {
background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0)));
background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0));
background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0));
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)";
filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81');
}
.sp-val {
background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0)));
background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0));
background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0));
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)";
filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000');
}
.sp-hue {
background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000));
background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
}
/* IE filters do not support multiple color stops.
Generate 6 divs, line them up, and do two color gradients for each.
Yes, really.
*/
.sp-1 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00');
}
.sp-2 {
height:16%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00');
}
.sp-3 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff');
}
.sp-4 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff');
}
.sp-5 {
height:16%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff');
}
.sp-6 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000');
}
.sp-hidden {
display: none !important;
}
/* Clearfix hack */
.sp-cf:before, .sp-cf:after { content: ""; display: table; }
.sp-cf:after { clear: both; }
.sp-cf { *zoom: 1; }
/* Mobile devices, make hue slider bigger so it is easier to slide */
@media (max-device-width: 480px) {
.sp-color { right: 40%; }
.sp-hue { left: 63%; }
.sp-fill { padding-top: 60%; }
}
.sp-dragger {
border-radius: 5px;
height: 5px;
width: 5px;
border: 1px solid #fff;
background: #000;
cursor: pointer;
position:absolute;
top:0;
left: 0;
}
.sp-slider {
position: absolute;
top:0;
cursor:pointer;
height: 3px;
left: -1px;
right: -1px;
border: 1px solid #000;
background: white;
opacity: .8;
}
/*
Theme authors:
Here are the basic themeable display options (colors, fonts, global widths).
See http://bgrins.github.io/spectrum/themes/ for instructions.
*/
.sp-container {
border-radius: 0;
background-color: #ECECEC;
border: solid 1px #f0c49B;
padding: 0;
}
.sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear {
font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.sp-top {
margin-bottom: 3px;
}
.sp-color, .sp-hue, .sp-clear {
border: solid 1px #666;
}
/* Input */
.sp-input-container {
float:right;
width: 100px;
margin-bottom: 4px;
}
.sp-initial-disabled .sp-input-container {
width: 100%;
}
.sp-input {
font-size: 12px !important;
border: 1px inset;
padding: 4px 5px;
margin: 0;
width: 100%;
background:transparent;
border-radius: 3px;
color: #222;
}
.sp-input:focus {
border: 1px solid orange;
}
.sp-input.sp-validation-error {
border: 1px solid red;
background: #fdd;
}
.sp-picker-container , .sp-palette-container {
float:left;
position: relative;
padding: 10px;
padding-bottom: 300px;
margin-bottom: -290px;
}
.sp-picker-container {
width: 172px;
border-left: solid 1px #fff;
}
/* Palettes */
.sp-palette-container {
border-right: solid 1px #ccc;
}
.sp-palette-only .sp-palette-container {
border: 0;
}
.sp-palette .sp-thumb-el {
display: block;
position:relative;
float:left;
width: 24px;
height: 15px;
margin: 3px;
cursor: pointer;
border:solid 2px transparent;
}
.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active {
border-color: orange;
}
.sp-thumb-el {
position:relative;
}
/* Initial */
.sp-initial {
float: left;
border: solid 1px #333;
}
.sp-initial span {
width: 30px;
height: 25px;
border:none;
display:block;
float:left;
margin:0;
}
.sp-initial .sp-clear-display {
background-position: center;
}
/* Buttons */
.sp-palette-button-container,
.sp-button-container {
float: right;
}
/* Replacer (the little preview div that shows up instead of the <input>) */
.sp-replacer {
margin:0;
overflow:hidden;
cursor:pointer;
padding: 4px;
display:inline-block;
*zoom: 1;
*display: inline;
border: solid 1px #91765d;
background: #eee;
color: #333;
vertical-align: middle;
}
.sp-replacer:hover, .sp-replacer.sp-active {
border-color: #F0C49B;
color: #111;
}
.sp-replacer.sp-disabled {
cursor:default;
border-color: silver;
color: silver;
}
.sp-dd {
padding: 2px 0;
height: 16px;
line-height: 16px;
float:left;
font-size:10px;
}
.sp-preview {
position:relative;
width:25px;
height: 20px;
border: solid 1px #222;
margin-right: 5px;
float:left;
z-index: 0;
}
.sp-palette {
*width: 220px;
max-width: 220px;
}
.sp-palette .sp-thumb-el {
width:16px;
height: 16px;
margin:2px 1px;
border: solid 1px #d0d0d0;
}
.sp-container {
padding-bottom:0;
}
/* Buttons: http://hellohappy.org/css3-buttons/ */
.sp-container button {
background-color: #eeeeee;
background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc);
background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
background-image: -ms-linear-gradient(top, #eeeeee, #cccccc);
background-image: -o-linear-gradient(top, #eeeeee, #cccccc);
background-image: linear-gradient(to bottom, #eeeeee, #cccccc);
border: 1px solid #ccc;
border-bottom: 1px solid #bbb;
border-radius: 3px;
color: #333;
font-size: 14px;
line-height: 1;
padding: 5px 4px;
text-align: center;
text-shadow: 0 1px 0 #eee;
vertical-align: middle;
}
.sp-container button:hover {
background-color: #dddddd;
background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -o-linear-gradient(top, #dddddd, #bbbbbb);
background-image: linear-gradient(to bottom, #dddddd, #bbbbbb);
border: 1px solid #bbb;
border-bottom: 1px solid #999;
cursor: pointer;
text-shadow: 0 1px 0 #ddd;
}
.sp-container button:active {
border: 1px solid #aaa;
border-bottom: 1px solid #888;
-webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
}
.sp-cancel {
font-size: 11px;
color: #d93f3f !important;
margin:0;
padding:2px;
margin-right: 5px;
vertical-align: middle;
text-decoration:none;
}
.sp-cancel:hover {
color: #d93f3f !important;
text-decoration: underline;
}
.sp-palette span:hover, .sp-palette span.sp-thumb-active {
border-color: #000;
}
.sp-preview, .sp-alpha, .sp-thumb-el {
position:relative;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
}
.sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner {
display:block;
position:absolute;
top:0;left:0;bottom:0;right:0;
}
.sp-palette .sp-thumb-inner {
background-position: 50% 50%;
background-repeat: no-repeat;
}
.sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=);
}
.sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=);
}
.sp-clear-display {
background-repeat:no-repeat;
background-position: center;
background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==);
}

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