Upload via Git Manager GUI

This commit is contained in:
Git Manager GUI
2026-04-28 21:45:56 +02:00
parent a7134aaaba
commit f571c9e8f8

View File

@@ -235,6 +235,30 @@ class WIS_Activator {
$wpdb->query("ALTER TABLE $table ADD COLUMN custom_image_url varchar(500) DEFAULT NULL AFTER categories");
}
// Ankauf-Spalten nachrüsten (ab v6.5)
$col_sell = $wpdb->get_var("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '$table' AND COLUMN_NAME = 'sell_enabled'");
if (!$col_sell) {
$wpdb->query("ALTER TABLE $table ADD COLUMN sell_enabled tinyint(1) NOT NULL DEFAULT 0 AFTER status");
$wpdb->query("ALTER TABLE $table ADD COLUMN sell_price_mode varchar(20) NOT NULL DEFAULT 'percent' AFTER sell_enabled");
$wpdb->query("ALTER TABLE $table ADD COLUMN sell_price_value int(11) NOT NULL DEFAULT 80 AFTER sell_price_mode");
}
// Sell-Log-Tabelle anlegen
$wpdb->query("CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wis_sell_log (
id mediumint(9) NOT NULL AUTO_INCREMENT,
player_name varchar(100) NOT NULL,
server varchar(100) NOT NULL,
item_id varchar(100) NOT NULL,
item_name varchar(255) NOT NULL,
quantity int(11) NOT NULL DEFAULT 1,
price_per_item decimal(10,2) NOT NULL,
total_paid decimal(10,2) NOT NULL,
sold_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY player_name (player_name),
KEY sold_at (sold_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;");
self::create_tables();
self::set_default_options();
self::create_default_categories();
@@ -997,6 +1021,86 @@ class WIS_DB {
// ADMIN PAGES
// ===========================================================
class WIS_Admin {
// Wird auf admin_init gefeuert vor jeder HTML-Ausgabe, damit wp_redirect() funktioniert
public static function handle_save_item() {
if (!isset($_POST['wis_save_item'])) return;
if (!current_user_can('manage_options')) return;
check_admin_referer('wis_item_form');
global $wpdb;
$item_type = sanitize_text_field($_POST['item_type'] ?? 'minecraft');
if ($item_type === 'fly') {
$resolved_item_id = sanitize_text_field($_POST['fly_duration'] ?? 'fly_5min');
} elseif ($item_type === 'rank') {
$rank_id = preg_replace('/[^a-z0-9_\-]/', '', strtolower($_POST['rank_id'] ?? 'vip'));
$lp_group = preg_replace('/[^a-zA-Z0-9_\-]/', '', $_POST['lp_group'] ?? $rank_id);
$default_group = preg_replace('/[^a-zA-Z0-9_\-]/', '', $_POST['default_group'] ?? 'default');
$rank_days = max(0, intval($_POST['rank_days'] ?? 30));
if (empty($lp_group)) $lp_group = $rank_id;
if (empty($default_group)) $default_group = 'default';
$resolved_item_id = 'rank_' . $rank_id . '_' . $lp_group . '_' . $default_group . '_' . $rank_days;
} elseif ($item_type === 'fly_abo') {
$abo_days = max(1, intval($_POST['abo_days'] ?? 30));
$resolved_item_id = 'fly_abo_' . $abo_days;
} else {
$resolved_item_id = sanitize_text_field($_POST['item_id'] ?? '');
}
if (empty($resolved_item_id) || empty(trim($_POST['name'] ?? ''))) {
// Fehler als transient speichern, damit page_items() ihn anzeigen kann
set_transient('wis_save_error_' . get_current_user_id(), '❌ Name und Item-ID sind Pflichtfelder.', 30);
wp_redirect(wp_get_referer() ?: admin_url('admin.php?page=wis_items'));
exit;
}
$data = [
'item_id' => $resolved_item_id,
'name' => sanitize_text_field($_POST['name']),
'description' => sanitize_textarea_field($_POST['description'] ?? ''),
'price' => intval($_POST['price'] ?? 0),
'offer_price' => intval($_POST['offer_price'] ?? 0),
'is_offer' => isset($_POST['is_offer']) ? 1 : 0,
'servers' => isset($_POST['servers']) ? json_encode(array_map('sanitize_text_field', $_POST['servers'])) : '[]',
'categories' => isset($_POST['categories']) ? json_encode(array_map('sanitize_text_field', $_POST['categories'])) : '[]',
'custom_image_url' => esc_url_raw($_POST['custom_image_url'] ?? ''),
'sell_enabled' => isset($_POST['sell_enabled']) ? 1 : 0,
'sell_price_mode' => in_array($_POST['sell_price_mode'] ?? '', ['percent','fixed','minus']) ? $_POST['sell_price_mode'] : 'percent',
'sell_price_value' => max(0, intval($_POST['sell_price_value'] ?? 80)),
'status' => (intval($_POST['price'] ?? 0) > 0 || $item_type === 'fly' || $item_type === 'rank' || $item_type === 'fly_abo') ? 'publish' : 'draft',
];
if (isset($_GET['edit'])) {
$result = WIS_DB::update_item(intval($_GET['edit']), $data);
if ($result !== false) {
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . intval($_GET['edit']) . '&saved=1'));
exit;
} else {
set_transient('wis_save_error_' . get_current_user_id(), '❌ Fehler beim Speichern: ' . $wpdb->last_error, 30);
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . intval($_GET['edit'])));
exit;
}
} else {
$existing = WIS_DB::get_item_by_item_id($resolved_item_id);
if ($existing) {
WIS_DB::update_item($existing->id, $data);
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . $existing->id . '&saved=1'));
exit;
}
$result = WIS_DB::insert_item($data);
if ($result) {
$new_id = $wpdb->insert_id;
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . $new_id . '&created=1'));
exit;
} else {
$err = $wpdb->last_error ?: 'Unbekannter Fehler.';
set_transient('wis_save_error_' . get_current_user_id(), '❌ Fehler beim Erstellen: ' . $err, 30);
wp_redirect(admin_url('admin.php?page=wis_items&add=1'));
exit;
}
}
}
public static function register_menu() {
add_menu_page(
'Ingame Shop',
@@ -1205,6 +1309,14 @@ class WIS_Admin {
$data['categories'] = json_encode($_POST['item_cat_assignments'][$id]);
}
break;
case 'sell':
$data['sell_enabled'] = isset($_POST['item_sell_enabled'][$id]) ? 1 : 0;
$mode = sanitize_text_field($_POST['item_sell_mode'][$id] ?? 'percent');
if (!in_array($mode, ['percent', 'fixed', 'minus'])) $mode = 'percent';
$data['sell_price_mode'] = $mode;
$data['sell_price_value'] = max(0, intval($_POST['item_sell_value'][$id] ?? 80));
break;
}
if (!empty($data)) {
@@ -1437,6 +1549,100 @@ class WIS_Admin {
</div>
</td>
</tr>
<?php elseif ($action === 'sell'): ?>
<tr>
<th colspan="2"><h3>Ankauf pro Item konfigurieren</h3></th>
</tr>
<tr>
<td colspan="2" style="padding:0;">
<div style="max-height:800px; overflow-y:auto; border:1px solid #ddd; padding:10px;">
<table class="widefat striped">
<thead>
<tr>
<th style="width:30%">Item Name</th>
<th style="width:12%">VK-Preis</th>
<th style="width:13%">Ankauf?</th>
<th style="width:18%">Modus</th>
<th style="width:12%">Wert</th>
<th style="width:15%">Ankaufspreis</th>
</tr>
</thead>
<tbody>
<?php foreach ($items_to_edit as $item):
$s_enabled = !empty($item->sell_enabled);
$s_mode = $item->sell_price_mode ?? 'percent';
$s_value = $item->sell_price_value ?? 80;
?>
<tr id="sell_row_<?php echo $item->id; ?>">
<td>
<strong><?php echo esc_html($item->name); ?></strong><br>
<small style="color:#666"><?php echo esc_html($item->item_id); ?></small>
</td>
<td><?php echo esc_html($item->price); ?> <?php echo esc_html($currency); ?></td>
<td>
<input type="checkbox"
name="item_sell_enabled[<?php echo $item->id; ?>]"
value="1"
<?php checked($s_enabled); ?>
onchange="wisUpdateSellRow(<?php echo $item->id; ?>, <?php echo intval($item->price); ?>)">
</td>
<td>
<select name="item_sell_mode[<?php echo $item->id; ?>]"
id="sell_mode_<?php echo $item->id; ?>"
onchange="wisUpdateSellRow(<?php echo $item->id; ?>, <?php echo intval($item->price); ?>)"
style="width:100%">
<option value="percent" <?php selected($s_mode, 'percent'); ?>>% vom VK</option>
<option value="minus" <?php selected($s_mode, 'minus'); ?>>VK minus</option>
<option value="fixed" <?php selected($s_mode, 'fixed'); ?>>Fixpreis</option>
</select>
</td>
<td>
<input type="number"
name="item_sell_value[<?php echo $item->id; ?>]"
id="sell_value_<?php echo $item->id; ?>"
value="<?php echo esc_attr($s_value); ?>"
min="0" style="width:70px"
oninput="wisUpdateSellRow(<?php echo $item->id; ?>, <?php echo intval($item->price); ?>)">
</td>
<td id="sell_preview_<?php echo $item->id; ?>" style="color:#0073aa;font-weight:bold;">
<?php
if ($s_enabled) {
if ($s_mode === 'percent') $sp = round($item->price * $s_value / 100, 2);
elseif ($s_mode === 'minus') $sp = max(0, $item->price - $s_value);
else $sp = max(0, $s_value);
echo number_format($sp, 2) . ' ' . esc_html($currency);
} else {
echo '<span style="color:#aaa"></span>';
}
?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<script>
function wisUpdateSellRow(id, vk) {
var enabled = document.querySelector('input[name="item_sell_enabled[' + id + ']"]').checked;
var mode = document.getElementById('sell_mode_' + id).value;
var val = parseFloat(document.getElementById('sell_value_' + id).value) || 0;
var preview = document.getElementById('sell_preview_' + id);
if (!enabled) { preview.innerHTML = '<span style="color:#aaa"></span>'; return; }
var price = 0;
if (mode === 'percent') price = Math.max(0, Math.round(vk * val) / 100);
else if (mode === 'minus') price = Math.max(0, vk - val);
else price = Math.max(0, val);
preview.textContent = price.toFixed(2) + ' <?php echo esc_js($currency); ?>';
}
// Initiale Vorschau für alle Zeilen
document.addEventListener('DOMContentLoaded', function() {
<?php foreach ($items_to_edit as $item): ?>
wisUpdateSellRow(<?php echo $item->id; ?>, <?php echo intval($item->price); ?>);
<?php endforeach; ?>
});
</script>
</td>
</tr>
<?php endif; ?>
</table>
@@ -1469,72 +1675,11 @@ class WIS_Admin {
}
}
if (isset($_POST['wis_save_item'])) {
check_admin_referer('wis_item_form');
global $wpdb;
// item_id je nach Typ ermitteln
$item_type = sanitize_text_field($_POST['item_type'] ?? 'minecraft');
if ($item_type === 'fly') {
$resolved_item_id = sanitize_text_field($_POST['fly_duration'] ?? 'fly_5min');
} elseif ($item_type === 'rank') {
$rank_id = preg_replace('/[^a-z0-9_\-]/', '', strtolower($_POST['rank_id'] ?? 'vip'));
$lp_group = preg_replace('/[^a-zA-Z0-9_\-]/', '', $_POST['lp_group'] ?? $rank_id);
$default_group = preg_replace('/[^a-zA-Z0-9_\-]/', '', $_POST['default_group'] ?? 'default');
$rank_days = max(0, intval($_POST['rank_days'] ?? 30));
if (empty($lp_group)) $lp_group = $rank_id;
if (empty($default_group)) $default_group = 'default';
// Format: rank_{rank_id}_{lp_group}_{default_group}_{days}
$resolved_item_id = 'rank_' . $rank_id . '_' . $lp_group . '_' . $default_group . '_' . $rank_days;
} else {
$resolved_item_id = sanitize_text_field($_POST['item_id'] ?? '');
}
// Pflichtfeld-Prüfung
if (empty($resolved_item_id) || empty(trim($_POST['name'] ?? ''))) {
echo '<div class="error"><p>❌ Name und Item-ID sind Pflichtfelder.</p></div>';
} else {
$data = [
'item_id' => $resolved_item_id,
'name' => sanitize_text_field($_POST['name']),
'description' => sanitize_textarea_field($_POST['description'] ?? ''),
'price' => intval($_POST['price'] ?? 0),
'offer_price' => intval($_POST['offer_price'] ?? 0),
'is_offer' => isset($_POST['is_offer']) ? 1 : 0,
'servers' => isset($_POST['servers']) ? json_encode(array_map('sanitize_text_field', $_POST['servers'])) : '[]',
'categories' => isset($_POST['categories']) ? json_encode(array_map('sanitize_text_field', $_POST['categories'])) : '[]',
'custom_image_url' => esc_url_raw($_POST['custom_image_url'] ?? ''),
'status' => (intval($_POST['price'] ?? 0) > 0 || $item_type === 'fly' || $item_type === 'rank') ? 'publish' : 'draft',
];
if (isset($_GET['edit'])) {
$result = WIS_DB::update_item(intval($_GET['edit']), $data);
if ($result !== false) {
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . intval($_GET['edit']) . '&saved=1'));
exit;
} else {
echo '<div class="error"><p>❌ Fehler beim Speichern: ' . esc_html($wpdb->last_error) . '</p></div>';
}
} else {
// Prüfen ob item_id bereits existiert → direkt zum Edit weiterleiten
$existing = WIS_DB::get_item_by_item_id($resolved_item_id);
if ($existing) {
// Bestehenden Artikel aktualisieren statt Duplikat-Fehler
WIS_DB::update_item($existing->id, $data);
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . $existing->id . '&saved=1'));
exit;
}
$result = WIS_DB::insert_item($data);
if ($result) {
$new_id = $wpdb->insert_id;
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . $new_id . '&created=1'));
exit;
} else {
$err = $wpdb->last_error ?: 'Unbekannter Fehler.';
echo '<div class="error"><p>❌ Fehler beim Erstellen: ' . esc_html($err) . '</p></div>';
}
}
}
// Fehlermeldung aus handle_save_item() anzeigen (falls Validierung fehlschlug)
$save_error = get_transient('wis_save_error_' . get_current_user_id());
if ($save_error) {
delete_transient('wis_save_error_' . get_current_user_id());
echo '<div class="error"><p>' . esc_html($save_error) . '</p></div>';
}
if (isset($_GET['created'])) {
@@ -1580,21 +1725,24 @@ class WIS_Admin {
// Format: rank_{rank_id}_{lp_group}_{default_group}_{days}
// Fallback für altes Format rank_{rank_id}_{days}
$is_rank_old = !$is_rank && $item && preg_match('/^rank_(.+)_(\d+)$/', $item->item_id, $rm_old);
$is_fly_abo = $item && preg_match('/^fly_abo_(\d+)$/', $item->item_id, $abo_m);
$cur_abo_days = $is_fly_abo ? intval($abo_m[1]) : 30;
$cur_rank_id = $is_rank ? $rm[1] : ($is_rank_old ? $rm_old[1] : 'vip');
$cur_lp_group = $is_rank ? $rm[2] : $cur_rank_id;
$cur_default_group = $is_rank ? $rm[3] : 'default';
$cur_rank_days = $is_rank ? intval($rm[4]) : ($is_rank_old ? intval($rm_old[2]) : 30);
$cur_label = $item ? esc_attr($item->name) : '';
$detected_type = $is_fly ? 'fly' : (($is_rank || $is_rank_old) ? 'rank' : 'minecraft');
$detected_type = $is_fly ? 'fly' : (($is_rank || $is_rank_old) ? 'rank' : ($is_fly_abo ? 'fly_abo' : 'minecraft'));
?>
<select id="item_type" name="item_type" onchange="wisToggleItemType(this.value)" style="margin-bottom:8px;">
<option value="minecraft" <?php echo $detected_type === 'minecraft' ? 'selected' : ''; ?>>Minecraft Item</option>
<option value="fly" <?php echo $detected_type === 'fly' ? 'selected' : ''; ?>>✈ Fly-Gutschein</option>
<option value="rank" <?php echo $detected_type === 'rank' ? 'selected' : ''; ?>>👑 Rang (LuckPerms)</option>
<option value="fly_abo" <?php echo $detected_type === 'fly_abo' ? 'selected' : ''; ?>>✈ Fly-Abo (tägl. Limit)</option>
</select>
<div id="wis_item_minecraft">
<input type="text" id="item_id" name="item_id" value="<?php echo ($item && !$is_fly && !$is_rank) ? esc_attr($item->item_id) : ''; ?>" class="regular-text" placeholder="minecraft:diamond">
<input type="text" id="item_id" name="item_id" value="<?php echo ($item && !$is_fly && !$is_rank && !$is_fly_abo) ? esc_attr($item->item_id) : ''; ?>" class="regular-text" placeholder="minecraft:diamond">
<p class="description">Z.B.: minecraft:diamond (Kategorien werden automatisch zugewiesen)</p>
</div>
@@ -1641,11 +1789,31 @@ class WIS_Admin {
</p>
</div>
<div id="wis_item_fly_abo" style="display:none;">
<table style="border-collapse:collapse;">
<tr>
<td style="padding:4px 10px 4px 0;"><label for="abo_days"><strong>Laufzeit (Tage):</strong></label></td>
<td>
<input type="number" id="abo_days" name="abo_days"
value="<?php echo esc_attr($cur_abo_days); ?>"
min="1" style="width:80px;">
<span style="margin-left:6px; color:#666;">Tage</span>
</td>
</tr>
</table>
<p class="description" style="margin-top:6px;">
Spieler erhalten täglich bis zu <strong>6 Stunden</strong> Fly-Zeit (konfigurierbar per <code>fly-abo.max-daily-hours</code> im Plugin).<br>
Mehrfachkauf verlängert das bestehende Abo kumulativ.<br>
Gespeicherte Item-ID: <code>fly_abo_{tage}</code> z.B. <code>fly_abo_30</code>
</p>
</div>
<script>
function wisToggleItemType(val) {
document.getElementById('wis_item_minecraft').style.display = (val === 'minecraft') ? '' : 'none';
document.getElementById('wis_item_fly').style.display = (val === 'fly') ? '' : 'none';
document.getElementById('wis_item_rank').style.display = (val === 'rank') ? '' : 'none';
document.getElementById('wis_item_fly_abo').style.display = (val === 'fly_abo') ? '' : 'none';
// item_id Pflichtfeld nur bei Minecraft-Items
document.getElementById('item_id').required = (val === 'minecraft');
}
@@ -1702,6 +1870,56 @@ class WIS_Admin {
<p class="description">Optional: Wenn gesetzt, wird der normale Preis durchgestrichen</p>
</td>
</tr>
<tr>
<th><label for="sell_enabled">Ankauf aktivieren</label></th>
<td>
<label>
<input type="checkbox" id="sell_enabled" name="sell_enabled" value="1"
<?php echo ($item && !empty($item->sell_enabled)) ? 'checked' : ''; ?>
onchange="wisToggleSell(this.checked)">
Spieler können dieses Item an den Shop verkaufen
</label>
</td>
</tr>
<tr id="wis_sell_row" <?php echo ($item && !empty($item->sell_enabled)) ? '' : 'style="display:none"'; ?>>
<th><label for="sell_price_mode">Ankaufspreis</label></th>
<td>
<select id="sell_price_mode" name="sell_price_mode" onchange="wisUpdateSellPreview()">
<option value="percent" <?php echo ($item && $item->sell_price_mode === 'percent') ? 'selected' : ''; ?>>% vom Verkaufspreis</option>
<option value="minus" <?php echo ($item && $item->sell_price_mode === 'minus') ? 'selected' : ''; ?>>Verkaufspreis minus fixer Betrag</option>
<option value="fixed" <?php echo ($item && $item->sell_price_mode === 'fixed') ? 'selected' : ''; ?>>Fixer Preis</option>
</select>
&nbsp;
<input type="number" id="sell_price_value" name="sell_price_value" min="0"
value="<?php echo $item ? esc_attr($item->sell_price_value ?? 80) : '80'; ?>"
style="width:80px" oninput="wisUpdateSellPreview()">
<span id="wis_sell_preview" style="margin-left:8px;color:#0073aa;"></span>
<p class="description">
Beispiele: 80&nbsp;%&nbsp;→&nbsp;80&nbsp;% des VK-Preises &nbsp;|&nbsp;
Modus «minus 10»&nbsp;→&nbsp;VK-Preis&nbsp;&nbsp;10&nbsp;|&nbsp;
Fixer Preis 15&nbsp;→&nbsp;immer&nbsp;15&nbsp;<?php echo esc_html($currency); ?>
</p>
<script>
function wisToggleSell(on) {
document.getElementById('wis_sell_row').style.display = on ? '' : 'none';
if (on) wisUpdateSellPreview();
}
function wisUpdateSellPreview() {
var vk = parseFloat(document.getElementById('price').value) || 0;
var mode = document.getElementById('sell_price_mode').value;
var val = parseFloat(document.getElementById('sell_price_value').value) || 0;
var price = 0;
if (mode === 'percent') price = Math.max(0, Math.round(vk * val) / 100);
else if (mode === 'minus') price = Math.max(0, vk - val);
else price = Math.max(0, val);
document.getElementById('wis_sell_preview').textContent =
'→ Ankaufspreis: ' + price.toFixed(2) + ' <?php echo esc_js($currency); ?>';
}
document.getElementById('price').addEventListener('input', wisUpdateSellPreview);
wisUpdateSellPreview();
</script>
</td>
</tr>
<tr>
<th>Markierungen</th>
<td>
@@ -1854,6 +2072,7 @@ class WIS_Admin {
<option value="server">Server zuweisen</option>
<option value="category">Kategorie zuweisen</option>
<option value="status">Aktivieren/Deaktivieren</option>
<option value="sell">Ankauf konfigurieren</option>
</select>
<input type="submit" name="wis_bulk_apply" class="button action" value="Anwenden">
</div>
@@ -1916,6 +2135,8 @@ class WIS_Admin {
} elseif (preg_match('/^rank_(.+)_(\d+)$/', $item->item_id, $rm2)) {
$rd = intval($rm2[2]);
echo '<span title="' . esc_attr($item->item_id) . '">👑 <code>' . esc_html($rm2[1]) . '</code> &mdash; ' . ($rd === 0 ? '<em>dauerhaft</em>' : esc_html($rd) . ' Tage') . '</span>';
} elseif (preg_match('/^fly_abo_(\d+)$/', $item->item_id, $abo_m2)) {
echo '<span title="' . esc_attr($item->item_id) . '">✈ Fly-Abo &mdash; ' . esc_html($abo_m2[1]) . ' Tage</span>';
} else {
echo '<code>' . esc_html($item->item_id) . '</code>';
}
@@ -2669,6 +2890,158 @@ class WIS_API {
'callback' => [self::class, 'get_orders_history'],
'permission_callback' => [WIS_Activator::class, 'spigot_permission'],
]);
// Ankauf-Endpunkte (ab v6.5)
register_rest_route('wis/v1', '/sell_items', [
'methods' => 'GET',
'callback' => [self::class, 'get_sell_items'],
'permission_callback' => [WIS_Activator::class, 'spigot_permission'],
]);
register_rest_route('wis/v1', '/sell_item', [
'methods' => 'POST',
'callback' => [self::class, 'process_sell'],
'permission_callback' => [WIS_Activator::class, 'spigot_permission'],
]);
}
// =========================================================
// ANKAUF Endpunkte (ab v6.5)
// =========================================================
/**
* GET /wis/v1/sell_items?server=<slug>
* Liefert alle Items die auf dem angegebenen Server ankaufbar sind.
*/
public static function get_sell_items($request) {
global $wpdb;
$server = sanitize_text_field($request->get_param('server') ?? '');
$currency = get_option('wis_currency_name', 'Coins');
$table = $wpdb->prefix . 'wis_items';
$items = $wpdb->get_results(
"SELECT id, item_id, name, price, sell_price_mode, sell_price_value
FROM $table
WHERE status = 'publish' AND sell_enabled = 1"
);
$result = [];
foreach ($items as $item) {
// Ankaufspreis berechnen
$sell_price = self::calc_sell_price(
(int) $item->price,
$item->sell_price_mode,
(int) $item->sell_price_value
);
if ($sell_price <= 0) continue;
// Server-Filter
if (!empty($server)) {
$servers = $wpdb->get_var($wpdb->prepare(
"SELECT servers FROM $table WHERE id = %d", $item->id
));
$srv_list = json_decode($servers, true) ?: [];
if (!empty($srv_list) && !in_array(strtolower($server), array_map('strtolower', $srv_list))) {
continue;
}
}
$result[] = [
'item_id' => $item->item_id,
'name' => $item->name,
'buy_price' => (int) $item->price,
'sell_price' => $sell_price,
];
}
return new WP_REST_Response([
'items' => $result,
'currency' => $currency,
]);
}
/**
* POST /wis/v1/sell_item
* Body: { "player": "Steve", "server": "survival", "item_id": "minecraft:diamond", "quantity": 5 }
* Antwortet mit dem Betrag der gutgeschrieben werden soll.
*/
public static function process_sell($request) {
global $wpdb;
$player = sanitize_text_field($request->get_param('player') ?? '');
$server = sanitize_text_field($request->get_param('server') ?? '');
$item_id = sanitize_text_field($request->get_param('item_id') ?? '');
$quantity = max(1, intval($request->get_param('quantity') ?? 1));
if (!$player || !$item_id) {
return new WP_REST_Response(['success' => false, 'message' => 'Fehlende Parameter'], 400);
}
$table = $wpdb->prefix . 'wis_items';
// Item suchen (case-insensitive, mit/ohne minecraft: prefix)
$clean_id = strtolower(str_replace('minecraft:', '', $item_id));
$row = $wpdb->get_row($wpdb->prepare(
"SELECT id, item_id, name, price, sell_enabled, sell_price_mode, sell_price_value, servers
FROM $table
WHERE sell_enabled = 1
AND (LOWER(item_id) = %s OR LOWER(REPLACE(item_id,'minecraft:','')) = %s)
LIMIT 1",
strtolower($item_id), $clean_id
));
if (!$row) {
return new WP_REST_Response(['success' => false, 'message' => 'Item nicht ankaufbar'], 404);
}
$sell_price = self::calc_sell_price(
(int) $row->price,
$row->sell_price_mode,
(int) $row->sell_price_value
);
if ($sell_price <= 0) {
return new WP_REST_Response(['success' => false, 'message' => 'Ankaufspreis ist 0'], 422);
}
$total = round($sell_price * $quantity, 2);
// Sell-Log schreiben
$wpdb->insert($wpdb->prefix . 'wis_sell_log', [
'player_name' => $player,
'server' => $server,
'item_id' => $row->item_id,
'item_name' => $row->name,
'quantity' => $quantity,
'price_per_item' => $sell_price,
'total_paid' => $total,
]);
return new WP_REST_Response([
'success' => true,
'item_id' => $row->item_id,
'item_name' => $row->name,
'quantity' => $quantity,
'price_per_item' => $sell_price,
'total' => $total,
]);
}
/**
* Berechnet den Ankaufspreis aus Verkaufspreis + Modus.
* mode = "percent" → value ist ein Prozentwert des VK-Preises (z.B. 80 = 80 %)
* mode = "fixed" → value ist ein absoluter Festpreis
* mode = "minus" → value ist ein absoluter Abzug vom VK-Preis
*/
private static function calc_sell_price(int $buy_price, string $mode, int $value): float {
switch ($mode) {
case 'fixed':
return max(0, $value);
case 'minus':
return max(0, $buy_price - $value);
case 'percent':
default:
return max(0, round($buy_price * $value / 100, 2));
}
}
public static function get_shop_items($request) {
@@ -3068,6 +3441,16 @@ class WIS_API {
];
}
$title_parts[] = $item['qty'] . 'x ' . $item['title'];
} elseif (preg_match('/^fly_abo_(\d+)$/', $item_id, $abo_m)) {
// Fly-Abo: qty > 1 verlängert das Abo (addiert Tage)
$abo_days = intval($abo_m[1]);
$total_days = $abo_days * intval($item['qty']);
$commands_payload[] = [
'type' => 'fly_abo',
'days' => $total_days,
'label' => $item['title'],
];
$title_parts[] = $item['qty'] . 'x ' . $item['title'];
} else {
// Normales Item
$items_payload[] = [
@@ -3392,8 +3775,12 @@ class WIS_Shortcode {
btn.addEventListener('click', function() {
document.querySelectorAll('.wis-cat-btn').forEach(function(b){ b.classList.remove('active'); });
btn.classList.add('active');
currentCat = btn.getAttribute('data-cat');
currentPage = 1;
currentCat = btn.getAttribute('data-cat');
currentPage = 1;
// Suche beim Kategoriewechsel leeren
currentSearch = '';
var searchInput = document.getElementById('wis-search');
if (searchInput) searchInput.value = '';
loadItems(1);
});
});
@@ -3406,6 +3793,18 @@ class WIS_Shortcode {
searchTimer = setTimeout(function() {
currentSearch = searchInput.value.trim();
currentPage = 1;
// Bei aktiver Suche: Kategorie-Filter deaktivieren (kategorienübergreifend suchen)
if (currentSearch) {
document.querySelectorAll('.wis-cat-btn').forEach(function(b){ b.classList.remove('active'); });
currentCat = '';
} else {
// Suche geleert: erste Kategorie wieder aktivieren
var firstBtn = document.querySelector('.wis-cat-btn');
if (firstBtn) {
firstBtn.classList.add('active');
currentCat = firstBtn.getAttribute('data-cat');
}
}
loadItems(1);
}, 350);
});
@@ -3860,6 +4259,7 @@ register_activation_hook(__FILE__, [WIS_Activator::class, 'activate']);
register_deactivation_hook(__FILE__, [WIS_Activator::class, 'deactivate']);
add_action('admin_menu', [WIS_Admin::class, 'register_menu']);
add_action('admin_init', [WIS_Admin::class, 'handle_save_item']);
add_action('rest_api_init',[WIS_API::class, 'register_routes']);
add_action('init', [WIS_Shortcode::class, 'register']);
add_action('widgets_init', function() {