@@ -66,10 +66,10 @@ public function getRender($data)
Enrolled
-
Eligible
+
Eligible
- Log in to the game server to activate
-
Not Eligible
+
Not Eligible
getAcoreAccountId();
+ if (!$accId) return false;
+ $conn = $services->getAccountEm()->getConnection();
+ $row = $conn->executeQuery('SELECT totp_secret FROM account WHERE id = ?', [$accId])->fetchAssociative();
+ return $row && $row['totp_secret'] !== null;
+ } catch (\Throwable $e) {
+ return false;
+ }
+}
+
+/**
+ * Detect a user-initiated Website 2FA removal (done through the WP 2FA plugin UI)
+ * and log it with the user's IP. Admin removals pre-set the "last seen" flag to
+ * '0', so they are never misattributed here. Runs in the user's own session, so
+ * the only IP ever recorded is the user's - never an administrator's.
+ */
+function acore_2fa_sync_self_removals(int $userId): void {
+ $enabled = acore_website_2fa_enabled($userId);
+ $seen = get_user_meta($userId, 'acore_2fa_ws_seen_enabled', true);
+
+ if ($seen === '') {
+ update_user_meta($userId, 'acore_2fa_ws_seen_enabled', $enabled ? '1' : '0');
+ return;
+ }
+
+ if ($seen === '1' && !$enabled) {
+ $log = get_user_meta($userId, 'acore_2fa_admin_log', true);
+ $log = is_array($log) ? $log : [];
+ $log[] = [
+ 'type' => 'website',
+ 'by' => 'self',
+ 'timestamp' => time(),
+ 'ip' => \ACore\Hooks\User\acore_resolve_client_ip(),
+ ];
+ update_user_meta($userId, 'acore_2fa_admin_log', $log);
+ }
+
+ update_user_meta($userId, 'acore_2fa_ws_seen_enabled', $enabled ? '1' : '0');
+}
+
add_action( 'rest_api_init', function () {
register_rest_route( ACORE_SLUG . '/v1', 'server-info', array(
'methods' => 'GET',
+ 'permission_callback' => function() { return current_user_can('manage_options'); },
'callback' => function( $request ) {
- $data = ['message' => ServerInfoApi::serverInfo()];
- return $data;
+ $result = ServerInfoApi::serverInfo();
+ $errorPatterns = [
+ 'could not connect', 'connection refused', 'operation timed out',
+ 'error fetching', 'failed to enable', 'soap fault', 'not configured',
+ 'unable to connect', 'network unreachable',
+ ];
+ foreach ($errorPatterns as $pattern) {
+ if (stripos($result, $pattern) !== false) {
+ error_log('[acore] server-info SOAP error: ' . (is_scalar($result) ? (string) $result : 'non-scalar response'));
+ return new \WP_Error('soap_error', __('Server information is temporarily unavailable.', 'acore-wp-plugin'), ['status' => 503]);
+ }
+ }
+ return ['message' => $result];
+ }
+ ) );
+
+ $defaultRequirements = [['Scroll of Resurrection', 'mod-resurrection-scroll']];
+
+ register_rest_route( ACORE_SLUG . '/v1', 'server-module-requirements', array(
+ 'methods' => 'GET',
+ 'permission_callback' => function() { return current_user_can('manage_options'); },
+ 'callback' => function( $request ) use ($defaultRequirements) {
+ return ['requirements' => get_option('acore_module_requirements', $defaultRequirements)];
+ }
+ ));
+
+ register_rest_route( ACORE_SLUG . '/v1', 'server-module-requirements', array(
+ 'methods' => 'POST',
+ 'permission_callback' => function() { return current_user_can('manage_options'); },
+ 'callback' => function( $request ) {
+ $data = $request->get_json_params();
+ $reqs = isset($data['requirements']) ? $data['requirements'] : [];
+ $clean = [];
+ foreach ($reqs as $row) {
+ if (is_array($row) && count($row) === 2) {
+ $clean[] = [sanitize_text_field($row[0]), sanitize_text_field($row[1])];
+ }
+ }
+ update_option('acore_module_requirements', $clean);
+ return ['success' => true, 'requirements' => $clean];
+ }
+ ));
+
+ register_rest_route( ACORE_SLUG . '/v1', 'server-modules', array(
+ 'methods' => 'POST',
+ 'permission_callback' => function() { return current_user_can('manage_options'); },
+ 'callback' => function( $request ) {
+ try {
+ $raw = ACoreServices::I()->getServerSoap()->executeCommand('.server debug');
+ } catch (\Throwable $e) {
+ return new \WP_Error('soap_error', $e->getMessage(), ['status' => 503]);
+ }
+ if (!is_string($raw)) {
+ return new \WP_Error('soap_error', __('Unexpected module response from the server.', 'acore-wp-plugin'), ['status' => 503]);
+ }
+ $modules = [];
+ $capturing = false;
+ foreach (explode("\n", $raw) as $line) {
+ $line = trim($line);
+ if (!$capturing) {
+ if (stripos($line, 'List of enabled modules:') !== false) $capturing = true;
+ continue;
+ }
+ if (preg_match('/\b(mod-[a-zA-Z0-9_-]+)/', $line, $m)) $modules[] = $m[1];
+ }
+ $csv = implode(',', $modules);
+ $timestamp = time();
+ update_option('acore_modules_csv', $csv);
+ update_option('acore_modules_refreshed', $timestamp);
+ return ['modules' => $modules, 'csv' => $csv, 'refreshed' => $timestamp];
+ }
+ ) );
+
+ // Admin: check 2FA status for any user
+ register_rest_route( ACORE_SLUG . '/v1', 'admin/2fa-check', array(
+ 'methods' => 'POST',
+ 'permission_callback' => function() { return current_user_can('manage_options'); },
+ 'callback' => function( \WP_REST_Request $request ) {
+ $data = $request->get_json_params();
+ $type = isset($data['type']) ? sanitize_text_field($data['type']) : '';
+ $username = isset($data['username']) ? sanitize_text_field($data['username']) : '';
+ if (!in_array($type, ['website', 'ingame'], true))
+ return new \WP_Error('invalid_type', 'Invalid type.', ['status' => 400]);
+ if ($username === '')
+ return new \WP_Error('missing_username', 'Username is required.', ['status' => 400]);
+ $user = get_user_by('login', $username);
+ if (!$user)
+ return new \WP_Error('user_not_found', 'No WordPress account found with that username.', ['status' => 404]);
+
+ $log = get_user_meta($user->ID, 'acore_2fa_admin_log', true);
+ $log = is_array($log) ? $log : [];
+ $lastRemoval = null;
+ foreach (array_reverse($log) as $entry) {
+ if ($entry['type'] === $type) { $lastRemoval = $entry; break; }
+ }
+
+ if ($type === 'website') {
+ $active = acore_website_2fa_enabled($user->ID);
+ $backupCodes = get_user_meta($user->ID, 'wp_2fa_backup_codes', true);
+ $resp = [
+ 'found' => true,
+ 'username' => $user->user_login,
+ 'active' => $active,
+ 'backup_codes' => is_array($backupCodes) ? count($backupCodes) : 0,
+ ];
+ if ($lastRemoval) $resp['last_removal'] = ['date' => wp_date('jS \o\f F, Y \a\t H:i', $lastRemoval['timestamp']), 'by' => $lastRemoval['by'] ?? 'admin', 'staff' => $lastRemoval['staff'] ?? null, 'ip' => $lastRemoval['ip'] ?? null];
+ return $resp;
+ }
+
+ try {
+ $conn = ACoreServices::I()->getAccountEm()->getConnection();
+ $result = $conn->executeQuery('SELECT totp_secret FROM account WHERE username = ?', [strtoupper($username)]);
+ $row = $result->fetchAssociative();
+ if (!$row) return new \WP_Error('no_game_account', 'No game account found for this user.', ['status' => 404]);
+ $resp = ['found' => true, 'username' => $user->user_login, 'active' => $row['totp_secret'] !== null];
+ if ($lastRemoval) $resp['last_removal'] = ['date' => wp_date('jS \o\f F, Y \a\t H:i', $lastRemoval['timestamp']), 'by' => $lastRemoval['by'] ?? 'admin', 'staff' => $lastRemoval['staff'] ?? null, 'ip' => $lastRemoval['ip'] ?? null];
+ return $resp;
+ } catch (\Exception $e) {
+ return new \WP_Error('db_error', 'Database error.', ['status' => 500]);
+ }
+ }
+ ));
+
+ // Admin: remove 2FA for any user and log the action
+ register_rest_route( ACORE_SLUG . '/v1', 'admin/2fa-remove', array(
+ 'methods' => 'POST',
+ 'permission_callback' => function() { return current_user_can('manage_options'); },
+ 'callback' => function( \WP_REST_Request $request ) {
+ $data = $request->get_json_params();
+ $type = isset($data['type']) ? sanitize_text_field($data['type']) : '';
+ $username = isset($data['username']) ? sanitize_text_field($data['username']) : '';
+ if (!in_array($type, ['website', 'ingame'], true))
+ return new \WP_Error('invalid_type', 'Invalid type.', ['status' => 400]);
+ if ($username === '')
+ return new \WP_Error('missing_username', 'Username is required.', ['status' => 400]);
+ $user = get_user_by('login', $username);
+ if (!$user)
+ return new \WP_Error('user_not_found', 'No WordPress account found with that username.', ['status' => 404]);
+
+ if ($type === 'website') {
+ delete_user_meta($user->ID, 'wp_2fa_totp_key');
+ delete_user_meta($user->ID, 'wp_2fa_enabled_methods');
+ delete_user_meta($user->ID, 'wp_2fa_backup_methods_enabled');
+ delete_user_meta($user->ID, 'wp_2fa_grace_period_expiry');
+ delete_user_meta($user->ID, 'wp_2fa_user_setup_started_at');
+ delete_user_meta($user->ID, 'wp_2fa_user_authenticated_methods');
+ // Pre-set the user's last-seen flag so the self-removal detector
+ // does not misattribute this admin action to the user.
+ update_user_meta($user->ID, 'acore_2fa_ws_seen_enabled', '0');
+ } else {
+ try {
+ $conn = ACoreServices::I()->getAccountEm()->getConnection();
+ $rows = $conn->executeStatement('UPDATE account SET totp_secret = NULL WHERE username = ?', [strtoupper($username)]);
+ if ($rows === 0) return new \WP_Error('no_game_account', 'No game account found for this user.', ['status' => 404]);
+ } catch (\Exception $e) {
+ return new \WP_Error('db_error', 'Database error.', ['status' => 500]);
+ }
+ }
+
+ $staff = wp_get_current_user();
+ $now = time();
+ $log = get_user_meta($user->ID, 'acore_2fa_admin_log', true);
+ $log = is_array($log) ? $log : [];
+ $log[] = ['type' => $type, 'by' => 'admin', 'timestamp' => $now, 'staff' => $staff->user_login, 'staff_id' => $staff->ID];
+ update_user_meta($user->ID, 'acore_2fa_admin_log', $log);
+ return ['success' => true, 'date' => wp_date('jS \o\f F, Y \a\t H:i', $now), 'staff' => $staff->user_login];
+ }
+ ));
+
+ // Admin: remove a user's WP 2FA backup codes and notify them
+ register_rest_route( ACORE_SLUG . '/v1', 'admin/backup-codes-remove', array(
+ 'methods' => 'POST',
+ 'permission_callback' => function() { return current_user_can('manage_options'); },
+ 'callback' => function( \WP_REST_Request $request ) {
+ $data = $request->get_json_params();
+ $username = isset($data['username']) ? sanitize_text_field($data['username']) : '';
+ if ($username === '')
+ return new \WP_Error('missing_username', 'Username is required.', ['status' => 400]);
+ $user = get_user_by('login', $username);
+ if (!$user)
+ return new \WP_Error('user_not_found', 'No WordPress account found with that username.', ['status' => 404]);
+
+ delete_user_meta($user->ID, 'wp_2fa_backup_codes');
+
+ $staff = wp_get_current_user();
+ $now = time();
+ $log = get_user_meta($user->ID, 'acore_2fa_admin_log', true);
+ $log = is_array($log) ? $log : [];
+ $log[] = ['type' => 'backup', 'by' => 'admin', 'timestamp' => $now, 'staff' => $staff->user_login, 'staff_id' => $staff->ID];
+ update_user_meta($user->ID, 'acore_2fa_admin_log', $log);
+
+ return ['success' => true, 'date' => wp_date('jS \o\f F, Y \a\t H:i', $now), 'staff' => $staff->user_login];
+ }
+ ));
+
+ // Admin: look up a user's recorded login IP history (same data the user sees)
+ register_rest_route( ACORE_SLUG . '/v1', 'admin/login-history', array(
+ 'methods' => 'POST',
+ 'permission_callback' => function() { return current_user_can('manage_options'); },
+ 'callback' => function( \WP_REST_Request $request ) {
+ $data = $request->get_json_params();
+ $username = isset($data['username']) ? sanitize_text_field($data['username']) : '';
+ $page = max(1, (int) ($data['page'] ?? 1));
+ $perPage = 50;
+ if ($username === '')
+ return new \WP_Error('missing_username', 'Username is required.', ['status' => 400]);
+
+ $user = get_user_by('login', $username);
+ if (!$user)
+ return new \WP_Error('user_not_found', 'No WordPress account found with that username.', ['status' => 404]);
+ $all = \ACore\Hooks\User\acore_get_login_history($user->ID, 500);
+
+ $all = is_array($all) ? $all : [];
+ $total = count($all);
+ $offset = ($page - 1) * $perPage;
+ $slice = array_slice($all, $offset, $perPage);
+
+ $history = [];
+ foreach ($slice as $r) {
+ $history[] = [
+ 'ip' => $r['ip_address'] ?? '',
+ 'country' => $r['country'] ?? 'Unknown',
+ 'date' => isset($r['login_at']) ? \ACore\Hooks\User\acore_format_connection_date($r['login_at']) : '',
+ 'where' => (($r['source'] ?? 'website') === 'ingame') ? 'In-game' : 'Website',
+ ];
+ }
+ return [
+ 'found' => true,
+ 'username' => $username,
+ 'history' => $history,
+ 'total' => $total,
+ 'from' => $total ? $offset + 1 : 0,
+ 'to' => $offset + count($slice),
+ 'page' => $page,
+ 'has_more' => ($offset + count($slice)) < $total,
+ ];
+ }
+ ));
+
+ register_rest_route( ACORE_SLUG . '/v1', 'remove-ingame-2fa', array(
+ 'methods' => 'POST',
+ 'permission_callback' => function() { return is_user_logged_in(); },
+ 'callback' => function( \WP_REST_Request $request ) {
+ $user = wp_get_current_user();
+ if (!acore_website_2fa_enabled($user->ID))
+ return new \WP_Error('no_website_2fa', __('You must have website 2FA enabled to remove in-game 2FA from here.'), ['status' => 403]);
+
+ // Authorise via a recent panel unlock (TOTP or email) OR a fresh TOTP code.
+ if (!get_transient(acore_2fa_unlock_key($user->ID))) {
+ if (!acore_website_totp_enabled($user->ID))
+ return new \WP_Error('verify_required', __('Please verify your 2FA above before removing in-game 2FA.'), ['status' => 403]);
+ $data = $request->get_json_params();
+ $token = isset($data['token']) ? trim((string) $data['token']) : '';
+ if (!preg_match('/^\d{6}$/', $token))
+ return new \WP_Error('invalid_token', __('Please enter a valid 6-digit code.'), ['status' => 400]);
+ if (!\ACore\Components\ServerInfo\acore_wp2fa_code_is_valid($user->ID, $token))
+ return new \WP_Error('wrong_token', __('Incorrect code. Please try again.'), ['status' => 401]);
+ }
+
+ try {
+ $accId = ACoreServices::I()->getAcoreAccountId();
+ if (!$accId) return new \WP_Error('no_account', __('Could not find your game account.'), ['status' => 404]);
+ $conn = ACoreServices::I()->getAccountEm()->getConnection();
+ $conn->executeStatement('UPDATE account SET totp_secret = NULL WHERE id = ?', [$accId]);
+ return ['success' => true];
+ } catch (\Exception $e) {
+ return new \WP_Error('db_error', __('Database error. Please try again.'), ['status' => 500]);
+ }
+ }
+ ) );
+
+ // User: verify own website 2FA (TOTP) code - used to gate sensitive panels (e.g. backup codes)
+ register_rest_route( ACORE_SLUG . '/v1', 'verify-website-2fa', array(
+ 'methods' => 'POST',
+ 'permission_callback' => function() { return is_user_logged_in(); },
+ 'callback' => function( \WP_REST_Request $request ) {
+ $user = wp_get_current_user();
+ if (!acore_website_totp_enabled($user->ID))
+ return new \WP_Error('no_website_2fa', __('Website 2FA is not enabled on your account.'), ['status' => 400]);
+
+ $attemptKey = acore_2fa_attempt_key($user->ID);
+ if ((int) get_transient($attemptKey) >= 5)
+ return new \WP_Error('rate_limited', __('Too many incorrect codes. Please wait a few minutes.', 'acore-wp-plugin'), ['status' => 429]);
+
+ $data = $request->get_json_params();
+ $token = isset($data['token']) ? trim((string) $data['token']) : '';
+ if (!preg_match('/^\d{6}$/', $token))
+ return new \WP_Error('invalid_token', __('Please enter a valid 6-digit code.'), ['status' => 400]);
+ if (!\ACore\Components\ServerInfo\acore_wp2fa_code_is_valid($user->ID, $token)) {
+ set_transient($attemptKey, ((int) get_transient($attemptKey)) + 1, 10 * MINUTE_IN_SECONDS);
+ return new \WP_Error('wrong_token', __('Incorrect code. Please try again.'), ['status' => 401]);
+ }
+
+ delete_transient($attemptKey);
+ // Remember this unlock briefly so page refreshes don't re-prompt for the code.
+ set_transient(acore_2fa_unlock_key($user->ID), time(), 30 * MINUTE_IN_SECONDS);
+
+ return ['success' => true];
+ }
+ ) );
+
+ // User: email an unlock code (for accounts using email-based website 2FA)
+ register_rest_route( ACORE_SLUG . '/v1', 'request-email-2fa', array(
+ 'methods' => 'POST',
+ 'permission_callback' => function() { return is_user_logged_in(); },
+ 'callback' => function() {
+ $user = wp_get_current_user();
+ $rateKey = 'acore_2fa_email_rate_' . $user->ID;
+ if (get_transient($rateKey))
+ return new \WP_Error('rate_limited', __('Please wait before requesting another code.'), ['status' => 429]);
+
+ $primary = get_user_meta($user->ID, 'wp_2fa_enabled_methods', true);
+ $methods = is_array($primary) ? $primary : ($primary !== '' ? [$primary] : []);
+ if (!in_array('email', $methods, true))
+ return new \WP_Error('no_email_2fa', __('Email 2FA is not enabled on your account.'), ['status' => 400]);
+
+ $code = (string) wp_rand(100000, 999999);
+ set_transient('acore_2fa_email_code_' . acore_2fa_unlock_key($user->ID), wp_hash($code), 10 * MINUTE_IN_SECONDS);
+ set_transient($rateKey, true, 60);
+
+ $sent = wp_mail(
+ $user->user_email,
+ __('Your security verification code', 'acore-wp-plugin'),
+ sprintf(__('Your verification code is: %s (valid for 10 minutes).', 'acore-wp-plugin'), $code)
+ );
+ if (!$sent)
+ return new \WP_Error('email_failed', __('Could not send the email. Please try again later.'), ['status' => 500]);
+
+ return ['success' => true];
+ }
+ ) );
+
+ // User: verify an emailed code to unlock sensitive panels (same unlock as TOTP)
+ register_rest_route( ACORE_SLUG . '/v1', 'verify-email-2fa', array(
+ 'methods' => 'POST',
+ 'permission_callback' => function() { return is_user_logged_in(); },
+ 'callback' => function( \WP_REST_Request $request ) {
+ $user = wp_get_current_user();
+ $key = 'acore_2fa_email_code_' . acore_2fa_unlock_key($user->ID);
+ $attemptKey = acore_2fa_attempt_key($user->ID);
+ if ((int) get_transient($attemptKey) >= 5)
+ return new \WP_Error('rate_limited', __('Too many incorrect codes. Please wait a few minutes.', 'acore-wp-plugin'), ['status' => 429]);
+
+ $data = $request->get_json_params();
+ $code = isset($data['code']) ? trim((string) $data['code']) : '';
+ if (!preg_match('/^\d{6}$/', $code))
+ return new \WP_Error('invalid_token', __('Please enter a valid 6-digit code.'), ['status' => 400]);
+
+ $stored = get_transient($key);
+ if (!$stored || !hash_equals((string) $stored, wp_hash($code))) {
+ set_transient($attemptKey, ((int) get_transient($attemptKey)) + 1, 10 * MINUTE_IN_SECONDS);
+ return new \WP_Error('wrong_token', __('Incorrect or expired code. Please try again.'), ['status' => 401]);
+ }
+
+ delete_transient($attemptKey);
+ delete_transient($key);
+ set_transient(acore_2fa_unlock_key($user->ID), time(), 30 * MINUTE_IN_SECONDS);
+
+ return ['success' => true];
+ }
+ ) );
+
+ // User: lightweight 2FA status for real-time removal detection on the Security page
+ register_rest_route( ACORE_SLUG . '/v1', '2fa-status', array(
+ 'methods' => 'GET',
+ 'permission_callback' => function() { return is_user_logged_in(); },
+ 'callback' => function() {
+ $user = wp_get_current_user();
+ $log = get_user_meta($user->ID, 'acore_2fa_admin_log', true);
+ $log = is_array($log) ? $log : [];
+ return [
+ 'website_enabled' => acore_website_2fa_enabled($user->ID),
+ 'ingame_enabled' => acore_ingame_2fa_enabled($user->ID),
+ 'removal_count' => count($log),
+ ];
+ }
+ ) );
+
+ // User: paginated connection history (for "see more" without a page reload)
+ register_rest_route( ACORE_SLUG . '/v1', 'connections', array(
+ 'methods' => 'GET',
+ 'permission_callback' => function() { return is_user_logged_in(); },
+ 'callback' => function( \WP_REST_Request $request ) {
+ $user = wp_get_current_user();
+ $perPage = 50;
+ $page = max(1, (int) $request->get_param('page'));
+
+ $all = \ACore\Hooks\User\acore_get_login_history($user->ID, 500);
+ $all = is_array($all) ? $all : [];
+ $total = count($all);
+ $offset = ($page - 1) * $perPage;
+ $slice = array_slice($all, $offset, $perPage);
+ $myIp = \ACore\Hooks\User\acore_resolve_client_ip();
+
+ $rows = array_map(function ($r) use ($myIp) {
+ $ip = $r['ip_address'] ?? ($r['ip'] ?? '');
+ return [
+ 'ip' => $ip,
+ 'country' => ($r['country'] ?? '') !== '' ? $r['country'] : 'Unknown',
+ 'date' => ($r['login_at'] ?? '') !== '' ? \ACore\Hooks\User\acore_format_connection_date($r['login_at']) : '',
+ 'where' => (($r['source'] ?? 'website') === 'ingame') ? 'In-game' : 'Website',
+ 'current' => ($ip !== '' && $ip === $myIp),
+ ];
+ }, $slice);
+
+ return [
+ 'rows' => array_values($rows),
+ 'page' => $page,
+ 'total' => $total,
+ 'from' => $total ? $offset + 1 : 0,
+ 'to' => $offset + count($slice),
+ 'has_more' => ($offset + count($slice)) < $total,
+ ];
}
) );
});
diff --git a/src/acore-wp-plugin/src/Components/Tools/ToolsApi.php b/src/acore-wp-plugin/src/Components/Tools/ToolsApi.php
index 16f31175e..3d9347edf 100644
--- a/src/acore-wp-plugin/src/Components/Tools/ToolsApi.php
+++ b/src/acore-wp-plugin/src/Components/Tools/ToolsApi.php
@@ -7,27 +7,43 @@
class ToolsApi {
public static function ItemRestoreList($request) {
- return ACoreServices::I()->getRestorableItemsByCharacter($request['cguid']);
+ try {
+ return ACoreServices::I()->getRestorableItemsByCharacter($request['cguid']);
+ } catch (\InvalidArgumentException $e) {
+ return new \WP_Error('invalid_character', 'Character not found.', ['status' => 403]);
+ }
}
public static function ItemRestore($data) {
- $item = $data['item'];
- $cname = $data['cname'];
- return ACoreServices::I()->getServerSoap()->executeCommand("item restore $item $cname");
+ $cname = isset($data['cname']) && is_string($data['cname']) ? trim($data['cname']) : '';
+ $item = filter_var($data['item'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
+ if ($item === false) {
+ return new \WP_Error('invalid_item', 'Invalid item id.', ['status' => 400]);
+ }
+ if ($cname === '') {
+ return new \WP_Error('invalid_character', 'Invalid character.', ['status' => 400]);
+ }
+ $ownedCharacterName = ACoreServices::I()->getOwnedRestorableItemCharacterName($item, $cname);
+ if ($ownedCharacterName === null) {
+ return new \WP_Error('forbidden', 'Restorable item not found for that character.', ['status' => 403]);
+ }
+ return ACoreServices::I()->getServerSoap()->executeCommand("item restore $item $ownedCharacterName");
}
}
add_action( 'rest_api_init', function () {
register_rest_route( ACORE_SLUG . '/v1', 'item-restore/list/(?P
\d+)', array(
- 'methods' => 'GET',
- 'callback' => function( $request ) {
+ 'methods' => 'GET',
+ 'permission_callback' => function() { return is_user_logged_in(); },
+ 'callback' => function( $request ) {
return ToolsApi::ItemRestoreList($request);
}
));
register_rest_route( ACORE_SLUG . '/v1', 'item-restore', array(
- 'methods' => 'POST',
- 'callback' => function( $request ) {
+ 'methods' => 'POST',
+ 'permission_callback' => function() { return is_user_logged_in(); },
+ 'callback' => function( $request ) {
// if free item-restoration is disabled, return
if (Opts::I()->acore_item_restoration != '1') {
diff --git a/src/acore-wp-plugin/src/Components/UnstuckMenu/UnstuckView.php b/src/acore-wp-plugin/src/Components/UnstuckMenu/UnstuckView.php
index d3db91d66..fd2c93634 100644
--- a/src/acore-wp-plugin/src/Components/UnstuckMenu/UnstuckView.php
+++ b/src/acore-wp-plugin/src/Components/UnstuckMenu/UnstuckView.php
@@ -2,6 +2,8 @@
namespace ACore\Components\UnstuckMenu;
+use ACore\Utils\AcoreCharColors;
+
class UnstuckView
{
@@ -17,10 +19,14 @@ public function getUnstuckmenuRender($chars)
ob_start();
wp_enqueue_style('bootstrap-css', '//cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css', array(), '5.1.3');
- wp_enqueue_style('acore-css', ACORE_URL_PLG . 'web/assets/css/main.css', array(), '0.1');
+ wp_enqueue_style('acore-css', ACORE_URL_PLG . 'web/assets/css/main.css', array(), '0.5');
wp_enqueue_script('bootstrap-js', '//cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js', array(), '5.1.3');
wp_enqueue_script('jquery');
wp_enqueue_script('acore-unstuck-js', ACORE_URL_PLG . 'web/assets/unstuck/unstuck.js', array('jquery'), null, true);
+ wp_localize_script('acore-unstuck-js', 'unstuckData', array(
+ 'restUrl' => rest_url(ACORE_SLUG . '/v1/unstuck'),
+ 'nonce' => wp_create_nonce('wp_rest'),
+ ));
?>
@@ -31,7 +37,7 @@ public function getUnstuckmenuRender($chars)
Unstuck
Unstuck your characters and teleport them to the hearthstone location
-
+
$currentTime);
@@ -39,28 +45,26 @@ public function getUnstuckmenuRender($chars)
$tooltipText = $isDisabled ? 'Unstuck is on cooldown' : ''; // Tooltip text if disabled
$remainingCDTime = $isDisabled ? $char["time"] - $currentTime : 0;
$endTime = $isDisabled ? $char["time"] : 0;
+ $clsStyle = AcoreCharColors::rowStyle(intval($char["class"]), intval($char["race"]));
?>
-
-