567 lines
16 KiB
PHP
567 lines
16 KiB
PHP
<?php
|
|
|
|
// admin.php :: primary administration script.
|
|
|
|
namespace Admin;
|
|
|
|
use Router;
|
|
use SQLite3Result;
|
|
|
|
function register_routes(Router $r): Router
|
|
{
|
|
if (user() !== false && user()->authlevel === 1) {
|
|
$r->get('/admin', 'Admin\donothing');
|
|
|
|
$r->form('/admin/main', 'Admin\primary');
|
|
|
|
$r->get('/admin/items', 'Admin\items');
|
|
$r->form('/admin/items/:id', 'Admin\edit_item');
|
|
|
|
$r->get('/admin/drops', 'Admin\drops');
|
|
$r->form('/admin/drops/:id', 'Admin\edit_drop');
|
|
|
|
$r->get('/admin/towns', 'Admin\towns');
|
|
$r->form('/admin/towns/:id', 'Admin\edit_town');
|
|
|
|
$r->get('/admin/monsters', 'Admin\monsters');
|
|
$r->form('/admin/monsters/:id', 'Admin\edit_monster');
|
|
|
|
$r->get('/admin/levels', 'Admin\levels');
|
|
$r->post('/admin/levels', 'Admin\edit_level');
|
|
|
|
$r->get('/admin/spells', 'Admin\spells');
|
|
$r->form('/admin/spells/:id', 'Admin\edit_spell');
|
|
|
|
$r->get('/admin/users', 'Admin\users');
|
|
$r->form('/admin/users/:id', 'Admin\edit_user');
|
|
|
|
$r->form('/admin/news', 'Admin\add_news');
|
|
}
|
|
return $r;
|
|
}
|
|
|
|
/**
|
|
* Home page for the admin panel.
|
|
*/
|
|
function donothing(): string
|
|
{
|
|
page_title('Admin');
|
|
|
|
return <<<HTML
|
|
Welcome to the administration section. Use the links on the left bar to control and edit various
|
|
elements of the game.
|
|
<br><br>
|
|
Please note that the control panel has been created mostly as a shortcut for certain individual settings. It is
|
|
meant for use primarily with editing one thing at a time. If you need to completely replace an entire table
|
|
(say, to replace all stock monsters with your own new ones), it is suggested that you use a more in-depth
|
|
database tool such as <a href="https://sqlitebrowser.org/" target="_new">SQLite Browser</a>.
|
|
<br><br>
|
|
Also, you should be aware that certain portions of the DK code are dependent on the formatting of certain
|
|
database results (for example, the special attributes on item drops). While I have attempted to point these out
|
|
throughout the admin script, you should definitely pay attention and be careful when editing some fields,
|
|
because mistakes in the database content may result in script errors or your game breaking completely.
|
|
HTML;
|
|
}
|
|
|
|
/**
|
|
* Main settings that get written to .env
|
|
*/
|
|
function primary(): string
|
|
{
|
|
if (is_post()) {
|
|
$form = validate($_POST, [
|
|
'gamename' => ['alphanum-spaces'],
|
|
'gamesize' => ['int', 'min:5'],
|
|
'class1name' => ['alpha-spaces'],
|
|
'class2name' => ['alpha-spaces'],
|
|
'class3name' => ['alpha-spaces'],
|
|
'gameopen' => ['bool'],
|
|
'verifyemail' => ['bool'],
|
|
'shownews' => ['bool'],
|
|
'showonline' => ['bool'],
|
|
'showbabble' => ['bool']
|
|
]);
|
|
|
|
if ($form['valid']) {
|
|
$form = $form['data'];
|
|
if (($form['gamesize'] % 5) != 0) exit('Map size must be divisible by five.');
|
|
|
|
// @todo
|
|
// write changes to .env
|
|
|
|
$page = 'Main settings updated.';
|
|
} else {
|
|
$error_list = ul_from_validate_errors($form['errors']);
|
|
$page = <<<HTML
|
|
<b>Errors:</b><br>
|
|
<div style="color: red;">{$error_list}</div><br>
|
|
Please go back and try again.
|
|
HTML;
|
|
}
|
|
} else {
|
|
$page = render('admin/main_settings');
|
|
}
|
|
|
|
page_title('Admin: Main Settings');
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* Show the full list of items that can be edited.
|
|
*/
|
|
function items(): string
|
|
{
|
|
$items = db()->query('SELECT * FROM items ORDER BY id;');
|
|
$page = "<h2>Edit Items</h2>Click an item's name or ID to edit it.<br><br>\n";
|
|
$page .= build_bulk_table($items, 'name', '/admin/items');
|
|
|
|
page_title('Admin: Items');
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* Shows the form for editing an item via GET, processes edits via POST
|
|
*/
|
|
function edit_item(int $id): string
|
|
{
|
|
$item = get_item($id);
|
|
|
|
if (is_post()) {
|
|
$page = handle_edit_form($id, 'items', validate($_POST, [
|
|
'name' => [],
|
|
'type' => ['int', 'in:1,2,3'],
|
|
'buycost' => ['int', 'min:0'],
|
|
'attribute' => ['int', 'min:0'],
|
|
'special' => ['default:X']
|
|
]));
|
|
} else {
|
|
$page = render('admin/edit_item', ['item' => $item]);
|
|
}
|
|
|
|
page_title('Admin: Editing '.$item['name']);
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* Show the full list of drops that can be edited
|
|
*/
|
|
function drops()
|
|
{
|
|
$drops = db()->query('SELECT * FROM drops ORDER BY id;');
|
|
$page = "<h2>Edit Drops</h2>Click an item's name to edit it.<br><br>\n";
|
|
$page .= build_bulk_table($drops, 'name', '/admin/drops');
|
|
|
|
page_title('Admin: Drops');
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* Show the form to edit drops via GET, process those edits via POST
|
|
*/
|
|
function edit_drop(int $id): string
|
|
{
|
|
$drop = get_drop($id);
|
|
|
|
if (is_post()) {
|
|
$page = handle_edit_form($id, 'drops', validate($_POST, [
|
|
'name' => [],
|
|
'mlevel' => ['int', 'min:1'],
|
|
'attribute1' => [],
|
|
'attribute2' => ['default:X'],
|
|
]));
|
|
} else {
|
|
$page = render('admin/edit_drop', ['drop' => $drop]);
|
|
}
|
|
|
|
page_title('Admin: Editing '.$drop['name']);
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* Generate the list of towns that can be edited.
|
|
*/
|
|
function towns(): string
|
|
{
|
|
$towns = db()->query('SELECT * FROM towns ORDER BY id;');
|
|
$page = "<h2>Edit Towns</h2>Click an town's name or ID to edit it.<br><br>\n";
|
|
$page .= build_bulk_table($towns, 'name', '/admin/towns');
|
|
|
|
page_title('Admin: Towns');
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* Save any changes to the town made.
|
|
*/
|
|
function edit_town(int $id): string
|
|
{
|
|
$town = get_town_by_id($id);
|
|
|
|
if (is_post()) {
|
|
$page = handle_edit_form($id, 'towns', validate($_POST, [
|
|
'name' => [],
|
|
'latitude' => ['int', 'min:0', 'max:'.env('game_size')],
|
|
'longitude' => ['int', 'min:0', 'max:'.env('game_size')],
|
|
'innprice' => ['int', 'min:0'],
|
|
'mapprice' => ['int', 'min:0'],
|
|
'travelpoints' => ['int', 'min:0'],
|
|
'itemslist' => ['optional']
|
|
]));
|
|
} else {
|
|
$page = render('admin/edit_town', ['town' => $town]);
|
|
}
|
|
|
|
page_title('Admin: Editing '.$town['name']);
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* List the monsters available to edit.
|
|
*/
|
|
function monsters()
|
|
{
|
|
$max_level = db()->query('SELECT level FROM monsters ORDER BY level DESC LIMIT 1;')->fetchArray(SQLITE3_ASSOC)['level'];
|
|
$monsters = db()->query('SELECT * FROM monsters ORDER BY id;');
|
|
|
|
$page = "<h2>Edit Monsters</h2>";
|
|
|
|
if ((env('game_size') / 5) !== $max_level) {
|
|
$page .= "<span class=\"highlight\">Note:</span> Your highest monster level does not match with your entered map size. Highest monster level should be ".(env('game_size') / 5).", yours is $max_level. Please fix this before opening the game to the public.<br>";
|
|
} else {
|
|
$page .= "Monster level and map size match. No further actions are required for map compatibility.<br>";
|
|
}
|
|
|
|
$page .= "Click an monster's name or ID to edit it.<br><br>\n";
|
|
$page .= build_bulk_table($monsters, 'name', '/admin/monsters');
|
|
|
|
page_title('Admin: Monsters');
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* Handle the actual editing of the monster.
|
|
*/
|
|
function edit_monster(int $id): string
|
|
{
|
|
$monster = get_monster($id);
|
|
|
|
if (is_post()) {
|
|
$page = handle_edit_form($id, 'monsters', validate($_POST, [
|
|
'name' => [],
|
|
'maxhp' => ['int', 'min:1'],
|
|
'maxdam' => ['int', 'min:0'],
|
|
'armor' => ['int', 'min:0'],
|
|
'level' => ['int', 'min:1'],
|
|
'maxexp' => ['int', 'min:0'],
|
|
'maxgold' => ['int', 'min:0'],
|
|
'immune' => ['in:0,1,2']
|
|
]));
|
|
} else {
|
|
$page = render('admin/edit_monster', ['monster' => $monster]);
|
|
}
|
|
|
|
page_title('Admin: Editing '.$monster['name']);
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* List all spells available to edit.
|
|
*/
|
|
function spells(): string
|
|
{
|
|
$page = "<h2>Edit Spells</h2>Click an spell's name to edit it.<br><br>\n";
|
|
|
|
$spells = db()->query('SELECT * FROM spells ORDER BY id;');
|
|
$page .= build_bulk_table($spells, 'name', '/admin/spells');
|
|
|
|
page_title('Admin: Spells');
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* Handle the editing of an individual spell.
|
|
*/
|
|
function edit_spell(int $id): string
|
|
{
|
|
$spell = get_spell($id);
|
|
|
|
if (is_post()) {
|
|
$page = handle_edit_form($id, 'spells', validate($_POST, [
|
|
'name' => [],
|
|
'mp' => ['int', 'min:0'],
|
|
'attribute' => ['int', 'min:0'],
|
|
'type' => ['in:1,2,3,4,5']
|
|
]));
|
|
} else {
|
|
$page = render('admin/edit_spell', ['spell' => $spell]);
|
|
}
|
|
|
|
page_title('Admin: Editing '.$spell['name']);
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* List all editable levels.
|
|
*/
|
|
function levels(): string
|
|
{
|
|
$max_level = db()->query('SELECT id FROM levels ORDER BY id DESC LIMIT 1;')->fetchArray(SQLITE3_ASSOC)['id'];
|
|
|
|
$page = <<<HTML
|
|
<h2>Edit Levels</h2>
|
|
Select a level number from the dropdown box to edit it.<br><br>
|
|
<form action="/admin/levels" method="post" hx-post="/admin/levels" hx-target="#main">
|
|
<select name="level">
|
|
HTML;
|
|
for ($i = 2; $i < $max_level; $i++) $page .= "<option value=\"$i\">$i</option>\n";
|
|
$page .= <<<HTML
|
|
</select>
|
|
<button type="submit">Edit</button>
|
|
</form>
|
|
HTML;
|
|
|
|
page_title('Admin: Levels');
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* Handle the editing of a level.
|
|
*/
|
|
function edit_level()
|
|
{
|
|
if (!isset($_POST['level'])) return 'No level to edit.';
|
|
$id = $_POST['level'];
|
|
$level = db()->query('SELECT * FROM levels WHERE id=? LIMIT 1;', [$id])->fetchArray(SQLITE3_ASSOC);
|
|
|
|
if (is_post() && isset($_POST['save'])) {
|
|
unset($_POST['save']);
|
|
unset($_POST['level']);
|
|
|
|
$page = handle_edit_form($id, 'levels', validate($_POST, [
|
|
'1_exp' => ['int', 'min:0'],
|
|
'1_hp' => ['int', 'min:0'],
|
|
'1_mp' => ['int', 'min:0'],
|
|
'1_tp' => ['int', 'min:0'],
|
|
'1_strength' => ['int', 'min:0'],
|
|
'1_dexterity' => ['int', 'min:0'],
|
|
'1_spells' => ['int', 'min:0'],
|
|
'2_exp' => ['int', 'min:0'],
|
|
'2_hp' => ['int', 'min:0'],
|
|
'2_mp' => ['int', 'min:0'],
|
|
'2_tp' => ['int', 'min:0'],
|
|
'2_strength' => ['int', 'min:0'],
|
|
'2_dexterity' => ['int', 'min:0'],
|
|
'2_spells' => ['int', 'min:0'],
|
|
'3_exp' => ['int', 'min:0'],
|
|
'3_hp' => ['int', 'min:0'],
|
|
'3_mp' => ['int', 'min:0'],
|
|
'3_tp' => ['int', 'min:0'],
|
|
'3_strength' => ['int', 'min:0'],
|
|
'3_dexterity' => ['int', 'min:0'],
|
|
'3_spells' => ['int', 'min:0']
|
|
]), 'Level <b>'.$id.'</b> updated.');
|
|
} else {
|
|
$page = render('admin/edit_level', ['level' => $level]);
|
|
}
|
|
|
|
page_title('Admin: Editing Level '.$id);
|
|
return $page;
|
|
}
|
|
|
|
function users()
|
|
{
|
|
$users = db()->query('SELECT * FROM users ORDER BY id;');
|
|
$page = "<h2>Edit Users</h2>Click a username or ID to edit the account.<br><br><div class=\"table-wrapper\">";
|
|
$page .= build_bulk_table($users, 'username', '/admin/users');
|
|
|
|
page_title('Admin: Users');
|
|
return $page . '</div>';
|
|
}
|
|
|
|
/**
|
|
* Handle editing a user.
|
|
*/
|
|
function edit_user(int $id): string
|
|
{
|
|
$user = db()->query('SELECT * FROM users WHERE id = ? LIMIT 1;', [$id])->fetchArray(SQLITE3_ASSOC);
|
|
|
|
if (is_post()) {
|
|
$form = validate($_POST, [
|
|
'username' => ['length:3-18', 'alpha-spaces', 'unique:users,username'],
|
|
'verify' => [],
|
|
'authlevel' => ['int'],
|
|
'email' => ['email', 'unique:users,email'],
|
|
'charclass' => ['in:1,2,3'],
|
|
'latitude' => ['int', 'min:0', 'max:'.env('game_size')],
|
|
'longitude' => ['int', 'min:0', 'max:'.env('game_size')],
|
|
'currentaction' => [],
|
|
'currentfight' => ['int'],
|
|
'currentmonster' => ['int'],
|
|
'currentmonsterhp' => ['int'],
|
|
'currentmonstersleep' => ['int'],
|
|
'currentmonsterimmune' => ['int'],
|
|
'currentuberdamage' => ['int'],
|
|
'currentuberdefense' => ['int'],
|
|
'currenthp' => ['int', 'min:0'],
|
|
'currentmp' => ['int', 'min:0'],
|
|
'currenttp' => ['int', 'min:0'],
|
|
'maxhp' => ['int', 'min:1'],
|
|
'maxmp' => ['int', 'min:1'],
|
|
'maxtp' => ['int', 'min:1'],
|
|
'level' => ['int', 'min:1'],
|
|
'gold' => ['int', 'min:0'],
|
|
'experience' => ['int', 'min:0'],
|
|
'goldbonus' => ['int'],
|
|
'expbonus' => ['int'],
|
|
'strength' => ['int'],
|
|
'dexterity' => ['int'],
|
|
'attackpower' => ['int'],
|
|
'defensepower' => ['int'],
|
|
'weaponid' => ['int'],
|
|
'armorid' => ['int'],
|
|
'shieldid' => ['int'],
|
|
'slot1id' => ['int'],
|
|
'slot2id' => ['int'],
|
|
'slot3id' => ['int'],
|
|
'weaponname' => ['default:None'],
|
|
'armorname' => ['default:None'],
|
|
'shieldname' => ['default:None'],
|
|
'slot1name' => ['default:None'],
|
|
'slot2name' => ['default:None'],
|
|
'slot3name' => ['default:None'],
|
|
'dropcode' => ['int', 'min:0', 'default:0'],
|
|
'spells' => ['optional'],
|
|
'towns' => ['optional']
|
|
]);
|
|
|
|
if ($form['valid']) {
|
|
save_data_row('users', $form['data'], $id);
|
|
$page = 'User <b>'.$user['username'].'</b> updated.';
|
|
} else {
|
|
$error_list = ul_from_validate_errors($form['errors']);
|
|
$page = <<<HTML
|
|
<b>Errors:</b><br>
|
|
<div style="color: red;">{$error_list}</div><br>
|
|
Please go back and try again.
|
|
HTML;
|
|
}
|
|
} else {
|
|
$page = render('admin/edit_user', ['user' => $user]);
|
|
}
|
|
|
|
page_title('Admin: Editing '.$user['username']);
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* Handling adding news posts.
|
|
*/
|
|
function add_news()
|
|
{
|
|
if (is_post()) {
|
|
$c = trim($_POST['content'] ?? '');
|
|
|
|
$errors = [];
|
|
if (empty($c)) $errors[] = "Content is required.";
|
|
|
|
if (count($errors) === 0) {
|
|
db()->query('INSERT INTO news (author, content) VALUES (?, ?);', [user()->username, $c]);
|
|
$page = 'News post added.';
|
|
} else {
|
|
$error_list = implode('<br>', $errors);
|
|
$page = "<b>Errors:</b><br><div style=\"color:red;\">$error_list</div><br>Please go back and try again.";
|
|
}
|
|
} else {
|
|
$page = <<<HTML
|
|
<h2>Add a News Post</h2>
|
|
<form action="/admin/news" method="post" hx-post="/admin/news" hx-target="#main">
|
|
Type your post below and then click Submit to add it.<br>
|
|
<textarea name="content" rows="5" cols="50"></textarea><br>
|
|
<button type="submit">Submit</button>
|
|
<button type="reset">Clear</button>
|
|
</form>
|
|
HTML;
|
|
}
|
|
|
|
page_title('Admin: Add News');
|
|
return $page;
|
|
}
|
|
|
|
/**
|
|
* Build an HTML table containing all columns and rows of a given data structure. Takes a SQLiteResult3 of a SELECT
|
|
* query.
|
|
*/
|
|
function build_bulk_table(SQLite3Result $query_data, string $edit_column, string $edit_link): string
|
|
{
|
|
$data = [];
|
|
$data_count = 0;
|
|
while ($row = $query_data->fetchArray(SQLITE3_ASSOC)) $data[$data_count++] = $row;
|
|
if ($data_count === 0) return 'No data.';
|
|
|
|
$columns = array_diff(array_keys($data[0]), ['password']);
|
|
|
|
$html_parts = [
|
|
'<table><colgroup>',
|
|
str_repeat('<col>', count($columns)),
|
|
'</colgroup><thead><tr>'
|
|
];
|
|
|
|
foreach ($columns as $column) {
|
|
$html_parts[] = '<th>' .
|
|
make_safe($column === 'id' ? 'ID' : ucfirst($column)) .
|
|
'</th>';
|
|
}
|
|
$html_parts[] = '</tr></thead><tbody>';
|
|
|
|
$is_edit_column = array_flip(['id', $edit_column]);
|
|
|
|
foreach ($data as $row) {
|
|
$html_parts[] = '<tr>';
|
|
foreach ($columns as $column) {
|
|
$name = make_safe($row[$column]);
|
|
$html_parts[] = isset($is_edit_column[$column])
|
|
? "<td><a href=\"{$edit_link}/{$row['id']}\" hx-get=\"{$edit_link}/{$row['id']}\" hx-target=\"#main\">{$name}</a></td>"
|
|
: "<td>{$name}</td>";
|
|
}
|
|
$html_parts[] = '</tr>';
|
|
}
|
|
|
|
$html_parts[] = '</tbody></table>';
|
|
return implode('', $html_parts);
|
|
}
|
|
|
|
/**
|
|
* Save a row of data to it's table from the data supplied.
|
|
*/
|
|
function save_data_row(string $table, array $data, int $id): SQLite3Result|false
|
|
{
|
|
$data = array_filter($data, fn($value) => $value !== null && $value !== '');
|
|
if (empty($data)) return false;
|
|
|
|
$fields = implode(',', array_map(fn($key) => "`$key`=?", array_keys($data)));
|
|
$values = array_values($data);
|
|
$values[] = $id;
|
|
|
|
return db()->query("UPDATE $table SET $fields WHERE id=?", $values);
|
|
}
|
|
|
|
/**
|
|
* Handle the result of a generic edit form.
|
|
*/
|
|
function handle_edit_form(int $id, string $table, array $form, string $updated_message = ''): string
|
|
{
|
|
if ($form['valid']) {
|
|
save_data_row($table, $form['data'], $id);
|
|
$page = $updated_message ?: '<b>'.$form['data']['name'].'</b> updated.';
|
|
} else {
|
|
$error_list = ul_from_validate_errors($form['errors']);
|
|
$page = <<<HTML
|
|
<b>Errors:</b><br>
|
|
<div style="color: red;">{$error_list}</div><br>
|
|
Please go back and try again.
|
|
HTML;
|
|
}
|
|
|
|
return $page;
|
|
}
|