diff --git a/wp-ingame-shop/wp-ingame-shop-pro.php b/wp-ingame-shop/wp-ingame-shop-pro.php
index 739ec80..304e2e3 100644
--- a/wp-ingame-shop/wp-ingame-shop-pro.php
+++ b/wp-ingame-shop/wp-ingame-shop-pro.php
@@ -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 {
+
+
+ Ankauf pro Item konfigurieren
+
+
+
+
+
+
+
@@ -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 '❌ Name und Item-ID sind Pflichtfelder.
';
- } 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 '❌ Fehler beim Speichern: ' . esc_html($wpdb->last_error) . '
';
- }
- } 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 '❌ Fehler beim Erstellen: ' . esc_html($err) . '
';
- }
- }
- }
+ // 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 '' . esc_html($save_error) . '
';
}
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'));
?>
>Minecraft Item
>✈ Fly-Gutschein
>👑 Rang (LuckPerms)
+ >✈ Fly-Abo (tägl. Limit)
@@ -1641,11 +1789,31 @@ class WIS_Admin {
+
+
+
+ Spieler erhalten täglich bis zu 6 Stunden Fly-Zeit (konfigurierbar per fly-abo.max-daily-hours im Plugin).
+ Mehrfachkauf verlängert das bestehende Abo kumulativ.
+ Gespeicherte Item-ID: fly_abo_{tage} – z.B. fly_abo_30
+
+
+
+
+
Markierungen
@@ -1854,6 +2072,7 @@ class WIS_Admin {
Server zuweisen
Kategorie zuweisen
Aktivieren/Deaktivieren
+ Ankauf konfigurieren
@@ -1916,6 +2135,8 @@ class WIS_Admin {
} elseif (preg_match('/^rank_(.+)_(\d+)$/', $item->item_id, $rm2)) {
$rd = intval($rm2[2]);
echo '👑 ' . esc_html($rm2[1]) . ' — ' . ($rd === 0 ? 'dauerhaft ' : esc_html($rd) . ' Tage') . ' ';
+ } elseif (preg_match('/^fly_abo_(\d+)$/', $item->item_id, $abo_m2)) {
+ echo '✈ Fly-Abo — ' . esc_html($abo_m2[1]) . ' Tage ';
} else {
echo '' . esc_html($item->item_id) . '';
}
@@ -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=
+ * 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() {