Files
WP-Ingame-Shop-Pro/wp-ingame-shop.php
2026-01-06 18:21:01 +00:00

1619 lines
88 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/*
Plugin Name: WP Ingame Shop Pro - NO RCON & CUSTOM CURRENCY
Description: Vollautomatischer Shop mit Warenkorb.
Version: 1.0.0
Author: M_Viper
*/
if (!defined('ABSPATH')) exit;
// ===========================================================
// 1. ACTIVATOR & TABLES
// ===========================================================
class WIS_Activator {
public static function activate() {
self::create_tables();
self::setup_daily_deal();
if(get_option('wis_currency_name') === false) {
add_option('wis_currency_name', 'Coins');
}
if(get_option('wis_image_base_url') === false) {
add_option('wis_image_base_url', 'https://assets.minecraft-ids.com/1_21_10/');
}
if(get_option('wis_header_text') === false) {
add_option('wis_header_text', '✅ Auto-Bilder | 💰 Sicherer Checkout | 🎮 Ingame-Bestätigung | 🎫 Gutscheine');
}
if(get_option('wis_coupon_exclude_offers') === false) {
add_option('wis_coupon_exclude_offers', '0');
}
// Neue Settings
if(get_option('wis_daily_deal_enabled') === false) {
add_option('wis_daily_deal_enabled', '0');
}
if(get_option('wis_daily_deal_discount') === false) {
add_option('wis_daily_deal_discount', '20');
}
// Cron scheduling
if ( ! wp_next_scheduled( 'wis_daily_deal_event' ) ) {
wp_schedule_event( time(), 'daily', 'wis_daily_deal_event' );
}
flush_rewrite_rules();
}
public static function deactivate() {
wp_clear_scheduled_hook('wis_daily_deal_event');
flush_rewrite_rules();
}
private static function create_tables() {
global $wpdb;
$table_name = $wpdb->prefix . 'wis_orders';
$coupon_table_name = $wpdb->prefix . 'wis_coupons';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
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_title varchar(255) NOT NULL,
price int(11) NOT NULL,
quantity int(11) DEFAULT 1,
status varchar(20) DEFAULT 'pending',
response text,
created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY (id)
) $charset_collate;";
$sql2 = "CREATE TABLE $coupon_table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
code varchar(50) NOT NULL,
value int(11) NOT NULL,
type varchar(10) DEFAULT 'fixed',
usage_limit int(11) DEFAULT 1,
used_count int(11) DEFAULT 0,
expiry date DEFAULT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY (id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
dbDelta($sql2);
// Alte Installationen updaten (type Spalte hinzufügen)
$existing_columns = $wpdb->get_col_info("SELECT * FROM $coupon_table_name LIMIT 1");
if(!in_array('type', $existing_columns)) {
$wpdb->query("ALTER TABLE $coupon_table_name ADD COLUMN type varchar(10) DEFAULT 'fixed'");
}
}
public static function setup_daily_deal() {
// Initial setup beim ersten Aktivieren
}
public static function run_daily_deal() {
if(get_option('wis_daily_deal_enabled') === '0') return;
global $wpdb;
$discount_percent = intval(get_option('wis_daily_deal_discount', 20));
// 1. Aktuellen Daily Deal deaktivieren
$wpdb->update(
$wpdb->prefix.'postmeta',
['meta_value' => 0],
['meta_key' => '_wis_daily_deal', 'meta_value' => 1]
);
// 2. Zufälliges Item auswählen
$args = [
'post_type' => 'wis_item',
'post_status' => 'publish',
'posts_per_page' => 1,
'meta_query' => [
'relation' => 'AND',
['key' => '_wis_price', 'value' => 0, 'compare' => '>'],
['key' => '_wis_daily_deal', 'compare' => 'NOT EXISTS'] // Eines holen, das noch kein Daily Deal ist
]
];
$items = get_posts($args);
if(!empty($items)) {
$item_id = $items[0]->ID;
$price = intval(get_post_meta($item_id, '_wis_price', true));
// Rabatt berechnen
$offer_price = max(0, floor($price - ($price * ($discount_percent / 100))));
// Flags setzen
update_post_meta($item_id, '_wis_daily_deal', 1);
update_post_meta($item_id, '_wis_is_offer', 1);
update_post_meta($item_id, '_wis_offer_price', $offer_price);
}
}
}
// ===========================================================
// 2. ADMIN & CPT
// ===========================================================
class WIS_CPT {
public static function register_post_types() {
register_post_type('wis_item', [
'labels' => ['name' => 'Shop Items', 'singular_name' => 'Shop Item'],
'public' => false,
'show_ui' => true,
'show_in_menu' => false,
'show_in_admin_all_list' => true,
'capability_type' => 'post',
'capabilities' => [
'edit_post' => 'manage_options',
'read_post' => 'manage_options',
'delete_post' => 'manage_options',
'edit_posts' => 'manage_options',
'delete_posts' => 'manage_options',
'delete_others_posts' => 'manage_options'
],
'supports' => ['title', 'editor'],
'taxonomies' => ['wis_category']
]);
register_post_type('wis_server', [
'labels' => ['name' => 'Servers', 'singular_name' => 'Server'],
'public' => false,
'show_ui' => true,
'show_in_menu' => false,
'capability_type' => 'post',
'supports' => ['title']
]);
register_post_type('wis_coupon', [
'labels' => ['name' => 'Gutscheine', 'singular_name' => 'Gutschein'],
'public' => false,
'show_ui' => true,
'show_in_menu' => false,
'capability_type' => 'post',
'supports' => ['title']
]);
// NEU: Kategorien Taxonomie
$labels = [
'name' => 'Kategorien',
'singular_name' => 'Kategorie',
'search_items' => 'Kategorien suchen',
'all_items' => 'Alle Kategorien',
'parent_item' => 'Übergeordnete Kategorie',
'edit_item' => 'Kategorie bearbeiten',
'update_item' => 'Kategorie aktualisieren',
'add_new_item' => 'Neue Kategorie',
'new_item_name' => 'Kategorie Name',
'menu_name' => 'Kategorien',
'popular_items' => 'Beliebte Kategorien',
'separate_items_with_commas' => "Kategorien mit Kommas trennen",
'add_or_remove_items' => 'Kategorien hinzufügen oder entfernen',
'choose_from_most_used' => 'Häufigste Kategorien auswählen',
'not_found' => 'Keine Kategorien gefunden'
];
$args = [
'hierarchical' => true,
'labels' => $labels,
'show_ui' => true,
'show_in_menu' => true, // Erscheint als Menüpunkt
'show_in_nav_menus' => false,
'show_admin_column' => true,
'public' => false,
'rewrite' => false,
];
register_taxonomy('wis_category', 'wis_item', $args);
}
public static function add_meta_boxes() {
add_meta_box('wis_item_meta', 'Item Einstellungen', [self::class, 'render_item_meta'], 'wis_item', 'normal', 'high');
add_meta_box('wis_server_meta', 'Server Einstellungen', [self::class, 'render_server_meta'], 'wis_server', 'normal', 'high');
add_meta_box('wis_coupon_meta', 'Gutschein Einstellungen', [self::class, 'render_coupon_meta'], 'wis_coupon', 'normal', 'high');
}
public static function render_item_meta($post) {
wp_nonce_field('wis_save_item', 'wis_item_nonce');
$price = get_post_meta($post->ID,'_wis_price',true);
$item_id = get_post_meta($post->ID,'_wis_item_id',true);
$servers = get_post_meta($post->ID,'_wis_servers',true);
if(!is_array($servers)) $servers = [];
$is_offer = get_post_meta($post->ID,'_wis_is_offer',true);
$is_daily_deal = get_post_meta($post->ID,'_wis_daily_deal',true);
$offer_price = get_post_meta($post->ID,'_wis_offer_price',true);
$manual_daily = get_post_meta($post->ID,'_wis_manual_daily_deal',true);
$all_servers = get_posts(['post_type'=>'wis_server','numberposts'=>-1]);
$currency = get_option('wis_currency_name', 'Coins');
$exclude_offers = get_option('wis_coupon_exclude_offers');
// NEU: Kategorien
$cats = wp_get_object_terms($post->ID, 'wis_category');
$cat_ids = array_map(function($t) { return $t->term_id; }, $cats);
$current_status = get_post_status($post->ID);
$is_active = ($current_status === 'publish') && ($price > 0);
$status_msg = $is_active ? '<span style="color:green; font-weight:bold;">✅ Aktiv im Shop</span>' : '<span style="color:orange; font-weight:bold;">⏸️ Inaktiv (Kein Preis oder Entwurf)</span>';
echo '<p>'.$status_msg.'</p>';
echo '<p><label><strong>Titel (Anzeige im Shop)</strong></label>';
echo '<p class="description">Wird automatisch aus dem Post-Titel übernommen</p></p>';
echo '<p><label><strong>Preis ('.esc_html($currency).')</strong></label><input type="number" name="wis_price" value="'.esc_attr($price).'" style="width:100%"></p>';
echo '<p class="description">Wenn du hier einen Preis einträgst (>0), wird das Item automatisch aktiv.</p>';
echo '<p><label><strong>Item ID (z.B. minecraft:diamond)</strong></label><input type="text" name="wis_item_id" value="'.esc_attr($item_id).'" style="width:100%" placeholder="z.B. minecraft:diamond">';
echo '<p class="description"><span style="color:green;">✅ Daraus wird automatisch das Bild generiert.</span></p></p>';
// NEU: Kategorie Auswahl
echo '<p><label><strong>Kategorie(n)</strong></label>';
$terms = get_terms([
'taxonomy' => 'wis_category',
'hide_empty' => false,
]);
echo '<div style="border:1px solid #ddd;padding:10px;background:#f9f9f9;max-height:200px;overflow-y:auto;">';
if(!empty($terms)) {
foreach($terms as $term) {
$selected = in_array($term->term_id, $cat_ids) ? 'checked' : '';
echo '<label style="display:block;margin:5px 0;">';
echo '<input type="checkbox" name="wis_cats[]" value="'.$term->term_id.'" '.$selected.'> '.esc_html($term->name);
echo '</label>';
}
} else {
echo '<em>Noch keine Kategorien erstellt.</em>';
}
echo '</div></p>';
echo '<div style="background:#e3f2fd; padding:10px; border-left:4px solid #2196f3; margin-bottom:15px;">';
echo '<p><label><strong>Daily Deal (Angebot des Tages)</strong></label></p>';
echo '<p><label><input type="checkbox" name="wis_manual_daily_deal" value="1" '.checked($manual_daily_deal, 1, false).'> <strong>Manuell als "Angebot des Tages" setzen 🎁</strong></label>';
echo '<br><small>Überschreibt die Automatik bis Mitternacht. Praktisch zum Testen.</small>';
if($is_daily_deal) {
echo '<br><strong style="color:#2196f3;"> Info:</strong> Dieses Item ist aktuell das Angebot des Tages. ';
echo 'Der Rabatt wurde automatisch berechnet.';
}
echo '</div>';
echo '<div style="background:#fff3cd; padding:10px; border-left:4px solid #ffc107; margin-bottom:15px;">';
echo '<p><label><strong>Angebot / Sale</strong></label></p>';
echo '<p><label><input type="checkbox" name="wis_is_offer" value="1" '.checked($is_offer, 1, false).'> Als "Angebot" markieren 🔥</label>';
echo '<br><small>Hervorgehobenes Item im Shop (rot/gold Badge).</small>';
if($exclude_offers) {
echo '<br><strong style="color:red;">Hinweis:</strong> Gutscheine sind aktuell global für Angebote deaktiviert.';
}
echo '</p>';
echo '<p><label>Angebots-Preis (Optional - wird ignoriert wenn Daily Deal aktiv)</label>';
echo '<input type="number" name="wis_offer_price" value="'.esc_attr($offer_price).'" style="width:100%" placeholder="Preis nur im Angebot">';
echo '<br><small>Wenn ausgefüllt, wird der normale Preis durchgestrichen und dieser angezeigt.</small></p>';
echo '</div>';
echo '<p><label><strong>Beschreibung</strong></label>';
echo '<p class="description">Dies ist der lange Text, der unter dem Titel im Shop angezeigt wird.</p>';
echo '<p><label><strong>Server zuweisen (Mehrfachauswahl möglich)</strong></label>';
echo '<div style="border:1px solid #ddd;padding:10px;background:#f9f9f9;max-height:200px;overflow-y:auto;">';
if(empty($all_servers)) echo '<em>Noch keine Server erstellt.</em>';
foreach($all_servers as $s){
$checked = in_array($s->post_name, $servers) ? 'checked' : '';
echo '<label style="display:block;margin:5px 0;">';
echo '<input type="checkbox" name="wis_servers[]" value="'.esc_attr($s->post_name).'" '.$checked.'> ';
echo esc_html($s->post_title);
echo '</label>';
}
echo '</div></p>';
}
public static function render_server_meta($post){
echo '<p style="background:#e2e3e5;padding:15px;border-left:4px solid #6c757d;">';
echo '<strong>Info:</strong> Da keine RCON-Verbindung benötigt wird (Vault/Plugin bestätigt), sind hier keine weiteren Einstellungen nötig.<br>';
echo 'Der Servername dient nur zur Identifikat im Shop-Frontend.';
echo '</p>';
}
public static function render_coupon_meta($post) {
wp_nonce_field('wis_save_coupon', 'wis_coupon_nonce');
$code = get_post_meta($post->ID,'_wis_code',true);
$value = get_post_meta($post->ID,'_wis_value',true);
$limit = get_post_meta($post->ID,'_wis_usage_limit',true);
$expiry = get_post_meta($post->ID,'_wis_expiry',true);
$type = get_post_meta($post->ID,'_wis_type',true);
$exclude_offers = get_option('wis_coupon_exclude_offers');
if(empty($type)) $type = 'fixed';
echo '<p><label><strong>Gutschein Code</strong></label>';
echo '<input type="text" name="wis_code" value="'.esc_attr($code).'" style="width:100%; text-transform:uppercase;" placeholder="z.B. SUMMER20">';
echo '<p><label><strong>Rabattart</strong></label>';
echo '<select name="wis_type" style="width:100%;">';
echo '<option value="fixed" '.selected($type, 'fixed', false).'>Festbetrag (z.B. -100 Coins)</option>';
echo '<option value="percent" '.selected($type, 'percent', false).'>Prozentual (z.B. 10% Rabatt)</option>';
echo '</select>';
echo '<p><label><strong>Wert</strong></label>';
echo '<input type="number" name="wis_value" value="'.esc_attr($value).'" style="width:100%" placeholder="Je nach Typ">';
if($exclude_offers) {
echo '<p style="background:#ffeeba; color:#856404; padding:10px; border:1px solid #ffeeba;">';
echo '⚠️ <strong>Achtung:</strong> In den Shop-Einstellungen ist aktiviert, dass Gutscheine <strong>nicht</strong> auf Angebot-Items (Sale) angewendet werden. Dieser Gutschein wirkt nur auf reguläre Items.';
echo '</p>';
}
echo '<p><label><strong>Nutzungslimit</strong></label>';
echo '<input type="number" name="wis_usage_limit" value="'.esc_attr($limit).'" style="width:100%;" placeholder="z.B. 50">';
echo '<p><label><strong>Ablaufdatum (Optional)</strong></label>';
echo '<input type="date" name="wis_expiry" value="'.esc_attr($expiry).'" style="width:100%;">';
}
public static function save_meta($post_id){
if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if(get_post_type($post_id)==='wis_item' && isset($_POST['wis_item_nonce']) && wp_verify_nonce($_POST['wis_item_nonce'],'wis_save_item')){
update_post_meta($post_id,'_wis_price',intval($_POST['wis_price']));
update_post_meta($post_id,'_wis_item_id',sanitize_text_field($_POST['wis_item_id']));
$is_offer = isset($_POST['wis_is_offer']) ? 1 : 0;
update_post_meta($post_id,'_wis_is_offer', $is_offer);
update_post_meta($post_id,'_wis_offer_price', intval($_POST['wis_offer_price']));
// NEU: Manual Daily Deal Logic
$manual_daily = isset($_POST['wis_manual_daily_deal']) ? 1 : 0;
if($manual_daily) {
update_post_meta($post_id,'_wis_daily_deal', 1);
update_post_meta($post_id,'_wis_is_offer', 1);
} else {
// Wenn manuell abgewählt, löschen wir das Flag, damit der Cron ran darf
// update_post_meta($post_id,'_wis_daily_deal', 0); // Optional: Reset flag
}
$servers = isset($_POST['wis_servers']) && is_array($_POST['wis_servers']) ? array_map('sanitize_text_field', $_POST['wis_servers']) : [];
update_post_meta($post_id,'_wis_servers', $servers);
$cats = isset($_POST['wis_cats']) ? array_map('intval', $_POST['wis_cats']) : [];
wp_set_object_terms($post_id, $cats, 'wis_category');
}
if(get_post_type($post_id)==='wis_server' && isset($_POST['wis_server_nonce']) && wp_verify_nonce($_POST['wis_server_nonce'],'wis_save_server')){
// Nichts zu tun
}
if(get_post_type($post_id)==='wis_coupon' && isset($_POST['wis_coupon_nonce']) && wp_verify_nonce($_POST['wis_coupon_nonce'],'wis_save_coupon')){
$code = sanitize_text_field(strtoupper($_POST['wis_code']));
$value = intval($_POST['wis_value']);
$limit = intval($_POST['wis_usage_limit']);
$expiry = sanitize_text_field($_POST['wis_expiry']);
$type = sanitize_text_field($_POST['wis_type']);
if(!in_array($type, ['fixed', 'percent'])) $type = 'fixed';
update_post_meta($post_id,'_wis_code', $code);
update_post_meta($post_id,'_wis_value', $value);
update_post_meta($post_id,'_wis_type', $type);
update_post_meta($post_id,'_wis_usage_limit', $limit);
update_post_meta($post_id,'_wis_expiry', $expiry);
global $wpdb;
$table_name = $wpdb->prefix . 'wis_coupons';
$data = ['code' => $code, 'value' => $value, 'type' => $type, 'usage_limit' => $limit, 'expiry' => $expiry];
$existing = $wpdb->get_row($wpdb->prepare("SELECT id FROM $table_name WHERE code = %s", $code));
if($existing) {
$wpdb->update($table_name, $data, ['id' => $existing->id]);
} else {
$wpdb->insert($table_name, $data);
}
$db_coupon = $wpdb->get_row($wpdb->prepare("SELECT used_count FROM $table_name WHERE code = %s", $code));
if($db_coupon) {
update_post_meta($post_id, '_wis_used_count', $db_coupon->used_count);
}
}
}
public static function auto_update_status($post_id) {
static $processing = [];
if (isset($processing[$post_id])) return;
$processing[$post_id] = true;
if (get_post_type($post_id) != 'wis_item') {
unset($processing[$post_id]);
return;
}
$price = get_post_meta($post_id, '_wis_price', true);
$current_status = get_post_status($post_id);
$new_status = ($price > 0) ? 'publish' : 'draft';
if ($current_status !== $new_status) {
remove_action('save_post', [__CLASS__, 'auto_update_status'], 10);
wp_update_post(['ID' => $post_id, 'post_status' => $new_status]);
add_action('save_post', [__CLASS__, 'auto_update_status'], 10);
}
unset($processing[$post_id]);
}
public static function delete_coupon_from_table($post_id) {
if(get_post_type($post_id) != 'wis_coupon') return;
$code = get_post_meta($post_id, '_wis_code', true);
if($code) {
global $wpdb;
$wpdb->delete($wpdb->prefix . 'wis_coupons', ['code' => $code]);
}
}
}
// ===========================================================
// Bulk Delete Funktionalität
// ===========================================================
class WIS_Bulk_Actions {
public static function register_bulk_actions($bulk_actions) {
$bulk_actions['delete'] = __('Löschen', 'wis');
return $bulk_actions;
}
public static function handle_bulk_actions($redirect_to, $action, $post_ids) {
if ($action !== 'delete') return $redirect_to;
$deleted = 0;
foreach ($post_ids as $post_id) {
if (current_user_can('delete_post', $post_id)) {
wp_delete_post($post_id, true);
$deleted++;
}
}
if ($deleted > 0) {
$redirect_to = add_query_arg([
'bulk_deleted' => $deleted,
'post_type' => get_post_type($post_ids[0] ?? 0)
], $redirect_to);
}
return $redirect_to;
}
public static function admin_notices() {
if (!empty($_GET['bulk_deleted'])) {
$count = intval($_GET['bulk_deleted']);
$msg = sprintf(_n('%d Eintrag gelöscht.', '%d Einträge gelöscht.', $count, 'wis'), $count);
echo '<div class="updated"><p>✅ ' . esc_html($msg) . '</p></div>';
}
}
}
// ===========================================================
// 3. ADMIN MENU
// ===========================================================
class WIS_Admin {
public static function register_menu() {
add_menu_page('Ingame Shop', 'Ingame Shop', 'manage_options', 'wis_shop', [self::class, 'page_overview'], 'dashicons-cart', 6);
add_submenu_page('wis_shop', 'Bestellungen', 'Bestellungen', 'manage_options', 'wis_orders', [self::class, 'page_orders']);
add_submenu_page('wis_shop', 'Top Spender', 'Top Spender', 'manage_options', 'wis_top_spenders', [self::class, 'page_top_spenders']);
add_submenu_page('wis_shop', 'Items', 'Items', 'manage_options', 'edit.php?post_type=wis_item');
add_submenu_page('wis_shop', 'Kategorien', 'Kategorien', 'manage_options', 'edit-tags.php?taxonomy=wis_category');
add_submenu_page('wis_shop', 'Servers', 'Servers', 'manage_options', 'edit.php?post_type=wis_server');
add_submenu_page('wis_shop', 'Gutscheine', 'Gutscheine', 'manage_options', 'edit.php?post_type=wis_coupon');
}
public static function page_overview() {
if(isset($_POST['wis_save_settings']) && check_admin_referer('wis_settings_nonce')) {
update_option('wis_currency_name', sanitize_text_field($_POST['wis_currency_name']));
update_option('wis_image_base_url', sanitize_text_field($_POST['wis_image_base_url']));
update_option('wis_header_text', sanitize_textarea_field($_POST['wis_header_text']));
update_option('wis_coupon_exclude_offers', isset($_POST['wis_coupon_exclude_offers']) ? '1' : '0');
// NEU: Daily Deal Settings
update_option('wis_daily_deal_enabled', isset($_POST['wis_daily_deal_enabled']) ? '1' : '0');
update_option('wis_daily_deal_discount', intval($_POST['wis_daily_deal_discount']));
echo '<div class="updated"><p>✅ Einstellungen gespeichert!</p></div>';
}
$currency = get_option('wis_currency_name', 'Coins');
$img_base = get_option('wis_image_base_url', 'https://assets.minecraft-ids.com/1_21_10/');
$header_text = get_option('wis_header_text', '✅ Auto-Bilder | 💰 Sicherer Checkout');
$exclude_offers = get_option('wis_coupon_exclude_offers', '0');
// NEU: Daily Deal Settings
$daily_deal_enabled = get_option('wis_daily_deal_enabled', '0');
$daily_deal_discount = get_option('wis_daily_deal_discount', '20');
echo '<div class="wrap"><h1>🛒 Ingame Shop Pro <span style="color:#28a745;">v1.0.0</span></h1>';
echo '<div class="card" style="max-width:800px; margin-top:20px;"><h2>✅ Feature Übersicht</h2><ul>';
echo '<li>🛒 <strong>Warenkorb:</strong> Kauf mehrerer Items auf einmal.</li>';
echo '<li>🔗 <strong>Einzelne Bestellung:</strong> Alles wird in einer einzigen Datenbankzeile gespeichert.</li>';
echo '<li>📋 <strong>Detail-Anzeige:</strong> Im Backend siehst du genau "1x Diamant, 64x Stein" etc. & Gutschein.</li>';
echo '<li>✨ <strong>Sauberer Prozess:</strong> Alle Items werden sofort nach einer Bestätigung ausgegeben.</li>';
echo '<li>🔥 <strong>Angebote:</strong> Items können hervorgehoben werden.</li>';
echo '<li>🎁 <strong>Daily Deal:</strong> Automatisches Angebot des Tages.</li>';
echo '<li>🎫 <strong>Smart Coupons:</strong> Gutscheine (Festbetrag & Prozent) werden korrekt bei reinen Angeboten ignoriert.</li>';
echo '<li>🏷️ <strong>Kategorien:</strong> Items können in Kategorien gruppiert werden.</li>';
echo '<li>🏆 <strong>Top Spender:</strong> Statistiken im Backend.</li>';
echo '</ul></div>';
echo '<div class="card" style="max-width:800px; margin-top:20px; padding:20px; border:1px solid #ccc; background:#fff;">';
echo '<h2>⚙️ Globale Einstellungen</h2>';
echo '<form method="post">';
wp_nonce_field('wis_settings_nonce');
echo '<table class="form-table"><tbody>';
echo '<tr><th scope="row"><label for="wis_header_text">Shop Header Text</label></th>';
echo '<td><textarea id="wis_header_text" name="wis_header_text" class="large-text" rows="2">'.esc_textarea($header_text).'</textarea>';
echo '<p class="description">Der Text, der ganz oben im Shop angezeigt wird. <br><strong>Hinweis:</strong> Wenn dieses Feld leer ist, wird auch kein grüner Balken im Shop angezeigt.</p></td></tr>';
echo '<tr><th scope="row"><label for="wis_currency_name">Währungsname</label></th>';
echo '<td><input type="text" id="wis_currency_name" name="wis_currency_name" value="'.esc_attr($currency).'" class="regular-text"></td></tr>';
echo '<tr><th scope="row"><label for="wis_image_base_url">Bilder Basis-URL</label></th>';
echo '<td><input type="text" id="wis_image_base_url" name="wis_image_base_url" value="'.esc_attr($img_base).'" class="large-text code">';
echo '<p class="description">Wird für die Bildgenerierung verwendet.</p></td></tr>';
echo '<tr><th scope="row"><label>Gutscheine bei Angeboten ausschließen?</label></th>';
echo '<td><label><input type="checkbox" name="wis_coupon_exclude_offers" value="1" '.checked($exclude_offers, '1', false).'> Ja, Gutscheine sollen NICHT auf Sale-Items (Angebote) angewendet werden.</label>';
echo '<p class="description">Wenn aktiv, wird der Rabatt nur auf den Warenkorbwert der regulären Items berechnet. Angebote werden vollgezahlt.</p></td></tr>';
// NEU: Daily Deal Settings
echo '<tr><th scope="row"><label>Daily Deal (Angebot des Tages)</label></th>';
echo '<td><label><input type="checkbox" name="wis_daily_deal_enabled" value="1" '.checked($daily_deal_enabled, '1', false).'> Automatischer Daily Deal aktivieren 🎁</label>';
echo '<p class="description">Wenn aktiv, wird jeden Tag um Mitternacht ein zufälliges Item als "Angebot des Tages" markiert mit einem Rabatt.</p>';
echo '</td></tr>';
echo '<tr><th scope="row"><label>Daily Deal Rabatt (%)</label></th>';
echo '<td><input type="number" min="1" max="99" id="wis_daily_deal_discount" name="wis_daily_deal_discount" value="'.esc_attr($daily_deal_discount).'">';
echo '<p class="description">Der Prozentsatz, um den der Preis gesenkt wird (z.B. 20 für 20%).</p></td></tr>';
echo '</tbody></table>';
echo '<p class="submit"><input type="submit" name="wis_save_settings" class="button button-primary" value="Einstellungen speichern"></p>';
echo '</form>';
echo '</div>';
echo '<div class="card" style="max-width:800px; margin-top:20px; padding:20px; border:1px solid #ccc; background:#f9f9f9;">';
echo '<h2>🚀 Bulk Import</h2>';
echo '<p><strong>Smart Import:</strong> Lädt Items in Paketen (20 pro Durchlauf). Vorhandene Items werden übersprungen.</p>';
echo '<div id="wis-import-controls">';
echo '<label for="wis_import_url">Import URL:</label>';
echo '<input type="text" id="wis_import_url" value="https://minecraft-ids.com/data/1.21.10.json" class="large-text code" style="width:100%; margin-top:10px; margin-bottom:10px;">';
echo '<div class="wis-import-actions">';
echo '<button type="button" id="wis-btn-fetch" class="button button-secondary">1. Daten laden</button>';
echo '<button type="button" id="wis-btn-start" class="button button-primary" disabled>2. Import starten</button>';
echo '</div></div>';
echo '<div id="wis-progress-container" style="display:none; margin-top:20px;">';
echo '<div style="display:flex; justify-content:space-between; margin-bottom:5px;">';
echo '<span id="wis-progress-text">0 / 0</span><span id="wis-percent">0%</span></div>';
echo '<div style="width:100%; height:20px; background:#e9ecef; border-radius:10px; overflow:hidden;">';
echo '<div id="wis-progress-bar" style="width:0%; height:100%; background:#28a745; transition:width 0.3s;"></div>';
echo '</div></div>';
echo '</div>';
?>
<script>
const WisImport = {
data: [], batchSize: 20, currentIndex: 0,
fetch: function() {
const url = document.getElementById('wis_import_url').value.trim();
const btn = document.getElementById('wis-btn-fetch');
if(!url) { alert('Bitte URL eingeben'); return; }
btn.disabled = true; btn.textContent = '⏳ Lade Daten...';
fetch('<?php echo rest_url('wis/v1/fetch_remote_data'); ?>', {
method: 'POST', headers: {'Content-Type':'application/json','X-WP-Nonce':'<?php echo wp_create_nonce('wp_rest'); ?>'},
body: JSON.stringify({url: url})
})
.then(r => r.json()).then(res => {
if(res.success) { this.data = res.items; document.getElementById('wis-btn-start').disabled = false; document.getElementById('wis-btn-start').textContent = '✅ 2. Import starten (' + this.data.length + ' Items)'; alert('Daten geladen! ' + this.data.length + ' Items bereit.'); } else { alert('Fehler: ' + res.message); }
}).catch(e => { alert('Fehler: ' + e.message); }).finally(() => { btn.disabled = false; btn.textContent = '1. Daten laden'; });
},
start: function() {
const btn = document.getElementById('wis-btn-start');
document.getElementById('wis-progress-container').style.display = 'block'; btn.disabled = true; this.currentIndex = 0; this.processBatch();
},
processBatch: function() {
if (this.currentIndex >= this.data.length) { alert('✅ Import abgeschlossen!'); window.location.href = '<?php echo admin_url('edit.php?post_type=wis_item'); ?>'; return; }
const batch = this.data.slice(this.currentIndex, this.currentIndex + this.batchSize);
const total = this.data.length;
const percent = Math.floor((this.currentIndex / total) * 100);
document.getElementById('wis-progress-bar').style.width = percent + '%';
document.getElementById('wis-percent').textContent = percent + '%';
document.getElementById('wis-progress-text').textContent = 'Verarbeite ' + this.currentIndex + ' / ' + total;
fetch('<?php echo rest_url('wis/v1/import_batch'); ?>', {
method: 'POST', headers: {'Content-Type':'application/json','X-WP-Nonce':'<?php echo wp_create_nonce('wp_rest'); ?>'},
body: JSON.stringify({items: batch})
}).then(r => r.json()).then(res => {
if(!res.success) { alert('Fehler im Batch: ' + res.message); return; }
this.currentIndex += res.processed || this.batchSize; this.processBatch();
}).catch(e => { alert('Kommunikationsfehler: ' + e.message); });
}
};
document.getElementById('wis-btn-fetch').addEventListener('click', () => WisImport.fetch());
document.getElementById('wis-btn-start').addEventListener('click', () => WisImport.start());
</script>
<?php
echo '</div>';
}
public static function page_top_spenders() {
global $wpdb;
$currency = get_option('wis_currency_name', 'Coins');
$results = $wpdb->get_results("
SELECT player_name, SUM(price) as total_spent, COUNT(*) as order_count
FROM {$wpdb->prefix}wis_orders
WHERE status = 'completed' OR status = 'cancelled'
GROUP BY player_name
ORDER BY total_spent DESC
LIMIT 50
");
echo '<div class="wrap"><h1>🏆 Top Spender</h1>';
echo '<p>Hier siehst du die Spieler, die insgesamt am meisten in deinem Shop ausgegeben haben.</p>';
echo '<table class="widefat fixed striped">';
echo '<thead><tr><th>Rang</th><th>Spieler</th><th>Ausgegeben (Total)</th><th>Anzahl Bestellungen</th></tr></thead><tbody>';
if(empty($results)) {
echo '<tr><td colspan="4" style="text-align:center;">Noch keine Statistiken vorhanden.</td></tr>';
} else {
$rank = 1;
foreach($results as $row) {
$total = intval($row->total_spent);
$count = intval($row->order_count);
echo '<tr>';
echo '<td>#' . esc_html($rank++) . '</td>';
echo '<td><strong>' . esc_html($row->player_name) . '</strong></td>';
echo '<td>' . esc_html($total) . ' ' . esc_html($currency) . '</td>';
echo '<td>' . esc_html($count) . '</td>';
echo '</tr>';
}
}
echo '</tbody></table>';
echo '</div>';
}
public static function page_orders() {
global $wpdb;
$currency = get_option('wis_currency_name', 'Coins');
if(isset($_GET['action']) && isset($_GET['order_id']) && isset($_GET['_wpnonce']) && wp_verify_nonce($_GET['_wpnonce'], 'wis_order_action')) {
$action = sanitize_text_field($_GET['action']);
$id = intval($_GET['order_id']);
if($action === 'delete') { $wpdb->delete($wpdb->prefix.'wis_orders', ['id' => $id]); echo '<div class="updated"><p>✓ Bestellung gelöscht.</p></div>'; }
elseif($action === 'complete') { $wpdb->update($wpdb->prefix.'wis_orders', ['status' => 'completed'], ['id' => $id]); echo '<div class="updated"><p>✓ Manuell erledigt.</p></div>'; }
}
if(isset($_GET['order_id'])) {
$order = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}wis_orders WHERE id = %d", intval($_GET['order_id'])));
if($order) {
$status_colors = ['pending' => '#ffc107', 'processing' => '#0073aa', 'completed' => 'green', 'cancelled' => 'red', 'failed' => 'red'];
$status_labels = ['pending' => 'Warte auf Ingame', 'processing' => 'In Bearbeitung', 'completed' => 'Erledigt', 'cancelled' => 'Abgebrochen', 'failed' => 'Fehler'];
$status_label = $status_labels[$order->status] ?? $order->status;
$status_color = $status_colors[$order->status] ?? 'red';
$items_list = '<em>Keine Daten</em>';
$coupon_row = '<em>Kein Gutschein</em>';
$json_data = $order->response;
$decoded = json_decode($json_data, true);
if(is_array($decoded)) {
if(isset($decoded['items']) && is_array($decoded['items'])) {
$items_list = '<ul style="margin:0; padding-left:20px;">';
foreach($decoded['items'] as $item) {
$amount = isset($item['amount']) ? intval($item['amount']) :1;
$id = isset($item['id']) ? esc_html($item['id']) : 'Unbekannt';
$items_list .= "<li><strong>{$amount}x</strong> {$id}</li>";
}
$items_list .= '</ul>';
if(isset($decoded['coupon']) && !empty($decoded['coupon']['code'])) {
$code = esc_html($decoded['coupon']['code']);
$disc = intval($decoded['coupon']['discount']);
$coupon_row = "<span style='color:green; font-weight:bold;'>{$code}</span> (-{$disc} {$currency})";
}
} elseif(!empty($decoded)) {
$items_list = '<ul style="margin:0; padding-left:20px;">';
foreach($decoded as $item) {
$amount = isset($item['amount']) ? intval($item['amount']) :1;
$id = isset($item['id']) ? esc_html($item['id']) : 'Unbekannt';
$items_list .= "<li><strong>{$amount}x</strong> {$id}</li>";
}
$items_list .= '</ul>';
}
}
echo '<div class="wrap"><h1>Bestellung #'.$order->id.' - Details</h1>';
echo '<a href="'.admin_url('admin.php?page=wis_orders').'" class="button">← Zurück</a><br><br>';
echo '<table class="widefat" style="max-width:800px;"><tbody>';
echo '<tr><th style="width:200px;">ID</th><td>'.$order->id.'</td></tr>';
echo '<tr><th>Datum</th><td>'.date('d.m.Y H:i', strtotime($order->created_at)).'</td></tr>';
echo '<tr><th>Spieler</th><td><strong>'.$order->player_name.'</strong></td></tr>';
echo '<tr><th>Server</th><td>'.$order->server.'</td></tr>';
echo '<tr><th>Zusammenfassung</th><td>'.esc_html($order->item_title).'</td></tr>';
echo '<tr><th>Gutschein</th><td>'.$coupon_row.'</td></tr>';
echo '<tr><th>Gekaufte Items</th><td>'.$items_list.'</td></tr>';
echo '<tr><th>Gesamtpreis</th><td>'.$order->price.' '.esc_html($currency).'</td></tr>';
echo '<tr><th>Status</th><td style="color:'.$status_color.';font-weight:bold;">'.esc_html($status_label).'</td></tr>';
echo '<tr><th>JSON (Debug)</th><td><code style="display:block; background:#eee; padding:5px; font-size:0.8em;">'.esc_html($json_data).'</code></td></tr>';
echo '</tbody></table>';
echo '</div>';
return;
}
}
$orders = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}wis_orders ORDER BY created_at DESC LIMIT 100");
echo '<div class="wrap"><h1>Bestellungen</h1>';
echo '<table class="widefat fixed striped"><thead><tr><th style="width:140px;">Datum</th><th style="width:150px;">Spieler</th><th>Inhalt</th><th style="width:100px;">Preis</th><th style="width:120px;">Status</th><th style="width:200px;">Aktion</th></tr></thead><tbody>';
if(empty($orders)) { echo '<tr><td colspan="7" style="text-align:center;padding:40px;">Noch keine Bestellungen vorhanden.</td></tr>'; }
foreach($orders as $o){
$status_map = ['pending' => 'Warte', 'processing' => 'Geben...', 'completed' => 'Fertig', 'cancelled' => 'Abgebrochen', 'failed' => 'Fehler'];
$status_label = $status_map[$o->status] ?? $o->status;
$status_colors = ['pending' => '#ffc107', 'processing' => '#0073aa', 'completed' => 'green', 'cancelled' => 'red', 'failed' => 'red'];
$status_color = $status_colors[$o->status] ?? 'red';
$delete_url = wp_nonce_url(admin_url('admin.php?page=wis_orders&action=delete&order_id='.$o->id), 'wis_order_action');
$details_url = admin_url('admin.php?page=wis_orders&order_id='.$o->id);
$item_label = strlen($o->item_title) > 50 ? substr($o->item_title, 0, 50).'...' : $o->item_title;
echo '<tr>';
echo '<td>'.esc_html(date('d.m.Y H:i', strtotime($o->created_at))).'</td>';
echo '<td><strong>'.esc_html($o->player_name).'</strong></td>';
echo '<td>'.esc_html($item_label).'</td>';
echo '<td>'.esc_html($o->price).' '.esc_html($currency).'</td>';
echo '<td style="color:'.$status_color.';font-weight:bold;">'.esc_html($status_label).'</td>';
echo '<td>';
echo '<a href="'.$details_url.'" class="button button-small">👁️ Details</a> ';
echo '<a href="'.$delete_url.'" class="button button-small" style="color:red;" onclick="return confirm(\'Wirklich löschen?\');">🗑️ Löschen</a>';
echo '</td>';
echo '</tr>';
}
echo '</tbody></table></div>';
}
}
// ===========================================================
// 4. API - AUTOMATION & CONFIRMATION
// ===========================================================
class WIS_API {
public static function register_routes() {
register_rest_route('wis/v1','/order', ['methods'=>'POST','callback'=>[self::class,'create_order'],'permission_callback'=> '__return_true']);
register_rest_route('wis/v1','/pending_orders', ['methods'=>'GET','callback'=>[self::class,'get_pending_orders'],'permission_callback'=> '__return_true']);
register_rest_route('wis/v1','/execute_order', ['methods'=>'POST','callback'=>[self::class,'execute_order'],'permission_callback'=> '__return_true']);
register_rest_route('wis/v1','/complete_order', ['methods'=>'POST','callback'=>[self::class,'complete_order'],'permission_callback'=> '__return_true']);
register_rest_route('wis/v1','/cancel_order', ['methods'=>'POST','callback'=>[self::class,'cancel_order'],'permission_callback'=> '__return_true']);
register_rest_route('wis/v1','/fetch_remote_data', ['methods'=>'POST','callback'=>[self::class,'fetch_remote_data'],'permission_callback'=> '__return_true']);
register_rest_route('wis/v1','/import_batch', ['methods'=>'POST','callback'=>[self::class,'import_batch'],'permission_callback'=> '__return_true']);
register_rest_route('wis/v1','/validate_coupon', ['methods'=>'POST','callback'=>[self::class,'validate_coupon'],'permission_callback'=> '__return_true']);
}
public static function create_order($request) {
global $wpdb;
$data = $request->get_json_params();
$player = sanitize_text_field($data['player'] ?? '');
$cart = $data['cart'] ?? [];
$server_slug = sanitize_text_field($data['server'] ?? '');
$coupon_code = isset($data['coupon_code']) ? sanitize_text_field(strtoupper($data['coupon_code'])) : '';
if(!$player || empty($cart) || !$server_slug) { return new WP_REST_Response(['success'=>false,'message'=>'Fehlende Eingabedaten'], 400); }
$exclude_offers_setting = get_option('wis_coupon_exclude_offers', '0');
$valid_cart = [];
$total_normal = 0;
$total_offer = 0;
foreach($cart as $item) {
$post_id = intval($item['id'] ?? 0);
$qty = intval($item['quantity'] ?? 1);
if($post_id <= 0 || $qty <= 0) continue;
$price = intval(get_post_meta($post_id, '_wis_price', true));
$item_servers = get_post_meta($post_id, '_wis_servers', true);
$is_offer = get_post_meta($post_id, '_wis_is_offer', true);
if(!is_array($item_servers)) $item_servers = [];
if($price <= 0) continue;
if(!in_array($server_slug, $item_servers)) continue;
$offer_price = intval(get_post_meta($post_id, '_wis_offer_price', true));
$display_price = ($offer_price > 0) ? $offer_price : $price;
$real_item_id = get_post_meta($post_id, '_wis_item_id', true);
$valid_cart[] = [
'id' => $post_id,
'title' => get_the_title($post_id),
'price' => $display_price,
'qty' => $qty,
'is_offer' => $is_offer,
'real_item_id' => $real_item_id
];
$item_total = $display_price * $qty;
if($is_offer && $exclude_offers_setting) {
$total_offer += $item_total;
} else {
$total_normal += $item_total;
}
}
if(empty($valid_cart)) return new WP_REST_Response(['success'=>false,'message'=>'Keine gültigen Items im Warenkorb.'], 400);
// Gutschein Logik
$coupon_discount = 0;
$coupon_msg = '';
$coupon_applied = false;
$currency = get_option('wis_currency_name', 'Coins');
if(!empty($coupon_code)) {
$coupon = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}wis_coupons WHERE code = %s", $coupon_code));
if($coupon) {
// Basis Checks
if($coupon->expiry && date('Y-m-d') > $coupon->expiry) {
$coupon_msg = 'Gutschein abgelaufen.';
} elseif($coupon->used_count >= $coupon->usage_limit) {
$coupon_msg = 'Gutschein wurde zu oft verwendet.';
} else {
// Prüfen ob Gutschein angewendet werden darf
$apply_coupon = false;
$calculate_discount = false;
if($exclude_offers_setting === '1') {
if($total_normal > 0) {
$apply_coupon = true;
}
} else {
if($total_normal > 0 || $total_offer > 0) {
$apply_coupon = true;
}
}
if($apply_coupon) {
$calculate_discount = true;
}
}
} else {
$coupon_msg = 'Ungültiger Gutscheincode.';
}
}
// Wenn angewendet, berechnen
if($calculate_discount) {
$coupon_type = $coupon->type;
if($coupon_type === 'percent') {
$coupon_discount = $total_normal * ($coupon->value / 100);
$coupon_msg = "Gutschein eingelöst: -{$coupon->value}% (-{$coupon_discount} {$currency})";
} else {
$coupon_discount = $coupon->value;
$coupon_msg = "Gutschein eingelöst: -{$coupon_discount} {$currency}";
}
// Aufrunden
$coupon_discount = floor($coupon_discount);
$wpdb->update($wpdb->prefix.'wis_coupons', ['used_count' => $coupon->used_count + 1], ['id' => $coupon->id]);
$coupon_applied = true;
}
$final_normal = max(0, $total_normal - $coupon_discount);
$final_price = $final_normal + $total_offer;
// Speichern in DB
$items_payload = [];
$title_parts = [];
foreach($valid_cart as $item) {
$items_payload[] = [
'id' => $item['real_item_id'],
'amount' => $item['qty']
];
$title_parts[] = $item['qty'] . 'x ' . $item['title'];
}
$readable_title = "Warenkorb: " . implode(', ', $title_parts);
if (strlen($readable_title) > 240) $readable_title = substr($readable_title, 0, 237) . '...';
$coupon_data = [];
if($coupon_applied) {
$coupon_data = [
'code' => $coupon_code,
'discount' => $coupon_discount
];
}
$json_payload = json_encode([
'items' => $items_payload,
'coupon' => $coupon_data
]);
$inserted = $wpdb->insert($wpdb->prefix.'wis_orders', [
'player_name' => $player,
'server' => $server_slug,
'item_id' => 'multi_item_cart',
'item_title' => $readable_title,
'price' => $final_price,
'quantity' => count($valid_cart),
'status' => 'pending',
'response' => $json_payload,
'created_at' => current_time('mysql')
]);
if($inserted) {
$msg = '✅ Bestellung erfolgreich erstellt!';
if($coupon_msg) $msg .= ' ('.$coupon_msg.')';
return new WP_REST_Response(['success'=>true,'message'=>$msg], 200);
} else {
return new WP_REST_Response(['success'=>false,'message'=>'Fehler beim Speichern.'], 500);
}
}
public static function validate_coupon($request) {
global $wpdb;
$data = $request->get_json_params();
$code = sanitize_text_field(strtoupper($data['code'] ?? ''));
$cart = $data['cart'] ?? [];
$currency = get_option('wis_currency_name', 'Coins');
if(!$code) {
return new WP_REST_Response(['success'=>false, 'message'=>'Kein Code angegeben']);
}
$coupon = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}wis_coupons WHERE code = %s", $code));
if(!$coupon) {
return new WP_REST_Response(['success'=>false, 'message'=>'Gutschein nicht gefunden']);
}
if($coupon->expiry && date('Y-m-d') > $coupon->expiry) {
return new WP_REST_Response(['success'=>false, 'message'=>'Gutschein abgelaufen']);
}
if($coupon->used_count >= $coupon->usage_limit) {
return new WP_REST_Response(['success'=>false, 'message'=>'Dieser Gutschein wurde bereits maximal eingelöst']);
}
$exclude_offers_setting = get_option('wis_coupon_exclude_offers', '0');
if($exclude_offers_setting === '1' && !empty($cart)) {
$has_normal_items = false;
foreach($cart as $item) {
$post_id = intval($item['id'] ?? 0);
$is_offer = get_post_meta($post_id, '_wis_is_offer', true);
if(empty($is_offer) || $is_offer == 0) {
$has_normal_items = true;
break;
}
}
if(!$has_normal_items) {
return new WP_REST_Response(['success'=>false, 'message'=>'⚠️ Dieser Gutschein gilt nicht für Angebote.']);
}
}
$message = '';
if($coupon->type === 'percent') {
$message = "Gutschein gültig (-{$coupon->value}%)";
} else {
$message = "Gutschein gültig (-{$coupon->value} {$currency})";
}
return new WP_REST_Response(['success'=>true, 'type'=>$coupon->type, 'value'=>$coupon->value, 'message'=>$message]);
}
public static function get_pending_orders($request) {
global $wpdb;
$player = sanitize_text_field($request->get_param('player'));
if(!$player) return new WP_REST_Response(['orders'=>[]]);
$results = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->prefix}wis_orders WHERE player_name = %s AND status = 'pending' ORDER BY created_at ASC LIMIT 10", $player));
return new WP_REST_Response(['orders' => $results]);
}
public static function execute_order($request) {
global $wpdb;
$data = $request->get_json_params();
$id = intval($data['id'] ?? 0);
if(!$id) return new WP_REST_Response(['success'=>false], 400);
$order = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}wis_orders WHERE id = %d", $id));
if($order) {
$wpdb->update($wpdb->prefix.'wis_orders', ['status' => 'processing'], ['id' => $id]);
return new WP_REST_Response(['success'=>true]);
}
return new WP_REST_Response(['success'=>false], 400);
}
public static function complete_order($request) {
global $wpdb;
$data = $request->get_json_params();
$id = intval($data['id'] ?? 0);
if(!$id) return new WP_REST_Response(['success'=>false], 400);
$wpdb->update($wpdb->prefix.'wis_orders', ['status' => 'completed'], ['id' => $id]);
return new WP_REST_Response(['success'=>true]);
}
public static function cancel_order($request) {
global $wpdb;
$data = $request->get_json_params();
$id = intval($data['id'] ?? 0);
if(!$id) return new WP_REST_Response(['success'=>false], 400);
$updated = $wpdb->update($wpdb->prefix.'wis_orders', ['status' => 'cancelled'], ['id' => $id]);
if($updated !== false) {
return new WP_REST_Response(['success'=>true]);
}
return new WP_REST_Response(['success'=>false], 400);
}
public static function fetch_remote_data($request) {
$url = esc_url_raw($request->get_json_params()['url']);
$response = wp_remote_get($url, ['timeout' => 30]);
if(is_wp_error($response)) return new WP_REST_Response(['success'=>false, 'message'=>$response->get_error_message()], 400);
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
$import_data = [];
if (isset($data['items']) && is_array($data['items'])) { $import_data = $data['items']; } elseif (is_array($data)) { $import_data = $data; }
if(empty($import_data)) return new WP_REST_Response(['success'=>false, 'message'=>'Keine gültigen Daten gefunden'], 400);
return new WP_REST_Response(['success'=>true, 'items' => $import_data]);
}
public static function import_batch($request) {
$items = $request->get_json_params()['items'] ?? [];
if(!is_array($items) || empty($items)) return new WP_REST_Response(['success'=>false], 400);
set_time_limit(300);
$processed = 0; $skipped = 0;
foreach($items as $item) {
$item_id_val = $item['id'] ?? ''; $title_val = $item['name'] ?? 'Unbekannt';
if(empty($item_id_val)) continue;
$exists = get_posts(['post_type' => 'wis_item', 'meta_key' => '_wis_item_id', 'meta_value' => $item_id_val, 'posts_per_page' => 1]);
if($exists) { $skipped++; continue; }
$new_post_id = wp_insert_post(['post_title' => $title_val, 'post_content' => '', 'post_type' => 'wis_item', 'post_status' => 'draft', 'meta_input' => ['_wis_item_id' => $item_id_val, '_wis_price' => 0]]);
if(!is_wp_error($new_post_id)) $processed++;
}
return new WP_REST_Response(['success'=>true, 'processed' => $processed, 'skipped' => $skipped]);
}
}
// ===========================================================
// 5. SHORTCODE
// ===========================================================
class WIS_Shortcode {
public static function register_shortcode() {
add_shortcode('ingame_shop_form', [self::class, 'render_form']);
}
public static function render_form() {
$servers = get_posts(['post_type'=>'wis_server','numberposts'=>-1]);
$items = get_posts(['post_type'=>'wis_item','numberposts'=>-1]);
$currency = get_option('wis_currency_name', 'Coins');
$img_base = get_option('wis_image_base_url', 'https://assets.minecraft-ids.com/1_21_10/');
$header_text = get_option('wis_header_text', '✅ Auto-Bilder | 💰 Sicherer Checkout');
$exclude_offers = get_option('wis_coupon_exclude_offers', '0');
// NEU: Kategorien
$categories = get_terms(['taxonomy'=>'wis_category', 'hide_empty'=>true]);
ob_start();
?>
<style>
.wis-pro-shop { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 1400px; margin: 40px auto; background: #f4f6f8; padding: 20px; border-radius: 10px; }
.wis-header { text-align: center; margin-bottom: 30px; }
.wis-header h2 { color: #333; margin-bottom: 10px; }
.wis-status { background: #d4edda; color: #155724; padding: 15px; border-radius: 8px; border-left: 4px solid #c3e6cb; margin-bottom: 20px; font-weight: 500; }
.wis-control-bar { display: flex; flex-wrap: wrap; gap: 15px; margin-bottom: 30px; align-items: center; justify-content: space-between; background: #fff; padding: 15px; border-radius: 8px; border: 1px solid #ddd; }
.wis-search-input { padding: 10px 15px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; width: 100%; max-width: 300px; }
.wis-offer-filter label { cursor: pointer; display: flex; align-items: center; gap: 5px; font-weight: bold; color: #e67e22; }
.wis-filter-select { padding: 12px 20px; font-size: 16px; border: 2px solid #ddd; border-radius: 8px; background: white; cursor: pointer; min-width: 200px; }
.wis-cart-btn { padding: 12px 30px; background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; border: none; border-radius: 8px; font-weight: bold; font-size: 1rem; cursor: pointer; transition: all 0.3s; position: relative; display: flex; align-items: center; gap: 10px; }
.wis-cart-btn:hover { transform: scale(1.05); box-shadow: 0 5px 15px rgba(40, 167, 69, 0.4); }
.wis-cart-badge { position: absolute; top: -8px; right: -8px; background: #dc3545; color: white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; font-size: 0.8rem; font-weight: bold; }
.wis-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; }
.wis-card { background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.05); transition: transform 0.2s, box-shadow 0.2s; display: flex; flex-direction: column; border: 1px solid #eee; position: relative; }
.wis-card.offer { border: 2px solid #ffc107; }
.wis-card:hover { transform: translateY(-5px); box-shadow: 0 10px 20px rgba(0,0,0,0.1); border-color: #0073aa; }
.wis-card-img { width: 100%; height: 180px; background: #222; display: flex; align-items: center; justify-content: center; position: relative; }
.wis-card-img img { max-width: 80%; max-height: 80%; object-fit: contain; filter: drop-shadow(0 4px 4px rgba(0,0,0,0.5)); transition: transform 0.3s ease; }
.wis-card:hover .wis-card-img img { transform: scale(1.15) rotate(3deg); }
.wis-offer-badge { position: absolute; top: 10px; left: 10px; background: linear-gradient(135deg, #ff416c, #ff4b2b); color: white; padding: 4px 10px; border-radius: 20px; font-size: 0.75rem; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.2); z-index: 2; }
.wis-daily-badge { position: absolute; top: 10px; left: 10px; background: linear-gradient(135deg, #6f42c1, #8e44ad); color: white; padding: 4px 10px; border-radius: 20px; font-size: 0.75rem; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.2); z-index: 2; border: 1px solid #fff; }
.wis-card-body { padding: 15px; flex-grow: 1; display: flex; flex-direction: column; }
.wis-card-title { font-size: 1.1rem; font-weight: 700; margin: 0 0 10px 0; color: #333; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wis-card-desc { font-size: 0.85rem; color: #666; margin-bottom: 10px; line-height: 1.4; height: 40px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.wis-card-price-container { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px; }
.wis-card-price { font-size: 1.2rem; color: #e67e22; font-weight: 800; }
.wis-card-price-old { font-size: 0.9rem; color: #999; text-decoration: line-through; font-style: italic; }
.wis-card-servers { font-size: 0.85rem; color: #666; margin-bottom: 15px; line-height: 1.4; }
.wis-hint { font-size: 0.75rem; color: #dc3545; margin-top: 5px; font-weight: bold; }
.wis-quantity-control { display: flex; align-items: center; gap: 10px; margin-bottom: 15px; }
.wis-quantity-btn { width: 35px; height: 35px; border: 2px solid #667eea; background: white; color: #667eea; border-radius: 6px; font-weight: bold; cursor: pointer; transition: all 0.2s; }
.wis-quantity-btn:hover { background: #667eea; color: white; }
.wis-quantity-input { width: 60px; text-align: center; border: 2px solid #ddd; border-radius: 6px; padding: 8px; font-weight: bold; }
.wis-btn-add { margin-top: auto; width: 100%; padding: 12px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 6px; font-weight: bold; font-size: 0.9rem; cursor: pointer; transition: all 0.3s; }
.wis-btn-add:hover { transform: scale(1.05); box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); }
.wis-coupon-input-group { display: flex; gap: 10px; margin-bottom: 20px; }
.wis-coupon-input { flex-grow: 1; padding: 10px; border: 1px solid #ddd; border-radius: 6px; }
.wis-coupon-btn { padding: 10px 15px; background: #6f42c1; color: white; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; }
.wis-coupon-msg { font-size: 0.85rem; color: #e67e22; margin-top: 5px; min-height: 20px; }
.wis-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 9999; display: none; align-items: center; justify-content: center; backdrop-filter: blur(3px); }
.wis-modal { background: white; width: 90%; max-width: 600px; max-height: 80vh; padding: 30px; border-radius: 15px; box-shadow: 0 15px 30px rgba(0,0,0,0.3); animation: popIn 0.3s ease; overflow-y: auto; }
@keyframes popIn { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.wis-modal h2 { margin-top: 0; color: #333; text-align: center; }
.wis-cart-empty { text-align: center; padding: 40px; color: #999; }
.wis-cart-item { display: flex; justify-content: space-between; align-items: center; padding: 15px; border-bottom: 1px solid #eee; }
.wis-cart-item-info { flex-grow: 1; }
.wis-cart-item-title { font-weight: bold; color: #333; margin-bottom: 5px; }
.wis-cart-item-price { color: #e67e22; font-weight: 600; }
.wis-cart-item-remove { background: #dc3545; color: white; border: none; padding: 8px 15px; border-radius: 6px; cursor: pointer; font-weight: bold; transition: all 0.2s; }
.wis-cart-item-remove:hover { background: #c82333; }
.wis-cart-total { border-top: 2px solid #333; padding: 20px 0; margin-top: 15px; }
.wis-cart-total-row { display: flex; justify-content: space-between; font-size: 1.3rem; font-weight: bold; color: #333; }
.wis-modal-input { width: 100%; padding: 15px; margin: 20px 0; border: 2px solid #ddd; border-radius: 8px; font-size: 1.1rem; box-sizing: border-box; }
.wis-modal-input:focus { border-color: #667eea; outline: none; }
.wis-modal-actions { display: flex; gap: 10px; }
.wis-modal-btn { flex: 1; padding: 12px; border: none; border-radius: 8px; font-weight: bold; cursor: pointer; font-size: 1rem; transition: all 0.3s; }
.wis-btn-confirm { background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; }
.wis-btn-confirm:hover { transform: scale(1.05); }
.wis-btn-cancel { background: #6c757d; color: white; }
.wis-btn-cancel:hover { background: #5a6268; }
.wis-alert { margin-top: 15px; padding: 12px; border-radius: 5px; display: none; }
.wis-alert.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.wis-alert.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
/* NEU: Kategorien Tabs */
.wis-cat-tabs { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 20px; justify-content: center; }
.wis-cat-btn { padding: 8px 16px; border: 1px solid #ddd; background: #fff; border-radius: 20px; cursor: pointer; transition: all 0.2s; }
.wis-cat-btn:hover, .wis-cat-btn.active { background: #667eea; color: white; border-color: #667eea; }
.wis-hidden { display: none; }
</style>
<div class="wis-pro-shop">
<div class="wis-header">
<h2>🛒 Ingame Shop Pro</h2>
<?php if (!empty(trim($header_text))) : ?>
<div class="wis-status"><?=esc_html($header_text)?></div>
<?php endif; ?>
</div>
<div class="wis-control-bar">
<div style="flex-grow: 1;">
<input type="text" id="wis-search" class="wis-search-input" placeholder="🔍 Suche Item...">
</div>
<div class="wis-offer-filter">
<label><input type="checkbox" id="wis-offer-filter"> Nur Angebote</label>
</div>
<select id="server-filter" class="wis-filter-select">
<option value="">Alle Server</option>
<?php foreach($servers as $s): ?>
<option value="<?=esc_attr($s->post_name)?>"><?=esc_html($s->post_title)?></option>
<?php endforeach; ?>
</select>
<button class="wis-cart-btn" onclick="openCart()"> 🛒 Warenkorb <span class="wis-cart-badge" id="cart-count">0</span></button>
</div>
<!-- NEU: Kategorien Tabs -->
<div class="wis-cat-tabs" id="wis-cat-container">
<button class="wis-cat-btn active" onclick="filterCategory(0)">Alle</button>
<?php foreach($categories as $cat): ?>
<button class="wis-cat-btn" onclick="filterCategory(<?=esc_attr($cat->term_id)?>)"><?=esc_html($cat->name)?></button>
<?php endforeach; ?>
</div>
<div class="wis-grid" id="shop-grid">
<?php if(empty($items)){ ?>
<div style="grid-column: 1 / -1; text-align: center; padding: 20px; background: #fff; border-radius: 10px;">Keine Items im Shop verfügbar.</div>
<?php } ?>
<?php foreach($items as $item):
$item_servers = get_post_meta($item->ID, '_wis_servers', true);
if(!is_array($item_servers)) $item_servers = [];
$price = get_post_meta($item->ID, '_wis_price', true);
$item_id_code = get_post_meta($item->ID, '_wis_item_id', true);
$item_desc = $item->post_content;
$is_offer = get_post_meta($item->ID, '_wis_is_offer', true);
$is_daily_deal = get_post_meta($item->ID, '_wis_daily_deal', true);
$offer_price = get_post_meta($item->ID, '_wis_offer_price', true);
$display_price = ($offer_price > 0) ? $offer_price : $price;
$show_old_price = ($offer_price > 0 && $offer_price != $price);
// NEU: Kategorien
$item_cats = wp_get_post_terms($item->ID, 'wis_category');
$cat_ids = array_map(function($t) { return $t->term_id; }, $item_cats);
$cat_data = json_encode($cat_ids);
$img_name = str_replace(':', '_', $item_id_code) . '.png';
$full_img_url = $img_base . $img_name;
$server_names = [];
foreach($item_servers as $slug) {
$srv = get_page_by_path($slug, OBJECT, 'wis_server');
if($srv) $server_names[] = $srv->post_title;
}
$servers_display = !empty($server_names) ? implode(', ', $server_names) : 'Kein Server';
$servers_data = json_encode($item_servers);
?>
<div class="wis-card <?=esc_attr($is_offer?'offer':'')?>" data-cats='<?=esc_attr($cat_data)?>' data-servers='<?=esc_attr($servers_data)?>' data-title="<?=esc_attr(strtolower($item->post_title))?>" data-offer="<?=esc_attr($is_offer?'1':'0')?>">
<!-- NEU: Badges Logic -->
<?php if($is_daily_deal): ?><div class="wis-daily-badge">🎁 Angebot des Tages</div>
<?php elseif($is_offer): ?><div class="wis-offer-badge">🔥 Angebot</div><?php endif; ?>
<div class="wis-card-img">
<img src="<?=esc_url($full_img_url)?>" alt="<?=esc_attr($item->post_title)?>" loading="lazy" onerror="this.onerror=null; this.src='https://via.placeholder.com/128/333/fff?text=?';">
</div>
<div class="wis-card-body">
<h3 class="wis-card-title" title="<?=esc_attr($item->post_title)?>"><?=esc_html($item->post_title)?></h3>
<div class="wis-card-price-container">
<?php if($show_old_price): ?><div class="wis-card-price-old"><?=esc_html($price)?> <?=esc_html($currency)?></div><?php endif; ?>
<div class="wis-card-price"><?=esc_html($display_price)?> <?=esc_html($currency)?></div>
</div>
<div class="wis-card-servers">📡 <?=esc_html($servers_display)?></div>
<?php if($is_offer && $exclude_offers): ?>
<div class="wis-hint">⚠️ Gutschein ungültig</div>
<?php endif; ?>
<div class="wis-quantity-control">
<button class="wis-quantity-btn" onclick="changeQuantity(this, -1)">-</button>
<input type="number" class="wis-quantity-input" value="1" min="1" max="999" onchange="validateQuantity(this)">
<button class="wis-quantity-btn" onclick="changeQuantity(this, 1)">+</button>
</div>
<button class="wis-btn-add" onclick="addToCart(<?=esc_attr($item->ID)?>, '<?=esc_js($item->post_title)?>', <?=esc_attr($display_price)?>, this, <?=esc_attr($is_offer?1:0)?>)">
In den Warenkorb
</button>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- Warenkorb Modal -->
<div class="wis-modal-overlay" id="cart-modal">
<div class="wis-modal">
<h2>🛒 Dein Warenkorb</h2>
<div id="cart-content">
<div class="wis-cart-empty">Dein Warenkorb ist leer</div>
</div>
<div id="cart-checkout" style="display:none;">
<div style="margin-bottom: 20px; background: #f8f9fa; padding: 15px; border-radius: 8px; border: 1px solid #e9ecef;">
<label style="display:block; font-weight:bold; margin-bottom: 5px;">🎫 Gutscheincode (Optional)</label>
<div class="wis-coupon-input-group">
<input type="text" id="coupon-code-input" class="wis-coupon-input" placeholder="CODE EINGEBEN">
<button type="button" class="wis-coupon-btn" onclick="validateCoupon()">Einlösen</button>
</div>
<div id="coupon-message" class="wis-coupon-msg"></div>
</div>
<div class="wis-cart-total">
<div class="wis-cart-total-row">
<span>Gesamt:</span>
<span id="cart-total">0 <?=esc_html($currency)?></span>
</div>
</div>
<select id="checkout-server" class="wis-modal-input" style="margin-top:20px;">
<option value="">-- Server wählen --</option>
<?php foreach($servers as $s): ?>
<option value="<?=esc_attr($s->post_name)?>"><?=esc_html($s->post_title)?></option>
<?php endforeach; ?>
</select>
<input type="text" class="wis-modal-input" id="checkout-playername" placeholder="Dein exakter Spielername">
<div class="wis-modal-actions">
<button class="wis-modal-btn wis-btn-confirm" onclick="checkout()">💰 Kauf abschließen</button>
<button class="wis-modal-btn wis-btn-cancel" onclick="closeCart()">Abbrechen</button>
</div>
<div id="cart-alert" class="wis-alert"></div>
</div>
</div>
</div>
<script>
const shopCurrency = "<?=esc_js($currency)?>";
const shopExcludeOffers = "<?=esc_js($exclude_offers)?>" === "1";
let cart = [];
let couponData = {}; // Speichert {type, value}
let activeCategory = 0; // 0 = Alle
function changeQuantity(btn, delta) {
const input = btn.parentElement.querySelector('.wis-quantity-input');
let val = parseInt(input.value) ||1;
val = Math.max(1, Math.min(999, val + delta));
input.value = val;
}
function validateQuantity(input) {
let val = parseInt(input.value) ||1;
input.value = Math.max(1, Math.min(999, val));
}
function filterCategory(catId) {
activeCategory = catId;
document.querySelectorAll('.wis-cat-btn').forEach(btn => {
btn.classList.remove('active');
});
const btn = event.target;
btn.classList.add('active');
updateGrid();
}
function updateGrid() {
const term = document.getElementById('wis-search').value.toLowerCase();
const isOffersOnly = document.getElementById('wis-offer-filter').checked;
const serverFilter = document.getElementById('server-filter').value.toLowerCase();
const searchTerm = document.getElementById('wis-search').value.toLowerCase();
document.querySelectorAll('.wis-card').forEach(card => {
const cats = JSON.parse(card.dataset.cats || '[]');
const isOffer = card.getAttribute('data-offer') === '1';
const servers = JSON.parse(card.dataset.servers || '[]');
const title = card.getAttribute('data-title').toLowerCase();
let visible = true;
if(term && !title.includes(term)) visible = false;
if(isOffersOnly && !isOffer) visible = false;
if(activeCategory > 0 && !cats.includes(activeCategory)) visible = false;
if(serverFilter && !servers.includes(serverFilter)) visible = false;
card.style.display = visible ? 'flex' : 'none';
});
}
document.getElementById('wis-search').addEventListener('input', updateGrid);
document.getElementById('wis-offer-filter').addEventListener('change', updateGrid);
document.getElementById('server-filter').addEventListener('change', updateGrid);
function addToCart(itemId, itemTitle, price, btn, isOffer) {
const card = btn.closest('.wis-card');
const quantity = parseInt(card.querySelector('.wis-quantity-input').value) || 1;
const existing = cart.find(i => i.id === itemId);
if (existing) {
existing.quantity += quantity;
} else {
const serversData = JSON.parse(card.dataset.servers || '[]');
const catsData = JSON.parse(card.dataset.cats || '[]');
cart.push({
id: itemId,
title: itemTitle,
price: price,
quantity: quantity,
servers: serversData,
cats: catsData,
is_offer: !!isOffer
});
}
updateCartBadge();
btn.innerHTML = '✅ Hinzugefügt';
btn.style.background = '#28a745';
setTimeout(() => {
btn.innerHTML = ' In den Warenkorb';
btn.style.background = '';
}, 1500);
card.querySelector('.wis-quantity-input').value = 1;
}
function updateCartBadge() {
const totalItems = cart.reduce((sum, item) => sum + item.quantity, 0);
document.getElementById('cart-count').textContent = totalItems;
}
function openCart() {
renderCart();
document.getElementById('cart-modal').style.display = 'flex';
}
function closeCart() {
document.getElementById('cart-modal').style.display = 'none';
document.getElementById('cart-alert').style.display = 'none';
}
function calculateTotal() {
let normalSum = 0;
let offerSum = 0;
cart.forEach(item => {
const itemTotal = item.price * item.quantity;
if(shopExcludeOffers && item.is_offer) {
offerSum += itemTotal;
} else {
normalSum += itemTotal;
}
});
// FIX: Prüfen ob Gutschein existiert, um NaN zu vermeiden
let discount = 0;
if (couponData && typeof couponData.value !== 'undefined') {
if(couponData.type === 'percent') {
discount = normalSum * (couponData.value / 100);
} else {
discount = couponData.value;
}
}
let discountedNormal = Math.max(0, normalSum - discount);
discountedNormal = Math.floor(discountedNormal);
return discountedNormal + offerSum;
}
function renderCart() {
const content = document.getElementById('cart-content');
const checkout = document.getElementById('cart-checkout');
if (cart.length === 0) {
content.innerHTML = '<div class="wis-cart-empty">Dein Warenkorb ist leer</div>';
checkout.style.display = 'none';
return;
}
let html = '';
cart.forEach((item, index) => {
const itemTotal = item.price * item.quantity;
html += `
<div class="wis-cart-item">
<div class="wis-cart-item-info">
<div class="wis-cart-item-title">${escapeHtml(item.title)}</div>
<div class="wis-cart-item-price">${item.quantity}x × ${item.price} = ${itemTotal} ${shopCurrency}</div>
</div>
<button class="wis-cart-item-remove" onclick="removeFromCart(${index})">🗑️</button>
</div>
`;
});
content.innerHTML = html;
let finalTotal = calculateTotal();
document.getElementById('cart-total').textContent = finalTotal + ' ' + shopCurrency;
checkout.style.display = 'block';
}
function removeFromCart(index) {
cart.splice(index, 1);
updateCartBadge();
renderCart();
}
function validateCoupon() {
const codeInput = document.getElementById('coupon-code-input');
const msgBox = document.getElementById('coupon-message');
const code = codeInput.value.trim().toUpperCase();
// FIX: Wenn Feld leer ist, Gutschein löschen und Preiss neu berechnen
if(!code) {
couponData = {};
msgBox.textContent = '';
renderCart();
return;
}
msgBox.textContent = 'Prüfe...';
msgBox.style.color = '#0073aa';
msgBox.style.fontWeight = 'bold';
fetch('<?php echo rest_url('wis/v1/validate_coupon'); ?>', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({code: code, cart: cart})
})
.then(r => {
if(!r.ok) throw new Error('Fehler: ' + r.statusText);
return r.json();
})
.then(data => {
if(data.success) {
couponData = { type: data.type, value: data.value };
msgBox.textContent = '✅ ' + data.message;
msgBox.style.color = 'green';
msgBox.style.fontWeight = 'bold';
renderCart();
} else {
// FIX: Bei Fehlern, Gutschein Daten leeren, damit Preis stimmt
couponData = {};
msgBox.textContent = '❌ ' + data.message;
msgBox.style.color = 'red';
renderCart();
}
})
.catch(e => {
console.error(e);
// FIX: Auch bei Netzwerkfehler leeren
couponData = {};
msgBox.textContent = 'Fehler beim Prüfen (Server?)';
msgBox.style.color = 'red';
renderCart();
});
}
function checkout() {
const playerName = document.getElementById('checkout-playername').value.trim();
const server = document.getElementById('checkout-server').value;
const codeInput = document.getElementById('coupon-code-input').value.trim();
if (!playerName) { showCartAlert('Bitte Spielername eingeben!', 'error'); return; }
if (!server) { showCartAlert('Bitte Server auswählen!', 'error'); return; }
const invalidItems = cart.filter(item => !item.servers.includes(server));
if (invalidItems.length > 0) { showCartAlert('Einige Items sind nicht für den gewählten Server verfügbar!', 'error'); return; }
const btn = document.querySelector('.wis-btn-confirm');
btn.innerHTML = '⏳ Speichere...';
btn.disabled = true;
fetch('<?php echo rest_url('wis/v1/order')?>', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({
player: playerName,
cart: cart,
server: server,
coupon_code: codeInput
})
})
.then(r => r.json())
.then(data => {
if (data.success) {
showCartAlert(data.message, 'success');
cart = []; couponData = {}; document.getElementById('coupon-code-input').value = ''; document.getElementById('coupon-message').textContent = '';
updateCartBadge();
setTimeout(() => { closeCart(); location.reload(); },2000);
} else {
showCartAlert(data.message, 'error');
}
})
.catch(e => { showCartAlert('Netzwerkfehler: ' + e.message, 'error'); })
.finally(() => { btn.innerHTML = '💰 Kauf abschließen'; btn.disabled = false; });
}
function showCartAlert(msg, type) {
const alert = document.getElementById('cart-alert');
alert.textContent = msg;
alert.className = 'wis-alert ' + type;
alert.style.display = 'block';
}
function escapeHtml(text) {
const map = {'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;'};
return text.replace(/[&<>"']/g, m => map[m]);
}
document.getElementById('cart-modal').addEventListener('click', function(e) { if (e.target === this) closeCart(); });
</script>
<?php
return ob_get_clean();
}
}
// ===========================================================
// INIT + Bulk Delete Hooks + Cron
// ===========================================================
register_activation_hook(__FILE__, [WIS_Activator::class, 'activate']);
register_deactivation_hook(__FILE__, [WIS_Activator::class, 'deactivate']);
add_action('init', [WIS_CPT::class, 'register_post_types']);
add_action('add_meta_boxes', [WIS_CPT::class, 'add_meta_boxes']);
add_action('save_post', [WIS_CPT::class, 'save_meta']);
add_action('save_post', [WIS_CPT::class, 'auto_update_status'], 10);
add_action('delete_post', [WIS_CPT::class, 'delete_coupon_from_table']);
add_action('admin_menu', [WIS_Admin::class, 'register_menu']);
add_action('rest_api_init', [WIS_API::class, 'register_routes']);
add_action('init', [WIS_Shortcode::class, 'register_shortcode']);
// NEU: Cron Hook
add_action('wis_daily_deal_event', [WIS_Activator::class, 'run_daily_deal']);
// Bulk Delete aktivieren
add_filter('bulk_actions-edit-wis_item', [WIS_Bulk_Actions::class, 'register_bulk_actions']);
add_filter('bulk_actions-edit-wis_coupon', [WIS_Bulk_Actions::class, 'register_bulk_actions']);
add_filter('bulk_actions-edit-wis_server', [WIS_Bulk_Actions::class, 'register_bulk_actions']);
add_filter('handle_bulk_actions-edit-wis_item', [WIS_Bulk_Actions::class, 'handle_bulk_actions'], 10, 3);
add_filter('handle_bulk_actions-edit-wis_coupon', [WIS_Bulk_Actions::class, 'handle_bulk_actions'], 10, 3);
add_filter('handle_bulk_actions-edit-wis_server', [WIS_Bulk_Actions::class, 'handle_bulk_actions'], 10, 3);
add_action('admin_notices', [WIS_Bulk_Actions::class, 'admin_notices']);
?>