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

+ + + +
+ + + + + + + + + + + + + sell_enabled); + $s_mode = $item->sell_price_mode ?? 'percent'; + $s_value = $item->sell_price_value ?? 80; + ?> + + + + + + + + + + +
Item NameVK-PreisAnkauf?ModusWertAnkaufspreis
+ name); ?>
+ item_id); ?> +
price); ?> + + onchange="wisUpdateSellRow(id; ?>, price); ?>)"> + + + + + + 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 ''; + } + ?> +
+
+ + + @@ -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')); ?>
- +

Z.B.: minecraft:diamond (Kategorien werden automatisch zugewiesen)

@@ -1641,11 +1789,31 @@ class WIS_Admin {

+ + + + Markierungen @@ -1854,6 +2072,7 @@ class WIS_Admin { + @@ -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() {