Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d56d53d
Profile theming: dark mode toggle, shared CSS, character colour helper
TheSCREWEDSoftware Jun 23, 2026
ba1c597
Characters menu: clickable rows, reset-order, CSRF nonce, escaping
TheSCREWEDSoftware Jun 23, 2026
167a8cf
Mail Return: redesigned layout, subject escaping, empty-state reset
TheSCREWEDSoftware Jun 23, 2026
6f4f17b
Item Restoration: redesigned page, ownership checks (IDOR), input val…
TheSCREWEDSoftware Jun 23, 2026
3fb9994
Misc menus: Unstuck nonce/data, Resurrection Scroll, RAF nonce + icon…
TheSCREWEDSoftware Jun 23, 2026
c4263c7
Security tab: password change, 2FA (website/in-game/admin/email), con…
TheSCREWEDSoftware Jun 23, 2026
80e07dc
Admin: Realm Settings revamp (modules panel) + CSRF nonces on all set…
TheSCREWEDSoftware Jun 23, 2026
1e4dffe
3 Columns
TheSCREWEDSoftware Jun 24, 2026
1833636
Badges
TheSCREWEDSoftware Jun 24, 2026
d86f64f
Show Mute / Bans in Characters and Badge
TheSCREWEDSoftware Jun 24, 2026
b59964a
Notifiaction for Banned / Mute in Profile
TheSCREWEDSoftware Jun 24, 2026
e07f7de
Ensure perma banned still appear.
TheSCREWEDSoftware Jun 24, 2026
36b5b4f
Rabbit Reviews for this PR
TheSCREWEDSoftware Jun 24, 2026
c1cc262
RabbitReviews from Previouls PR that still valid here
TheSCREWEDSoftware Jun 24, 2026
e1f9344
Rabbit's Review
TheSCREWEDSoftware Jun 24, 2026
a2ffb9f
PDUMP
TheSCREWEDSoftware Jun 24, 2026
7d2c437
Format the single nad all pdump file names
TheSCREWEDSoftware Jun 25, 2026
f581aba
Toggle On and off showing the ban / mute info and choose what to appear.
TheSCREWEDSoftware Jun 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<h2> <?= __('AzerothCore Settings', Opts::I()->page_alias) ?></h2>
<p>Configure database connection for Eluna script that need use of the CMS.</p>
<form name="form-acore-eluna-settings" method="post" action="">
<?php wp_nonce_field('acore_eluna_settings_save', 'acore_eluna_settings_nonce'); ?>
<div class="row">
<div class="col-sm-6">
<div class="card p-0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<h5>Give rewards</h5>
<hr>
<form name="pvp-rewards" method="post" id="pvp-rewards" class="initial-form hide-if-no-js">
<?php wp_nonce_field('acore_pvp_rewards_save', 'acore_pvp_rewards_nonce'); ?>
<input type="hidden" name="page" value="<?= ACORE_SLUG . '-pvp-rewards'; ?>" />
<table class="form-table table table-borderless" role="presentation">
<tbody>
Expand Down
614 changes: 376 additions & 238 deletions src/acore-wp-plugin/src/Components/AdminPanel/Pages/RealmSettings.php

Large diffs are not rendered by default.

652 changes: 582 additions & 70 deletions src/acore-wp-plugin/src/Components/AdminPanel/Pages/Tools.php

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public function loadSettings() {
// If they did, this hidden field will be set to 'Y'

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
check_admin_referer('acore_realm_settings_save', 'acore_realm_settings_nonce');

foreach (Opts::I()->getConfs() as $key => $value) {
if (isset($_POST[$key])) {
$this->storeConf($key, $_POST[$key]);
Expand Down Expand Up @@ -84,6 +86,8 @@ public function loadElunaSettings() {
// If they did, this hidden field will be set to 'Y'

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
check_admin_referer('acore_eluna_settings_save', 'acore_eluna_settings_nonce');

foreach (Opts::I()->getConfs() as $key => $value) {
if (isset($_POST[$key])) {
$this->storeConf($key, $_POST[$key]);
Expand Down Expand Up @@ -131,6 +135,7 @@ public function loadPvpRewards() {
//! DEV NOTE: Put the rest of stuff within try { ... } check to handle every exception properly
try {
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
check_admin_referer('acore_pvp_rewards_save', 'acore_pvp_rewards_nonce');
global $wpdb;
$tableResult = $wpdb->query("CREATE TEMPORARY TABLE temp_pvp_rewards (
`account` VARCHAR(255) COLLATE utf8_unicode_ci,
Expand Down Expand Up @@ -385,6 +390,13 @@ public function loadTools() {
//! DEV NOTE: Put the rest of stuff within try { ... } check to handle every exception properly
try {
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
check_admin_referer('acore_tools_save', 'acore_tools_nonce');

// If the thresholds sentinel is present but no rows were submitted, clear them
if (isset($_POST['acore_name_unlock_thresholds_present']) && !isset($_POST['acore_name_unlock_thresholds'])) {
$this->storeConf('acore_name_unlock_thresholds', []);
}

foreach (Opts::I()->getConfs() as $key => $value) {
if (isset($_POST[$key])) {
if ($key == 'acore_name_unlock_allowed_banned_names_table') {
Expand Down
187 changes: 187 additions & 0 deletions src/acore-wp-plugin/src/Components/CharactersMenu/CharacterDumpApi.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
<?php

namespace ACore\Components\CharactersMenu;

use ACore\Manager\ACoreServices;
use ACore\Manager\Opts;

add_action('rest_api_init', function () {
register_rest_route(ACORE_SLUG . '/v1', 'pdump/(?P<guid>\d+)', [
'methods' => 'GET',
'callback' => __NAMESPACE__ . '\handlePdump',
'permission_callback' => '__return_true',
]);
register_rest_route(ACORE_SLUG . '/v1', 'pdump/all', [
'methods' => 'POST',
'callback' => __NAMESPACE__ . '\handlePdumpAll',
'permission_callback' => '__return_true',
]);
});

function handlePdump(\WP_REST_Request $request): void
{
$guid = (int) $request->get_param('guid');
$accId = ACoreServices::I()->getAcoreAccountId();

if (Opts::I()->acore_pdump_enabled != '1') {
wp_send_json_error(['message' => 'PDUMP export is not enabled on this server.'], 403);
exit;
}

if (!$accId || $guid < 1) {
wp_send_json_error(['message' => 'Forbidden.'], 403);
exit;
}

$conn = ACoreServices::I()->getCharacterEm()->getConnection();
$row = $conn->executeQuery(
"SELECT `name` FROM `characters`
WHERE `guid` = ? AND `account` = ? AND `deleteDate` IS NULL
LIMIT 1",
[$guid, $accId]
)->fetchAssociative();

if (!$row) {
wp_send_json_error(['message' => 'Character not found.'], 403);
exit;
}

$charName = $row['name'];

$phpWarnings = [];
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use (&$phpWarnings): bool {
$phpWarnings[] = "[{$errno}] {$errstr} in {$errfile}:{$errline}";
return true;
});

ob_start();
$dump = null;
try {
$writer = new CharacterDumpWriter($conn);
$dump = $writer->getDump($guid);
} catch (\Throwable $e) {
$phpWarnings[] = get_class($e) . ': ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine();
}
ob_end_clean();
restore_error_handler();

if (!empty($phpWarnings)) {
wp_send_json_error([
'message' => 'An internal error occurred while generating the dump.',
'detail' => implode("\n", $phpWarnings),
], 500);
exit;
}

if ($dump === null) {
wp_send_json_error(['message' => 'Character is deleted and cannot be exported.'], 400);
exit;
}

$filename = strtoupper($charName) . '_' . date('Ymd_His') . '.dump';

if (ob_get_level()) {
ob_end_clean();
}

header('Content-Type: text/plain; charset=utf-8');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: ' . strlen($dump));
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
echo $dump;
exit;
}

function handlePdumpAll(\WP_REST_Request $request): void
{
$accId = ACoreServices::I()->getAcoreAccountId();

if (Opts::I()->acore_pdump_enabled != '1') {
wp_send_json_error(['message' => 'PDUMP export is not enabled on this server.'], 403);
exit;
}

if (!$accId) {
wp_send_json_error(['message' => 'Forbidden.'], 403);
exit;
}

$body = $request->get_json_params();
$characters = $body['characters'] ?? [];
if (empty($characters) || !is_array($characters)) {
wp_send_json_error(['message' => 'No characters provided.'], 400);
exit;
}

$conn = ACoreServices::I()->getCharacterEm()->getConnection();
$now = date('d_m_Y_H_i_s');

$phpWarnings = [];
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use (&$phpWarnings): bool {
$phpWarnings[] = "[{$errno}] {$errstr} in {$errfile}:{$errline}";
return true;
});

ob_start();

$files = [];
foreach ($characters as $char) {
$guid = isset($char['guid']) ? (int) $char['guid'] : 0;
if ($guid < 1) continue;

$row = $conn->executeQuery(
"SELECT `name` FROM `characters`
WHERE `guid` = ? AND `account` = ? AND `deleteDate` IS NULL LIMIT 1",
[$guid, $accId]
)->fetchAssociative();

if (!$row) continue;

$writer = new CharacterDumpWriter($conn);
$dump = $writer->getDump($guid);
if ($dump === null) continue;

$order = preg_replace('/[^0-9]/', '', (string)($char['order'] ?? '0'));
$level = preg_replace('/[^0-9]/', '', (string)($char['level'] ?? '0'));
$race = preg_replace('/[^a-zA-Z]/', '', (string)($char['race'] ?? 'Unknown'));
$class = preg_replace('/[^a-zA-Z]/', '', (string)($char['class'] ?? 'Unknown'));

$files["{$order}_{$level}_{$race}_{$class}_{$now}.dump"] = $dump;
}

ob_end_clean();
restore_error_handler();

if (!empty($phpWarnings)) {
wp_send_json_error([
'message' => 'An internal error occurred while generating the dump.',
'detail' => implode("\n", $phpWarnings),
], 500);
exit;
}

if (empty($files)) {
wp_send_json_error(['message' => 'No valid characters could be exported.'], 400);
exit;
}

$tmp = tempnam(sys_get_temp_dir(), 'pdump_');
$zip = new \ZipArchive();
$zip->open($tmp, \ZipArchive::OVERWRITE);
foreach ($files as $filename => $content) {
$zip->addFromString($filename, $content);
}
$zip->close();

if (ob_get_level()) ob_end_clean();

header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="dump_all_' . $now . '.zip"');
header('Content-Length: ' . filesize($tmp));
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
readfile($tmp);
unlink($tmp);
exit;
}
Loading
Loading