Files
WP-Ingame-Shop-Pro/wp-ingame-shop/wp-ingame-shop.php

2473 lines
118 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
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
Plugin URI: https://git.viper.ipv64.net/M_Viper/WP-Ingame-Shop-Pro
Description: Vollautomatischer Shop mit Warenkorb + eigener DB-Struktur (SPIGOT COMPATIBLE)
Version: 2.1.0
Author: M_Viper
Author URI: https://m-viper.de
Requires at least: 6.9.1
Tested up to: 6.9.1
Requires PHP: 7.4
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: WP-Ingame-Shop-Pro
Tags: shop, items, minecraft, coupons, deals, categories
*/
if (!defined('ABSPATH')) exit;
// Plugin Constants
define('WIS_VERSION', '2.1.0');
define('WIS_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WIS_PLUGIN_URL', plugin_dir_url(__FILE__));
// ===========================================================
// ACTIVATION & DATABASE
// ===========================================================
class WIS_Activator {
public static function activate() {
self::create_tables();
self::set_default_options();
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;
$charset_collate = $wpdb->get_charset_collate();
$tables = [];
// Items
$tables[] = "CREATE TABLE {$wpdb->prefix}wis_items (
id mediumint(9) NOT NULL AUTO_INCREMENT,
item_id varchar(100) NOT NULL,
name varchar(255) NOT NULL,
description text,
price int(11) DEFAULT 0,
offer_price int(11) DEFAULT 0,
is_offer tinyint(1) DEFAULT 0,
is_daily_deal tinyint(1) DEFAULT 0,
servers text,
categories text,
status varchar(20) DEFAULT 'draft',
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY item_id (item_id),
KEY status (status)
) $charset_collate;";
// Orders
$tables[] = "CREATE TABLE {$wpdb->prefix}wis_orders (
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,
PRIMARY KEY (id),
KEY player_name (player_name),
KEY status (status)
) $charset_collate;";
// Coupons
$tables[] = "CREATE TABLE {$wpdb->prefix}wis_coupons (
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,
PRIMARY KEY (id),
UNIQUE KEY code (code)
) $charset_collate;";
// Servers
$tables[] = "CREATE TABLE {$wpdb->prefix}wis_servers (
id mediumint(9) NOT NULL AUTO_INCREMENT,
slug varchar(100) NOT NULL,
name varchar(255) NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY slug (slug)
) $charset_collate;";
// Categories
$tables[] = "CREATE TABLE {$wpdb->prefix}wis_categories (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
slug varchar(100) NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY slug (slug)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
foreach ($tables as $sql) {
dbDelta($sql);
}
}
private static function set_default_options() {
$defaults = [
'wis_currency_name' => 'Coins',
'wis_image_base_url' => 'https://git.viper.ipv64.net/M_Viper/minecraft-items/raw/branch/main/images/',
'wis_header_text' => '✅ Auto-Bilder | 💰 Sicherer Checkout | 🎮 Ingame-Bestätigung',
'wis_coupon_exclude_offers' => '0',
'wis_daily_deal_enabled' => '0',
'wis_daily_deal_discount' => '20'
];
foreach ($defaults as $key => $value) {
if (get_option($key) === false) {
add_option($key, $value);
}
}
}
public static function run_daily_deal() {
if (get_option('wis_daily_deal_enabled') !== '1') return;
global $wpdb;
$table = $wpdb->prefix . 'wis_items';
$discount = intval(get_option('wis_daily_deal_discount', 20));
// Reset old daily deal
$wpdb->update($table, ['is_daily_deal' => 0], ['is_daily_deal' => 1]);
// Select random item
$item = $wpdb->get_row("SELECT * FROM $table WHERE status = 'publish' AND price > 0 ORDER BY RAND() LIMIT 1");
if ($item) {
// Syntax Error Fix here: removed extra parent
$offer_price = max(0, floor($item->price - ($item->price * ($discount / 100))));
$wpdb->update($table, [
'is_daily_deal' => 1,
'is_offer' => 1,
'offer_price' => $offer_price
], ['id' => $item->id]);
}
}
public static function reset_shop() {
global $wpdb;
$tables = [
'wis_items', 'wis_orders', 'wis_coupons',
'wis_servers', 'wis_categories'
];
foreach ($tables as $table) {
$wpdb->query("TRUNCATE TABLE {$wpdb->prefix}$table");
}
self::set_default_options();
return true;
}
}
// ===========================================================
// DATABASE HELPER
// ===========================================================
class WIS_DB {
public static function get_items($args = []) {
global $wpdb;
$table = $wpdb->prefix . 'wis_items';
$where = isset($args['status']) ? $wpdb->prepare("WHERE status = %s", $args['status']) : "WHERE 1=1";
$limit = isset($args['limit']) ? "LIMIT " . intval($args['limit']) : "";
$orderby = isset($args['orderby']) ? "ORDER BY " . sanitize_text_field($args['orderby']) : "ORDER BY name ASC";
return $wpdb->get_results("SELECT * FROM $table $where $orderby $limit");
}
public static function get_item($id) {
global $wpdb;
return $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wis_items WHERE id = %d",
$id
));
}
public static function get_item_by_item_id($item_id) {
global $wpdb;
return $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wis_items WHERE item_id = %s",
$item_id
));
}
public static function insert_item($data) {
global $wpdb;
return $wpdb->insert($wpdb->prefix . 'wis_items', $data);
}
public static function update_item($id, $data) {
global $wpdb;
return $wpdb->update($wpdb->prefix . 'wis_items', $data, ['id' => $id]);
}
public static function delete_item($id) {
global $wpdb;
return $wpdb->delete($wpdb->prefix . 'wis_items', ['id' => $id]);
}
// Servers
public static function get_servers() {
global $wpdb;
return $wpdb->get_results("SELECT * FROM {$wpdb->prefix}wis_servers ORDER BY name ASC");
}
public static function insert_server($slug, $name) {
global $wpdb;
return $wpdb->insert($wpdb->prefix . 'wis_servers', [
'slug' => sanitize_title($slug),
'name' => sanitize_text_field($name)
]);
}
public static function delete_server($id) {
global $wpdb;
return $wpdb->delete($wpdb->prefix . 'wis_servers', ['id' => $id]);
}
// Categories
public static function get_categories() {
global $wpdb;
return $wpdb->get_results("SELECT * FROM {$wpdb->prefix}wis_categories ORDER BY name ASC");
}
public static function insert_category($name) {
global $wpdb;
return $wpdb->insert($wpdb->prefix . 'wis_categories', [
'name' => sanitize_text_field($name),
'slug' => sanitize_title($name)
]);
}
public static function delete_category($id) {
global $wpdb;
return $wpdb->delete($wpdb->prefix . 'wis_categories', ['id' => $id]);
}
// Coupons
public static function get_coupons() {
global $wpdb;
return $wpdb->get_results("SELECT * FROM {$wpdb->prefix}wis_coupons ORDER BY created_at DESC");
}
public static function get_coupon_by_code($code) {
global $wpdb;
return $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wis_coupons WHERE code = %s",
strtoupper($code)
));
}
public static function insert_coupon($data) {
global $wpdb;
$data['code'] = strtoupper($data['code']);
return $wpdb->insert($wpdb->prefix . 'wis_coupons', $data);
}
public static function update_coupon($id, $data) {
global $wpdb;
if (isset($data['code'])) {
$data['code'] = strtoupper($data['code']);
}
return $wpdb->update($wpdb->prefix . 'wis_coupons', $data, ['id' => $id]);
}
public static function delete_coupon($id) {
global $wpdb;
return $wpdb->delete($wpdb->prefix . 'wis_coupons', ['id' => $id]);
}
// Orders
public static function get_orders($limit = 100) {
global $wpdb;
return $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wis_orders ORDER BY created_at DESC LIMIT %d",
$limit
));
}
public static function get_order($id) {
global $wpdb;
return $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wis_orders WHERE id = %d",
$id
));
}
public static function insert_order($data) {
global $wpdb;
return $wpdb->insert($wpdb->prefix . 'wis_orders', $data);
}
public static function update_order_status($id, $status) {
global $wpdb;
return $wpdb->update(
$wpdb->prefix . 'wis_orders',
['status' => $status],
['id' => $id]
);
}
public static function delete_order($id) {
global $wpdb;
return $wpdb->delete($wpdb->prefix . 'wis_orders', ['id' => $id]);
}
}
// ===========================================================
// ADMIN PAGES
// ===========================================================
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', 'Einstellungen', 'Einstellungen', 'manage_options', 'wis_shop');
add_submenu_page('wis_shop', 'Items', 'Items', 'manage_options', 'wis_items', [self::class, 'page_items']);
add_submenu_page('wis_shop', 'Bestellungen', 'Bestellungen', 'manage_options', 'wis_orders', [self::class, 'page_orders']);
add_submenu_page('wis_shop', 'Server', 'Server', 'manage_options', 'wis_servers', [self::class, 'page_servers']);
add_submenu_page('wis_shop', 'Kategorien', 'Kategorien', 'manage_options', 'wis_categories', [self::class, 'page_categories']);
add_submenu_page('wis_shop', 'Gutscheine', 'Gutscheine', 'manage_options', 'wis_coupons', [self::class, 'page_coupons']);
add_submenu_page('wis_shop', 'JSON Export/Import', 'JSON Tools', 'manage_options', 'wis_json', [self::class, 'page_json']);
add_submenu_page('wis_shop', 'Shop Reset', '🔄 Reset', 'manage_options', 'wis_reset', [self::class, 'page_reset']);
add_submenu_page('wis_shop', 'Top Spender', 'Top Spender', 'manage_options', 'wis_top_spenders', [self::class, 'page_top_spenders']);
}
public static function page_overview() {
// Handle settings save
if (isset($_POST['wis_save_settings']) && check_admin_referer('wis_settings')) {
update_option('wis_currency_name', sanitize_text_field($_POST['wis_currency_name']));
update_option('wis_image_base_url', esc_url_raw($_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');
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>';
}
global $wpdb;
$stats = [
'items' => $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}wis_items"),
'orders' => $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}wis_orders"),
'servers' => $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}wis_servers"),
'coupons' => $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}wis_coupons")
];
?>
<div class="wrap">
<h1>🛒 WP Ingame Shop Pro <span style="color:#28a745;">v<?php echo WIS_VERSION; ?></span></h1>
<div class="card" style="max-width:800px; margin-top:20px;">
<h2>📊 Statistiken</h2>
<table class="widefat">
<tr><th>Items im Shop:</th><td><strong><?php echo $stats['items']; ?></strong></td></tr>
<tr><th>Bestellungen:</th><td><strong><?php echo $stats['orders']; ?></strong></td></tr>
<tr><th>Server:</th><td><strong><?php echo $stats['servers']; ?></strong></td></tr>
<tr><th>Gutscheine:</th><td><strong><?php echo $stats['coupons']; ?></strong></td></tr>
</table>
</div>
<div class="card" style="max-width:800px; margin-top:20px; padding:20px;">
<h2>⚙️ Einstellungen</h2>
<form method="post">
<?php wp_nonce_field('wis_settings'); ?>
<table class="form-table">
<tr>
<th><label for="header_text">Shop Header Text</label></th>
<td>
<textarea id="header_text" name="wis_header_text" class="large-text" rows="2"><?php echo esc_textarea(get_option('wis_header_text')); ?></textarea>
</td>
</tr>
<tr>
<th><label for="currency">Währungsname</label></th>
<td>
<input type="text" id="currency" name="wis_currency_name" value="<?php echo esc_attr(get_option('wis_currency_name')); ?>" class="regular-text">
</td>
</tr>
<tr>
<th><label for="image_url">Bilder Basis-URL</label></th>
<td>
<input type="text" id="image_url" name="wis_image_base_url" value="<?php echo esc_attr(get_option('wis_image_base_url')); ?>" class="large-text code">
<p class="description">Muss mit / enden! Z.B.: https://git.viper.ipv64.net/.../images/</p>
</td>
</tr>
<tr>
<th>Gutscheine bei Angeboten</th>
<td>
<label>
<input type="checkbox" name="wis_coupon_exclude_offers" value="1" <?php checked(get_option('wis_coupon_exclude_offers'), '1'); ?>>
Gutscheine NICHT auf Angebote anwenden
</label>
</td>
</tr>
<tr>
<th>Daily Deal</th>
<td>
<label>
<input type="checkbox" name="wis_daily_deal_enabled" value="1" <?php checked(get_option('wis_daily_deal_enabled'), '1'); ?>>
Automatisches Tagesangebot aktivieren
</label>
</td>
</tr>
<tr>
<th><label for="discount">Daily Deal Rabatt (%)</label></th>
<td>
<input type="number" id="discount" name="wis_daily_deal_discount" value="<?php echo esc_attr(get_option('wis_daily_deal_discount', 20)); ?>" min="1" max="99">
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="wis_save_settings" class="button button-primary" value="Einstellungen speichern">
</p>
</form>
</div>
</div>
<?php
}
public static function page_items() {
// ===========================================================
// HANDLE BULK ACTIONS (NEU)
// ===========================================================
// 1. Bulk Save: Die eigentliche Änderung in der Datenbank
if (isset($_POST['wis_bulk_save']) && check_admin_referer('wis_bulk_edit')) {
$ids = isset($_POST['item_ids']) ? array_map('intval', $_POST['item_ids']) : [];
$action = sanitize_text_field($_POST['bulk_action_type']);
if (!empty($ids) && !empty($action)) {
$count = 0;
foreach ($ids as $id) {
$data = [];
switch ($action) {
case 'price':
$new_price = intval($_POST['bulk_price']);
if ($new_price >= 0) {
$data['price'] = $new_price;
}
break;
case 'offer':
$is_offer = isset($_POST['bulk_is_offer']) ? 1 : 0;
$data['is_offer'] = $is_offer;
if (!$is_offer) {
$data['offer_price'] = 0;
}
break;
case 'status':
$new_status = sanitize_text_field($_POST['bulk_status']);
if (in_array($new_status, ['publish', 'draft'])) {
$data['status'] = $new_status;
}
break;
case 'server':
$servers = isset($_POST['bulk_servers']) ? $_POST['bulk_servers'] : [];
$data['servers'] = json_encode($servers);
break;
case 'category':
$categories = isset($_POST['bulk_categories']) ? $_POST['bulk_categories'] : [];
$data['categories'] = json_encode($categories);
break;
}
if (!empty($data)) {
WIS_DB::update_item($id, $data);
$count++;
}
}
echo '<div class="updated"><p>✅ ' . $count . ' Items erfolgreich aktualisiert!</p></div>';
}
}
// 2. Bulk Apply: Zeigt das Formular für die gewählte Aktion an
if (isset($_POST['wis_bulk_apply']) && !empty($_POST['wis_bulk_action']) && isset($_POST['item_ids'])) {
$action = sanitize_text_field($_POST['wis_bulk_action']);
$selected_ids = array_map('intval', $_POST['item_ids']);
// Daten für Dropdowns laden
$all_servers = WIS_DB::get_servers();
$all_categories = WIS_DB::get_categories();
?>
<div class="wrap">
<h1>📦 Mehrfachbearbeitung</h1>
<p><strong>Aktion:</strong> <?php echo esc_html(ucfirst($action)); ?> für <?php echo count($selected_ids); ?> Items.</p>
<div class="card" style="max-width:800px; padding:20px; margin-top:20px;">
<form method="post">
<?php wp_nonce_field('wis_bulk_edit'); ?>
<input type="hidden" name="bulk_action_type" value="<?php echo esc_attr($action); ?>">
<!-- IDs als Hidden Fields mitschicken -->
<?php foreach ($selected_ids as $id): ?>
<input type="hidden" name="item_ids[]" value="<?php echo $id; ?>">
<?php endforeach; ?>
<table class="form-table">
<?php if ($action === 'price'): ?>
<tr>
<th><label>Neuer Preis</label></th>
<td>
<input type="number" name="bulk_price" class="regular-text" min="0" required>
</td>
</tr>
<?php elseif ($action === 'offer'): ?>
<tr>
<th><label>Als Angebot markieren?</label></th>
<td>
<label>
<input type="checkbox" name="bulk_is_offer" value="1">
Ja, als Angebot aktivieren
</label>
</td>
</tr>
<?php elseif ($action === 'status'): ?>
<tr>
<th><label>Neuer Status</label></th>
<td>
<select name="bulk_status">
<option value="publish">Aktivieren (Publish)</option>
<option value="draft">Deaktivieren (Draft)</option>
</select>
</td>
</tr>
<?php elseif ($action === 'server'): ?>
<tr>
<th><label>Server zuweisen</label></th>
<td>
<?php if (empty($all_servers)): ?>
<p>Keine Server vorhanden.</p>
<?php else: ?>
<?php foreach ($all_servers as $s): ?>
<label style="display:block; margin-bottom:5px;">
<input type="checkbox" name="bulk_servers[]" value="<?php echo esc_attr($s->slug); ?>">
<?php echo esc_html($s->name); ?>
</label>
<?php endforeach; ?>
<p class="description">Hake die Server an, denen die Items zugewiesen werden sollen (überschreibt aktuelle Zuweisung).</p>
<?php endif; ?>
</td>
</tr>
<?php elseif ($action === 'category'): ?>
<tr>
<th><label>Kategorie zuweisen</label></th>
<td>
<?php if (empty($all_categories)): ?>
<p>Keine Kategorien vorhanden.</p>
<?php else: ?>
<?php foreach ($all_categories as $c): ?>
<label style="display:block; margin-bottom:5px;">
<input type="checkbox" name="bulk_categories[]" value="<?php echo esc_attr($c->slug); ?>">
<?php echo esc_html($c->name); ?>
</label>
<?php endforeach; ?>
<p class="description">Hake die Kategorien an (überschreibt aktuelle Zuweisung).</p>
<?php endif; ?>
</td>
</tr>
<?php endif; ?>
</table>
<p class="submit">
<a href="<?php echo admin_url('admin.php?page=wis_items'); ?>" class="button">Abbrechen</a>
<input type="submit" name="wis_bulk_save" class="button button-primary" value="Jetzt ändern">
</p>
</form>
</div>
</div>
<?php
return; // WICHTIG: Hier stoppen, damit die normale Liste nicht geladen wird
}
// ===========================================================
// BESTEHENDER CODE (Single Actions & Edit Form)
// ===========================================================
// Handle actions
if (isset($_GET['action'], $_GET['id'], $_GET['_wpnonce'])) {
if (!wp_verify_nonce($_GET['_wpnonce'], 'wis_item_action')) {
wp_die('Security check failed');
}
$id = intval($_GET['id']);
if ($_GET['action'] === 'delete') {
WIS_DB::delete_item($id);
echo '<div class="updated"><p>✅ Item gelöscht!</p></div>';
} elseif ($_GET['action'] === 'toggle_status') {
$item = WIS_DB::get_item($id);
$new_status = ($item->status === 'publish') ? 'draft' : 'publish';
WIS_DB::update_item($id, ['status' => $new_status]);
echo '<div class="updated"><p>✅ Status geändert!</p></div>';
}
}
// Handle edit/add
if (isset($_POST['wis_save_item'])) {
check_admin_referer('wis_item_form');
$data = [
'item_id' => sanitize_text_field($_POST['item_id']),
'name' => sanitize_text_field($_POST['name']),
'description' => sanitize_textarea_field($_POST['description']),
'price' => intval($_POST['price']),
'offer_price' => intval($_POST['offer_price']),
'is_offer' => isset($_POST['is_offer']) ? 1 : 0,
'servers' => isset($_POST['servers']) ? json_encode($_POST['servers']) : '[]',
'categories' => isset($_POST['categories']) ? json_encode($_POST['categories']) : '[]',
'status' => intval($_POST['price']) > 0 ? 'publish' : 'draft'
];
if (isset($_GET['edit'])) {
WIS_DB::update_item(intval($_GET['edit']), $data);
echo '<div class="updated"><p>✅ Item gespeichert!</p></div>';
} else {
WIS_DB::insert_item($data);
echo '<div class="updated"><p>✅ Item erstellt!</p></div>';
}
}
// Show form if edit or add
if (isset($_GET['edit']) || isset($_GET['add'])) {
$item = isset($_GET['edit']) ? WIS_DB::get_item(intval($_GET['edit'])) : null;
$servers = WIS_DB::get_servers();
$categories = WIS_DB::get_categories();
$currency = get_option('wis_currency_name', 'Coins');
$item_servers = $item ? json_decode($item->servers, true) : [];
$item_cats = $item ? json_decode($item->categories, true) : [];
?>
<div class="wrap">
<h1><?php echo $item ? 'Item bearbeiten' : 'Neues Item'; ?></h1>
<a href="<?php echo admin_url('admin.php?page=wis_items'); ?>" class="button">← Zurück zur Liste</a>
<form method="post" style="max-width:800px; margin-top:20px;">
<?php wp_nonce_field('wis_item_form'); ?>
<table class="form-table">
<tr>
<th><label for="name">Name *</label></th>
<td><input type="text" id="name" name="name" value="<?php echo $item ? esc_attr($item->name) : ''; ?>" class="regular-text" required></td>
</tr>
<tr>
<th><label for="item_id">Item ID *</label></th>
<td>
<input type="text" id="item_id" name="item_id" value="<?php echo $item ? esc_attr($item->item_id) : ''; ?>" class="regular-text" placeholder="minecraft:diamond" required>
<p class="description">Z.B.: minecraft:diamond</p>
</td>
</tr>
<tr>
<th><label for="description">Beschreibung</label></th>
<td><textarea id="description" name="description" rows="3" class="large-text"><?php echo $item ? esc_textarea($item->description) : ''; ?></textarea></td>
</tr>
<tr>
<th><label for="price">Preis (<?php echo esc_html($currency); ?>) *</label></th>
<td><input type="number" id="price" name="price" value="<?php echo $item ? esc_attr($item->price) : '0'; ?>" min="0" required></td>
</tr>
<tr>
<th><label for="offer_price">Angebotspreis</label></th>
<td>
<input type="number" id="offer_price" name="offer_price" value="<?php echo $item ? esc_attr($item->offer_price) : '0'; ?>" min="0">
<p class="description">Optional: Wenn gesetzt, wird der normale Preis durchgestrichen</p>
</td>
</tr>
<tr>
<th>Markierungen</th>
<td>
<label>
<input type="checkbox" name="is_offer" value="1" <?php echo ($item && $item->is_offer) ? 'checked' : ''; ?>>
Als Angebot markieren
</label>
</td>
</tr>
<tr>
<th>Server</th>
<td>
<?php if (empty($servers)): ?>
<p>Keine Server vorhanden. <a href="<?php echo admin_url('admin.php?page=wis_servers'); ?>">Server erstellen</a></p>
<?php else: ?>
<?php foreach ($servers as $server): ?>
<label style="display:block; margin:5px 0;">
<input type="checkbox" name="servers[]" value="<?php echo esc_attr($server->slug); ?>" <?php echo in_array($server->slug, $item_servers ?: []) ? 'checked' : ''; ?>>
<?php echo esc_html($server->name); ?>
</label>
<?php endforeach; ?>
<?php endif; ?>
</td>
</tr>
<tr>
<th>Kategorien</th>
<td>
<?php if (empty($categories)): ?>
<p>Keine Kategorien vorhanden. <a href="<?php echo admin_url('admin.php?page=wis_categories'); ?>">Kategorien erstellen</a></p>
<?php else: ?>
<?php foreach ($categories as $cat): ?>
<label style="display:block; margin:5px 0;">
<input type="checkbox" name="categories[]" value="<?php echo esc_attr($cat->slug); ?>" <?php echo in_array($cat->slug, $item_cats ?: []) ? 'checked' : ''; ?>>
<?php echo esc_html($cat->name); ?>
</label>
<?php endforeach; ?>
<?php endif; ?>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="wis_save_item" class="button button-primary" value="Speichern">
</p>
</form>
</div>
<?php
return;
}
// ===========================================================
// LISTE ANZEIGEN (MIT BULK ACTIONS)
// ===========================================================
$items = WIS_DB::get_items();
$currency = get_option('wis_currency_name', 'Coins');
?>
<div class="wrap">
<h1>Items <a href="<?php echo admin_url('admin.php?page=wis_items&add=1'); ?>" class="page-title-action">Neu erstellen</a></h1>
<!-- Bulk Actions Form Start -->
<form method="post">
<!-- Toolbar -->
<div class="tablenav top">
<div class="alignleft actions bulkactions">
<label for="bulk-action-selector-top" class="screen-reader-text">Massenaktionen wählen</label>
<select name="wis_bulk_action" id="bulk-action-selector-top">
<option value="">Massenaktionen</option>
<option value="server">Server zuweisen</option>
<option value="category">Kategorie zuweisen</option>
<option value="price">Preis ändern</option>
<option value="offer">Angebot ändern</option>
<option value="status">Aktivieren/Deaktivieren</option>
</select>
<input type="submit" name="wis_bulk_apply" class="button action" value="Anwenden">
</div>
<br class="clear">
</div>
<table class="widefat fixed striped">
<thead>
<tr>
<th style="width:40px;" class="manage-column column-cb check-column">
<input type="checkbox" id="cb-select-all-1">
</th>
<th>ID</th>
<th>Name</th>
<th>Item ID</th>
<th>Preis</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<?php if (empty($items)): ?>
<tr><td colspan="7" style="text-align:center; padding:40px;">Noch keine Items vorhanden.</td></tr>
<?php else: ?>
<?php foreach ($items as $item): ?>
<tr>
<th class="check-column">
<input type="checkbox" name="item_ids[]" id="cb-select-<?php echo $item->id; ?>" value="<?php echo $item->id; ?>">
</th>
<td><?php echo esc_html($item->id); ?></td>
<td><strong><?php echo esc_html($item->name); ?></strong></td>
<td><code><?php echo esc_html($item->item_id); ?></code></td>
<td><?php echo esc_html($item->price); ?> <?php echo esc_html($currency); ?></td>
<td>
<?php if ($item->status === 'publish'): ?>
<span style="color:green;">✅ Aktiv</span>
<?php else: ?>
<span style="color:orange;">📝 Entwurf</span>
<?php endif; ?>
</td>
<td>
<a href="<?php echo wp_nonce_url(admin_url('admin.php?page=wis_items&edit=' . $item->id), 'wis_item_action', '_wpnonce'); ?>" class="button button-small">Bearbeiten</a>
<a href="<?php echo wp_nonce_url(admin_url('admin.php?page=wis_items&action=delete&id=' . $item->id), 'wis_item_action', '_wpnonce'); ?>" class="button button-small" onclick="return confirm('Wirklich löschen?');" style="color:red;">Löschen</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</form>
<!-- Bulk Actions Form End -->
</div>
<script>
// Einfaches Skript für "Alle auswählen" Checkbox
(function() {
const mainCb = document.getElementById('cb-select-all-1');
if(mainCb) {
mainCb.addEventListener('change', function(e) {
const cbs = document.querySelectorAll('input[name="item_ids[]"]');
cbs.forEach(cb => cb.checked = e.target.checked);
});
}
})();
</script>
<?php
}
public static function page_servers() {
// Handle add
if (isset($_POST['wis_add_server'])) {
check_admin_referer('wis_server_form');
WIS_DB::insert_server($_POST['slug'], $_POST['name']);
echo '<div class="updated"><p>✅ Server erstellt!</p></div>';
}
// Handle delete
if (isset($_GET['action'], $_GET['id']) && $_GET['action'] === 'delete') {
check_admin_referer('wis_server_action', '_wpnonce');
WIS_DB::delete_server(intval($_GET['id']));
echo '<div class="updated"><p>✅ Server gelöscht!</p></div>';
}
$servers = WIS_DB::get_servers();
?>
<div class="wrap">
<h1>Server</h1>
<div class="card" style="max-width:600px; padding:20px; margin-top:20px;">
<h2>Neuen Server erstellen</h2>
<form method="post">
<?php wp_nonce_field('wis_server_form'); ?>
<table class="form-table">
<tr>
<th><label for="name">Name *</label></th>
<td><input type="text" id="name" name="name" class="regular-text" required></td>
</tr>
<tr>
<th><label for="slug">Slug *</label></th>
<td>
<input type="text" id="slug" name="slug" class="regular-text" placeholder="survival" required>
<p class="description">Kleinbuchstaben ohne Leerzeichen</p>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="wis_add_server" class="button button-primary" value="Server erstellen">
</p>
</form>
</div>
<h2 style="margin-top:40px;">Vorhandene Server</h2>
<table class="widefat fixed striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Slug</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<?php if (empty($servers)): ?>
<tr><td colspan="4" style="text-align:center; padding:40px;">Noch keine Server vorhanden.</td></tr>
<?php else: ?>
<?php foreach ($servers as $server): ?>
<tr>
<td><?php echo esc_html($server->id); ?></td>
<td><strong><?php echo esc_html($server->name); ?></strong></td>
<td><code><?php echo esc_html($server->slug); ?></code></td>
<td>
<a href="<?php echo wp_nonce_url(admin_url('admin.php?page=wis_servers&action=delete&id=' . $server->id), 'wis_server_action', '_wpnonce'); ?>" class="button button-small" onclick="return confirm('Wirklich löschen?');" style="color:red;">Löschen</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php
}
public static function page_categories() {
// Handle add
if (isset($_POST['wis_add_category'])) {
check_admin_referer('wis_category_form');
WIS_DB::insert_category($_POST['name']);
echo '<div class="updated"><p>✅ Kategorie erstellt!</p></div>';
}
// Handle delete
if (isset($_GET['action'], $_GET['id']) && $_GET['action'] === 'delete') {
check_admin_referer('wis_category_action', '_wpnonce');
WIS_DB::delete_category(intval($_GET['id']));
echo '<div class="updated"><p>✅ Kategorie gelöscht!</p></div>';
}
$categories = WIS_DB::get_categories();
?>
<div class="wrap">
<h1>Kategorien</h1>
<div class="card" style="max-width:600px; padding:20px; margin-top:20px;">
<h2>Neue Kategorie erstellen</h2>
<form method="post">
<?php wp_nonce_field('wis_category_form'); ?>
<table class="form-table">
<tr>
<th><label for="name">Name *</label></th>
<td><input type="text" id="name" name="name" class="regular-text" required></td>
</tr>
</table>
<p class="submit">
<input type="submit" name="wis_add_category" class="button button-primary" value="Kategorie erstellen">
</p>
</form>
</div>
<h2 style="margin-top:40px;">Vorhandene Kategorien</h2>
<table class="widefat fixed striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Slug</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<?php if (empty($categories)): ?>
<tr><td colspan="4" style="text-align:center; padding:40px;">Noch keine Kategorien vorhanden.</td></tr>
<?php else: ?>
<?php foreach ($categories as $cat): ?>
<tr>
<td><?php echo esc_html($cat->id); ?></td>
<td><strong><?php echo esc_html($cat->name); ?></strong></td>
<td><code><?php echo esc_html($cat->slug); ?></code></td>
<td>
<a href="<?php echo wp_nonce_url(admin_url('admin.php?page=wis_categories&action=delete&id=' . $cat->id), 'wis_category_action', '_wpnonce'); ?>" class="button button-small" onclick="return confirm('Wirklich löschen?');" style="color:red;">Löschen</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php
}
public static function page_coupons() {
// Handle add/edit
if (isset($_POST['wis_save_coupon'])) {
check_admin_referer('wis_coupon_form');
$data = [
'code' => sanitize_text_field($_POST['code']),
'value' => intval($_POST['value']),
'type' => sanitize_text_field($_POST['type']),
'usage_limit' => intval($_POST['usage_limit']),
'expiry' => !empty($_POST['expiry']) ? sanitize_text_field($_POST['expiry']) : null
];
if (isset($_GET['edit'])) {
unset($data['used_count']); // Don't reset usage count
WIS_DB::update_coupon(intval($_GET['edit']), $data);
echo '<div class="updated"><p>✅ Gutschein gespeichert!</p></div>';
} else {
$data['used_count'] = 0;
WIS_DB::insert_coupon($data);
echo '<div class="updated"><p>✅ Gutschein erstellt!</p></div>';
}
}
// Handle delete
if (isset($_GET['action'], $_GET['id']) && $_GET['action'] === 'delete') {
check_admin_referer('wis_coupon_action', '_wpnonce');
WIS_DB::delete_coupon(intval($_GET['id']));
echo '<div class="updated"><p>✅ Gutschein gelöscht!</p></div>';
}
// Show form
if (isset($_GET['add']) || isset($_GET['edit'])) {
global $wpdb;
$coupon = isset($_GET['edit']) ? $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}wis_coupons WHERE id = %d", intval($_GET['edit']))) : null;
$currency = get_option('wis_currency_name', 'Coins');
?>
<div class="wrap">
<h1><?php echo $coupon ? 'Gutschein bearbeiten' : 'Neuer Gutschein'; ?></h1>
<a href="<?php echo admin_url('admin.php?page=wis_coupons'); ?>" class="button">← Zurück zur Liste</a>
<form method="post" style="max-width:600px; margin-top:20px;">
<?php wp_nonce_field('wis_coupon_form'); ?>
<table class="form-table">
<tr>
<th><label for="code">Code *</label></th>
<td>
<input type="text" id="code" name="code" value="<?php echo $coupon ? esc_attr($coupon->code) : ''; ?>" class="regular-text" style="text-transform:uppercase;" required>
<p class="description">Z.B.: SUMMER20</p>
</td>
</tr>
<tr>
<th><label for="type">Typ *</label></th>
<td>
<select id="type" name="type" required>
<option value="fixed" <?php echo ($coupon && $coupon->type === 'fixed') ? 'selected' : ''; ?>>Festbetrag (z.B. 100 <?php echo esc_html($currency); ?> Rabatt)</option>
<option value="percent" <?php echo ($coupon && $coupon->type === 'percent') ? 'selected' : ''; ?>>Prozentual (z.B. 20% Rabatt)</option>
</select>
</td>
</tr>
<tr>
<th><label for="value">Wert *</label></th>
<td>
<input type="number" id="value" name="value" value="<?php echo $coupon ? esc_attr($coupon->value) : ''; ?>" min="1" required>
<p class="description">Bei Festbetrag: Betrag in <?php echo esc_html($currency); ?>. Bei Prozent: Zahl ohne %</p>
</td>
</tr>
<tr>
<th><label for="usage_limit">Nutzungslimit *</label></th>
<td>
<input type="number" id="usage_limit" name="usage_limit" value="<?php echo $coupon ? esc_attr($coupon->usage_limit) : '1'; ?>" min="1" required>
</td>
</tr>
<tr>
<th><label for="expiry">Ablaufdatum</label></th>
<td>
<input type="date" id="expiry" name="expiry" value="<?php echo $coupon && $coupon->expiry ? esc_attr($coupon->expiry) : ''; ?>">
<p class="description">Optional</p>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="wis_save_coupon" class="button button-primary" value="Speichern">
</p>
</form>
</div>
<?php
return;
}
// List coupons
$coupons = WIS_DB::get_coupons();
$currency = get_option('wis_currency_name', 'Coins');
?>
<div class="wrap">
<h1>Gutscheine <a href="<?php echo admin_url('admin.php?page=wis_coupons&add=1'); ?>" class="page-title-action">Neu erstellen</a></h1>
<table class="widefat fixed striped">
<thead>
<tr>
<th>Code</th>
<th>Rabatt</th>
<th>Genutzt</th>
<th>Gültig bis</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<?php if (empty($coupons)): ?>
<tr><td colspan="5" style="text-align:center; padding:40px;">Noch keine Gutscheine vorhanden.</td></tr>
<?php else: ?>
<?php foreach ($coupons as $coupon): ?>
<tr>
<td><strong><?php echo esc_html($coupon->code); ?></strong></td>
<td>
<?php if ($coupon->type === 'percent'): ?>
<?php echo esc_html($coupon->value); ?>%
<?php else: ?>
<?php echo esc_html($coupon->value); ?> <?php echo esc_html($currency); ?>
<?php endif; ?>
</td>
<td><?php echo esc_html($coupon->used_count); ?> / <?php echo esc_html($coupon->usage_limit); ?></td>
<td><?php echo $coupon->expiry ? esc_html(date('d.m.Y', strtotime($coupon->expiry))) : '∞'; ?></td>
<td>
<a href="<?php echo admin_url('admin.php?page=wis_coupons&edit=' . $coupon->id); ?>" class="button button-small">Bearbeiten</a>
<a href="<?php echo wp_nonce_url(admin_url('admin.php?page=wis_coupons&action=delete&id=' . $coupon->id), 'wis_coupon_action', '_wpnonce'); ?>" class="button button-small" onclick="return confirm('Wirklich löschen?');" style="color:red;">Löschen</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php
}
public static function page_orders() {
// Handle delete
if (isset($_GET['action'], $_GET['id']) && $_GET['action'] === 'delete') {
check_admin_referer('wis_order_action', '_wpnonce');
WIS_DB::delete_order(intval($_GET['id']));
echo '<div class="updated"><p>✅ Bestellung gelöscht!</p></div>';
}
// Handle status change
if (isset($_GET['action'], $_GET['id']) && $_GET['action'] === 'complete') {
check_admin_referer('wis_order_action', '_wpnonce');
WIS_DB::update_order_status(intval($_GET['id']), 'completed');
echo '<div class="updated"><p>✅ Status geändert!</p></div>';
}
// Show details
if (isset($_GET['view'])) {
$order = WIS_DB::get_order(intval($_GET['view']));
if (!$order) {
echo '<div class="error"><p>Bestellung nicht gefunden.</p></div>';
return;
}
$currency = get_option('wis_currency_name', 'Coins');
$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'
];
$decoded = json_decode($order->response, true);
?>
<div class="wrap">
<h1>Bestellung #<?php echo $order->id; ?> - Details</h1>
<a href="<?php echo admin_url('admin.php?page=wis_orders'); ?>" class="button">← Zurück</a>
<table class="widefat" style="max-width:800px; margin-top:20px;">
<tr><th>ID</th><td><?php echo esc_html($order->id); ?></td></tr>
<tr><th>Datum</th><td><?php echo esc_html(date('d.m.Y H:i', strtotime($order->created_at))); ?></td></tr>
<tr><th>Spieler</th><td><strong><?php echo esc_html($order->player_name); ?></strong></td></tr>
<tr><th>Server</th><td><?php echo esc_html($order->server); ?></td></tr>
<tr><th>Zusammenfassung</th><td><?php echo esc_html($order->item_title); ?></td></tr>
<tr><th>Preis</th><td><?php echo esc_html($order->price); ?> <?php echo esc_html($currency); ?></td></tr>
<tr><th>Status</th><td style="color:<?php echo $status_colors[$order->status] ?? 'black'; ?>; font-weight:bold;"><?php echo $status_labels[$order->status] ?? $order->status; ?></td></tr>
<tr>
<th>Details (JSON)</th>
<td><code style="display:block; background:#eee; padding:10px; font-size:11px; overflow-x:auto;"><?php echo esc_html($order->response); ?></code></td>
</tr>
</table>
</div>
<?php
return;
}
// List orders
$orders = WIS_DB::get_orders();
$currency = get_option('wis_currency_name', 'Coins');
?>
<div class="wrap">
<h1>Bestellungen</h1>
<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;">Aktionen</th>
</tr>
</thead>
<tbody>
<?php if (empty($orders)): ?>
<tr><td colspan="6" style="text-align:center; padding:40px;">Noch keine Bestellungen vorhanden.</td></tr>
<?php else: ?>
<?php foreach ($orders as $order): ?>
<?php
$status_map = [
'pending' => 'Warte',
'processing' => 'Geben...',
'completed' => 'Fertig',
'cancelled' => 'Abgebrochen',
'failed' => 'Fehler'
];
$status_colors = [
'pending' => '#ffc107',
'processing' => '#0073aa',
'completed' => 'green',
'cancelled' => 'red',
'failed' => 'red'
];
?>
<tr>
<td><?php echo esc_html(date('d.m.Y H:i', strtotime($order->created_at))); ?></td>
<td><strong><?php echo esc_html($order->player_name); ?></strong></td>
<td><?php echo esc_html(substr($order->item_title, 0, 50)) . (strlen($order->item_title) > 50 ? '...' : ''); ?></td>
<td><?php echo esc_html($order->price); ?> <?php echo esc_html($currency); ?></td>
<td style="color:<?php echo $status_colors[$order->status] ?? 'black'; ?>; font-weight:bold;"><?php echo $status_map[$order->status] ?? $order->status; ?></td>
<td>
<a href="<?php echo admin_url('admin.php?page=wis_orders&view=' . $order->id); ?>" class="button button-small">👁️ Details</a>
<a href="<?php echo wp_nonce_url(admin_url('admin.php?page=wis_orders&action=delete&id=' . $order->id), 'wis_order_action', '_wpnonce'); ?>" class="button button-small" onclick="return confirm('Wirklich löschen?');" style="color:red;">🗑️ Löschen</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php
}
public static function page_json() {
// Handle export
if (isset($_POST['wis_generate_json'])) {
check_admin_referer('wis_json_export');
$items = WIS_DB::get_items(['status' => 'publish']);
$img_base = get_option('wis_image_base_url', '');
$json_data = ['items' => []];
foreach ($items as $item) {
$img_name = str_replace(':', '_', $item->item_id) . '.png';
$json_data['items'][] = [
'id' => $item->item_id,
'name' => $item->name,
'description' => $item->description,
'price' => intval($item->price),
'image' => $img_base . $img_name
];
}
$json_output = json_encode($json_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo '<div class="wrap"><h1>📦 JSON Export</h1>';
echo '<div class="updated"><p>✅ JSON erfolgreich generiert!</p></div>';
echo '<textarea style="width:100%; height:400px; font-family:monospace; font-size:12px;">'.esc_textarea($json_output).'</textarea>';
echo '<p><button onclick="downloadJSON()" class="button button-primary">💾 Als items.json herunterladen</button></p>';
echo '<script>
function downloadJSON() {
const text = document.querySelector("textarea").value;
const blob = new Blob([text], {type: "application/json"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "items.json";
a.click();
}
</script>';
echo '<h3>📤 Nächste Schritte:</h3>';
echo '<ol>';
echo '<li>Lade die JSON-Datei herunter</li>';
echo '<li>Gehe zu deinem Gitea Repository</li>';
echo '<li>Lade die <code>items.json</code> hoch unter: <code>https://git.viper.ipv64.net/M_Viper/WP-Ingame-Shop-Pro</code></li>';
echo '<li>Klicke dann auf den <strong>Quick-Import</strong> Button unten!</li>';
echo '</ol>';
echo '</div>';
return;
}
// Default Gitea URL
$default_url = 'https://git.viper.ipv64.net/M_Viper/WP-Ingame-Shop-Pro/raw/branch/main/items.json';
?>
<div class="wrap">
<h1>📦 JSON Export/Import</h1>
<div class="card" style="max-width:800px; padding:20px; margin-top:20px;">
<h2>📤 JSON Export</h2>
<p>Generiere eine JSON-Datei mit allen deinen Items für Gitea.</p>
<form method="post">
<?php wp_nonce_field('wis_json_export'); ?>
<p class="submit">
<input type="submit" name="wis_generate_json" class="button button-primary button-large" value="📦 JSON Generieren">
</p>
</form>
</div>
<div class="card" style="max-width:800px; padding:20px; margin-top:20px; background:#e8f5e9;">
<h2>⚡ Quick-Import von Gitea</h2>
<p><strong>Importiert direkt von deinem Gitea Repository!</strong></p>
<p style="margin:10px 0; padding:10px; background:#fff; border-left:4px solid #28a745; font-family:monospace; font-size:12px; word-break:break-all;">
<?php echo esc_html($default_url); ?>
</p>
<button type="button" id="btn-quick-import" class="button button-primary button-large" style="background:#28a745; border-color:#28a745;">
⚡ Quick-Import starten
</button>
<div id="quick-import-result" style="margin-top:15px;"></div>
</div>
<div class="card" style="max-width:800px; padding:20px; margin-top:20px;">
<h2>📥 JSON Import (Manuelle URL)</h2>
<p>Importiere Items aus einer beliebigen JSON-URL.</p>
<div id="wis-import-form">
<input type="text" id="import-url" class="large-text code" value="<?php echo esc_attr($default_url); ?>" style="margin-bottom:15px;">
<br>
<button type="button" id="btn-import" class="button button-primary button-large">📥 Importieren</button>
<div id="import-result" style="margin-top:15px;"></div>
</div>
</div>
</div>
<script>
// Quick-Import Function
document.getElementById('btn-quick-import').addEventListener('click', function() {
const url = '<?php echo esc_js($default_url); ?>';
const resultDiv = document.getElementById('quick-import-result');
const btn = this;
btn.disabled = true;
btn.textContent = '⏳ Importiere von Gitea...';
resultDiv.innerHTML = '<div style="padding:10px; background:#fff3cd; border-left:4px solid:#ffc107; margin-top:10px;">⏳ Lade Items von Gitea...</div>';
fetch('<?php echo rest_url('wis/v1/import_json'); ?>', {
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(data => {
if (data.success) {
resultDiv.innerHTML = '<div class="updated" style="margin-top:10px;"><p><strong>✅ Erfolgreich!</strong><br>' + data.imported + ' Items importiert! (' + data.skipped + ' übersprungen)</p></div>';
setTimeout(() => {
window.location.href = '<?php echo admin_url('admin.php?page=wis_items'); ?>';
}, 2000);
} else {
resultDiv.innerHTML = '<div class="error" style="margin-top:10px;"><p><strong>❌ Fehler:</strong><br>' + data.message + '</p></div>';
}
})
.catch(e => {
resultDiv.innerHTML = '<div class="error" style="margin-top:10px;"><p><strong>❌ Netzwerkfehler:</strong><br>' + e.message + '</p></div>';
})
.finally(() => {
btn.disabled = false;
btn.textContent = '⚡ Quick-Import starten';
});
});
// Manual Import Function
document.getElementById('btn-import').addEventListener('click', function() {
const url = document.getElementById('import-url').value.trim();
const resultDiv = document.getElementById('import-result');
if (!url) {
resultDiv.innerHTML = '<div class="error"><p>Bitte URL eingeben!</p></div>';
return;
}
this.disabled = true;
this.textContent = '⏳ Importiere...';
fetch('<?php echo rest_url('wis/v1/import_json'); ?>', {
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(data => {
if (data.success) {
resultDiv.innerHTML = '<div class="updated"><p>✅ ' + data.imported + ' Items importiert! (' + data.skipped + ' übersprungen)</p></div>';
setTimeout(() => {
window.location.href = '<?php echo admin_url('admin.php?page=wis_items'); ?>';
}, 2000);
} else {
resultDiv.innerHTML = '<div class="error"><p>❌ Fehler: ' + data.message + '</p></div>';
}
})
.catch(e => {
resultDiv.innerHTML = '<div class="error"><p>❌ Netzwerkfehler: ' + e.message + '</p></div>';
})
.finally(() => {
this.disabled = false;
this.textContent = '📥 Importieren';
});
});
</script>
<?php
}
public static function page_reset() {
if (isset($_POST['wis_confirm_reset'])) {
check_admin_referer('wis_reset');
WIS_Activator::reset_shop();
echo '<div class="updated"><p>✅ Shop wurde komplett zurückgesetzt!</p></div>';
}
?>
<div class="wrap">
<h1>🔄 Shop Reset</h1>
<div class="card" style="max-width:800px; padding:20px; background:#fff3cd;">
<h2 style="color:#856404;">⚠️ WARNUNG</h2>
<p><strong>Diese Aktion löscht ALLE Daten:</strong></p>
<ul>
<li>❌ Alle Items</li>
<li>❌ Alle Bestellungen</li>
<li>❌ Alle Gutscheine</li>
<li>❌ Alle Server</li>
<li>❌ Alle Kategorien</li>
</ul>
<p style="color:red; font-weight:bold;">Diese Aktion kann NICHT rückgängig gemacht werden!</p>
<form method="post" onsubmit="return confirm('WIRKLICH ALLE DATEN LÖSCHEN? Dies kann nicht rückgängig gemacht werden!');">
<?php wp_nonce_field('wis_reset'); ?>
<p class="submit">
<input type="submit" name="wis_confirm_reset" class="button button-secondary button-large" value="🗑️ Shop jetzt zurücksetzen">
</p>
</form>
</div>
</div>
<?php
}
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'
GROUP BY player_name
ORDER BY total_spent DESC
LIMIT 50
");
?>
<div class="wrap">
<h1>🏆 Top Spender</h1>
<p>Spieler mit den höchsten Gesamtausgaben</p>
<table class="widefat fixed striped">
<thead>
<tr>
<th style="width:80px;">Rang</th>
<th>Spieler</th>
<th style="width:150px;">Ausgegeben</th>
<th style="width:150px;">Bestellungen</th>
</tr>
</thead>
<tbody>
<?php if (empty($results)): ?>
<tr><td colspan="4" style="text-align:center; padding:40px;">Noch keine Statistiken vorhanden.</td></tr>
<?php else: ?>
<?php $rank = 1; foreach ($results as $row): ?>
<tr>
<td><strong>#<?php echo $rank++; ?></strong></td>
<td><?php echo esc_html($row->player_name); ?></td>
<td><?php echo esc_html(number_format($row->total_spent)); ?> <?php echo esc_html($currency); ?></td>
<td><?php echo esc_html($row->order_count); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php
}
}
// ===========================================================
// REST API
// ===========================================================
class WIS_API {
public static function register_routes() {
register_rest_route('wis/v1', '/import_json', [
'methods' => 'POST',
'callback' => [self::class, 'import_json'],
'permission_callback' => '__return_true'
]);
register_rest_route('wis/v1', '/order', [
'methods' => 'POST',
'callback' => [self::class, 'create_order'],
'permission_callback' => '__return_true'
]);
register_rest_route('wis/v1', '/validate_coupon', [
'methods' => 'POST',
'callback' => [self::class, 'validate_coupon'],
'permission_callback' => '__return_true'
]);
// =================== SPIGOT COMPATIBILITY ROUTES ===================
// Get pending orders for a specific player (called by Spigot plugin)
register_rest_route('wis/v1', '/pending_orders', [
'methods' => 'GET',
'callback' => [self::class, 'get_pending_orders'],
'permission_callback' => '__return_true'
]);
// Execute order (called when player clicks Yes in Spigot GUI)
register_rest_route('wis/v1', '/execute_order', [
'methods' => 'POST',
'callback' => [self::class, 'execute_order'],
'permission_callback' => '__return_true'
]);
// Complete order (called after items given in Spigot)
register_rest_route('wis/v1', '/complete_order', [
'methods' => 'POST',
'callback' => [self::class, 'complete_order'],
'permission_callback' => '__return_true'
]);
// Cancel order (called when player clicks No in Spigot GUI)
register_rest_route('wis/v1', '/cancel_order', [
'methods' => 'POST',
'callback' => [self::class, 'cancel_order'],
'permission_callback' => '__return_true'
]);
}
// --- SPIGOT ENDPOINTS ---
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);
$wpdb->update(
$wpdb->prefix . 'wis_orders',
['status' => 'processing'],
['id' => $id]
);
return new WP_REST_Response(['success' => true]);
}
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);
$wpdb->update(
$wpdb->prefix . 'wis_orders',
['status' => 'cancelled'],
['id' => $id]
);
return new WP_REST_Response(['success' => true]);
}
// --- EXISTING ENDPOINTS ---
public static function import_json($request) {
$data = $request->get_json_params();
$url = esc_url_raw($data['url'] ?? '');
if (!$url) {
return new WP_REST_Response(['success' => false, 'message' => 'Keine URL angegeben'], 400);
}
$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);
$json = json_decode($body, true);
if (!isset($json['items']) || !is_array($json['items'])) {
return new WP_REST_Response(['success' => false, 'message' => 'Ungültiges JSON Format'], 400);
}
$imported = 0;
$skipped = 0;
foreach ($json['items'] as $item) {
$item_id = sanitize_text_field($item['id'] ?? '');
$name = sanitize_text_field($item['name'] ?? 'Unbekannt');
if (empty($item_id)) continue;
$exists = WIS_DB::get_item_by_item_id($item_id);
if ($exists) {
$skipped++;
continue;
}
WIS_DB::insert_item([
'item_id' => $item_id,
'name' => $name,
'description' => sanitize_textarea_field($item['description'] ?? ''),
'price' => intval($item['price'] ?? 0),
'status' => intval($item['price'] ?? 0) > 0 ? 'publish' : 'draft',
'servers' => '[]',
'categories' => '[]'
]);
$imported++;
}
return new WP_REST_Response(['success' => true, 'imported' => $imported, 'skipped' => $skipped]);
}
public static function create_order($request) {
global $wpdb;
$data = $request->get_json_params();
$player = sanitize_text_field($data['player'] ?? '');
$cart = $data['cart'] ?? [];
$server = sanitize_text_field($data['server'] ?? '');
$coupon_code = isset($data['coupon_code']) ? sanitize_text_field(strtoupper($data['coupon_code'])) : '';
if (!$player || empty($cart) || !$server) {
return new WP_REST_Response(['success' => false, 'message' => 'Fehlende Daten'], 400);
}
$exclude_offers = get_option('wis_coupon_exclude_offers', '0');
$currency = get_option('wis_currency_name', 'Coins');
$valid_cart = [];
$total_normal = 0;
$total_offer = 0;
foreach ($cart as $item_data) {
$item = WIS_DB::get_item(intval($item_data['id'] ?? 0));
if (!$item || $item->status !== 'publish') continue;
$qty = intval($item_data['quantity'] ?? 1);
if ($qty <= 0) continue;
$servers = json_decode($item->servers, true);
if (!in_array($server, $servers ?: [])) continue;
$price = $item->offer_price > 0 ? $item->offer_price : $item->price;
$valid_cart[] = [
'id' => $item->item_id, // Important: Spigot needs the item ID (e.g., minecraft:diamond)
'title' => $item->name,
'price' => $price,
'qty' => $qty,
'is_offer' => $item->is_offer
];
$item_total = $price * $qty;
if ($item->is_offer && $exclude_offers === '1') {
$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'], 400);
}
// Coupon validation
$coupon_discount = 0;
$coupon_msg = '';
$coupon_applied = false;
if (!empty($coupon_code)) {
$coupon = WIS_DB::get_coupon_by_code($coupon_code);
if ($coupon) {
if ($coupon->expiry && date('Y-m-d') > $coupon->expiry) {
$coupon_msg = 'Gutschein abgelaufen';
} elseif ($coupon->used_count >= $coupon->usage_limit) {
$coupon_msg = 'Gutschein bereits aufgebraucht';
} else {
if ($exclude_offers === '1' && $total_normal <= 0) {
$coupon_msg = 'Gutschein gilt nicht für Angebote';
} else {
if ($coupon->type === 'percent') {
$coupon_discount = floor($total_normal * ($coupon->value / 100));
} else {
$coupon_discount = $coupon->value;
}
WIS_DB::update_coupon($coupon->id, ['used_count' => $coupon->used_count + 1]);
$coupon_applied = true;
$coupon_msg = "Gutschein eingelöst: -{$coupon_discount} {$currency}";
}
}
} else {
$coupon_msg = 'Ungültiger Code';
}
}
$final_price = max(0, $total_normal - $coupon_discount) + $total_offer;
// Build order
$items_payload = [];
$title_parts = [];
foreach ($valid_cart as $item) {
$items_payload[] = [
'id' => $item['id'], // minecraft:diamond
'amount' => $item['qty']
];
$title_parts[] = $item['qty'] . 'x ' . $item['title'];
}
$title = "Warenkorb: " . implode(', ', $title_parts);
if (strlen($title) > 240) $title = substr($title, 0, 237) . '...';
$payload = [
'items' => $items_payload,
'coupon' => $coupon_applied ? ['code' => $coupon_code, 'discount' => $coupon_discount] : []
];
WIS_DB::insert_order([
'player_name' => $player,
'server' => $server,
'item_id' => 'cart',
'item_title' => $title,
'price' => $final_price,
'quantity' => count($valid_cart),
'status' => 'pending',
'response' => json_encode($payload)
]);
$msg = '✅ Bestellung erfolgreich!';
if ($coupon_msg) $msg .= ' (' . $coupon_msg . ')';
return new WP_REST_Response(['success' => true, 'message' => $msg]);
}
public static function validate_coupon($request) {
$data = $request->get_json_params();
$code = sanitize_text_field(strtoupper($data['code'] ?? ''));
$cart = $data['cart'] ?? [];
if (!$code) {
return new WP_REST_Response(['success' => false, 'message' => 'Kein Code']);
}
$coupon = WIS_DB::get_coupon_by_code($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' => 'Bereits aufgebraucht']);
}
$exclude_offers = get_option('wis_coupon_exclude_offers', '0');
if ($exclude_offers === '1' && !empty($cart)) {
$has_normal = false;
foreach ($cart as $item_data) {
$item = WIS_DB::get_item(intval($item_data['id'] ?? 0));
if ($item && !$item->is_offer) {
$has_normal = true;
break;
}
}
if (!$has_normal) {
return new WP_REST_Response(['success' => false, 'message' => 'Gutschein gilt nicht für Angebote']);
}
}
$currency = get_option('wis_currency_name', 'Coins');
$msg = $coupon->type === 'percent'
? "Gutschein gültig (-{$coupon->value}%)"
: "Gutschein gültig (-{$coupon->value} {$currency})";
return new WP_REST_Response([
'success' => true,
'type' => $coupon->type,
'value' => $coupon->value,
'message' => $msg
]);
}
}
// ===========================================================
// SHORTCODE - FRONTEND SHOP
// ===========================================================
class WIS_Shortcode {
public static function register() {
add_shortcode('ingame_shop_form', [self::class, 'render']);
}
public static function render() {
$servers = WIS_DB::get_servers();
$items = WIS_DB::get_items(['status' => 'publish']);
$categories = WIS_DB::get_categories();
$currency = get_option('wis_currency_name', 'Coins');
$img_base = get_option('wis_image_base_url', '');
$header_text = get_option('wis_header_text', '');
$exclude_offers = get_option('wis_coupon_exclude_offers', '0');
ob_start();
?>
<style>
.wis-shop { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 1400px; margin: 40px auto; background: #f4f6f8; padding: 20px; border-radius: 10px; position: relative; }
.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-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-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-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 320px)); gap: 15px; justify-content: center; margin: 0 auto; }
.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; width: 100%; }
.wis-card.offer { border: 2px solid #ffc107; }
.wis-card:hover { transform: translateY(-5px); box-shadow: 0 10px 20px rgba(0,0,0,0.1); }
.wis-card-img { width: 100%; height: 180px; background: #2d2d2d; display: flex; align-items: center; justify-content: center; position: relative; padding: 20px; }
.wis-card-img img { width: 100px !important; height: 100px !important; object-fit: contain; filter: drop-shadow(0 6px 8px rgba(0,0,0,0.7)); transition: transform 0.3s; image-rendering: pixelated; image-rendering: -moz-crisp-edges; image-rendering: crisp-edges; }
.wis-card:hover .wis-card-img img { transform: scale(1.15) rotate(5deg); }
.wis-offer-badge, .wis-daily-badge { position: absolute; top: 10px; left: 10px; background: linear-gradient(135deg, #ff416c, #ff4b2b); color: white; padding: 6px 12px; border-radius: 20px; font-size: 0.75rem; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.3); z-index: 2; }
.wis-daily-badge { background: linear-gradient(135deg, #6f42c1, #8e44ad); 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; }
.wis-card-servers { font-size: 0.85rem; color: #666; margin-bottom: 15px; }
.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; 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); }
/* FIX: HOHER Z-INDEX UND ISOLATION */
.wis-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.6);
z-index: 2147483647; /* Maximaler 32-bit Integer */
display: none;
align-items: center;
justify-content: center;
transform: translateZ(0); /* Erzwingt neue GPU Ebene */
}
.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); overflow-y: auto; position: relative; z-index: 2147483648; }
.wis-cart-item { display: flex; justify-content: space-between; align-items: center; padding: 15px; border-bottom: 1px solid #eee; }
.wis-cart-item-title { font-weight: bold; color: #333; }
.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; }
.wis-cart-total { border-top: 2px solid #333; padding: 20px 0; margin-top: 15px; font-size: 1.3rem; font-weight: bold; display: flex; justify-content: space-between; }
.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-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; }
.wis-btn-confirm { background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; }
.wis-btn-cancel { background: #6c757d; color: white; }
.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-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; }
</style>
<div class="wis-shop">
<div class="wis-header">
<h2>🛒 Ingame Shop</h2>
<?php if (!empty(trim($header_text))): ?>
<div class="wis-status"><?php echo esc_html($header_text); ?></div>
<?php endif; ?>
</div>
<div class="wis-control-bar">
<input type="text" id="wis-search" class="wis-search-input" placeholder="🔍 Suche Item...">
<select id="server-filter" class="wis-filter-select">
<option value="">Alle Server</option>
<?php foreach ($servers as $s): ?>
<option value="<?php echo esc_attr($s->slug); ?>"><?php echo esc_html($s->name); ?></option>
<?php endforeach; ?>
</select>
<button class="wis-cart-btn" onclick="openCart()">
🛒 Warenkorb
<span class="wis-cart-badge" id="cart-count">0</span>
</button>
</div>
<?php if (!empty($categories)): ?>
<div class="wis-cat-tabs">
<button class="wis-cat-btn active" onclick="filterCategory(0)">Alle</button>
<?php foreach ($categories as $cat): ?>
<button class="wis-cat-btn" onclick="filterCategory('<?php echo esc_js($cat->slug); ?>')"><?php echo esc_html($cat->name); ?></button>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="wis-grid">
<?php if (empty($items)): ?>
<div style="grid-column: 1 / -1; text-align: center; padding: 40px; background: #fff; border-radius: 10px;">
Keine Items im Shop verfügbar.
</div>
<?php else: ?>
<?php foreach ($items as $item):
$servers_arr = json_decode($item->servers, true) ?: [];
$cats_arr = json_decode($item->categories, true) ?: [];
$price = $item->offer_price > 0 ? $item->offer_price : $item->price;
$show_old = $item->offer_price > 0 && $item->offer_price != $item->price;
$img_name = str_replace(':', '_', $item->item_id) . '.png';
$img_url = $img_base . $img_name;
$server_names = [];
foreach ($servers_arr as $slug) {
foreach ($servers as $s) {
if ($s->slug === $slug) {
$server_names[] = $s->name;
break;
}
}
}
$servers_display = !empty($server_names) ? implode(', ', $server_names) : 'Kein Server';
?>
<div class="wis-card <?php echo $item->is_offer ? 'offer' : ''; ?>"
data-servers='<?php echo esc_attr(json_encode($servers_arr)); ?>'
data-cats='<?php echo esc_attr(json_encode($cats_arr)); ?>'
data-title="<?php echo esc_attr(strtolower($item->name)); ?>"
data-offer="<?php echo $item->is_offer ? '1' : '0'; ?>">
<?php if ($item->is_daily_deal): ?>
<div class="wis-daily-badge">🎁 Angebot des Tages</div>
<?php elseif ($item->is_offer): ?>
<div class="wis-offer-badge">🔥 Angebot</div>
<?php endif; ?>
<div class="wis-card-img">
<img src="<?php echo esc_url($img_url); ?>"
alt="<?php echo esc_attr($item->name); ?>"
loading="lazy"
onerror="this.onerror=null; this.src='https://via.placeholder.com/100/333/fff?text=?';">
</div>
<div class="wis-card-body">
<h3 class="wis-card-title" title="<?php echo esc_attr($item->name); ?>">
<?php echo esc_html($item->name); ?>
</h3>
<?php if ($item->description): ?>
<div class="wis-card-desc"><?php echo esc_html($item->description); ?></div>
<?php endif; ?>
<div class="wis-card-price-container">
<?php if ($show_old): ?>
<div class="wis-card-price-old"><?php echo esc_html($item->price); ?> <?php echo esc_html($currency); ?></div>
<?php endif; ?>
<div class="wis-card-price"><?php echo esc_html($price); ?> <?php echo esc_html($currency); ?></div>
</div>
<div class="wis-card-servers">📡 <?php echo esc_html($servers_display); ?></div>
<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">
<button class="wis-quantity-btn" onclick="changeQuantity(this, 1)">+</button>
</div>
<button class="wis-btn-add" onclick="addToCart(<?php echo $item->id; ?>, '<?php echo esc_js($item->name); ?>', <?php echo $price; ?>, this, <?php echo $item->is_offer ? 1 : 0; ?>)">
In den Warenkorb
</button>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<!-- Cart Modal -->
<div class="wis-modal-overlay" id="cart-modal">
<div class="wis-modal">
<h2>🛒 Dein Warenkorb</h2>
<div id="cart-content">
<div style="text-align:center; padding:40px; color:#999;">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;">
<label style="display:block; font-weight:bold; margin-bottom:5px;">🎫 Gutscheincode</label>
<div class="wis-coupon-input-group">
<input type="text" id="coupon-code" class="wis-coupon-input" placeholder="CODE">
<button class="wis-coupon-btn" onclick="validateCoupon()">Einlösen</button>
</div>
<div id="coupon-msg" class="wis-coupon-msg"></div>
</div>
<div class="wis-cart-total">
<span>Gesamt:</span>
<span id="cart-total">0 <?php echo esc_html($currency); ?></span>
</div>
<select id="checkout-server" class="wis-modal-input">
<option value="">-- Server wählen --</option>
<?php foreach ($servers as $s): ?>
<option value="<?php echo esc_attr($s->slug); ?>"><?php echo esc_html($s->name); ?></option>
<?php endforeach; ?>
</select>
<input type="text" id="checkout-player" class="wis-modal-input" placeholder="Dein 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 = "<?php echo esc_js($currency); ?>";
const shopExcludeOffers = <?php echo $exclude_offers === '1' ? 'true' : 'false'; ?>;
let cart = [];
let couponData = {};
let activeCategory = 0;
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 filterCategory(cat) {
activeCategory = cat;
document.querySelectorAll('.wis-cat-btn').forEach(b => b.classList.remove('active'));
event.target.classList.add('active');
updateGrid();
}
function updateGrid() {
const search = document.getElementById('wis-search').value.toLowerCase();
const server = document.getElementById('server-filter').value;
document.querySelectorAll('.wis-card').forEach(card => {
const title = card.dataset.title;
const servers = JSON.parse(card.dataset.servers || '[]');
const cats = JSON.parse(card.dataset.cats || '[]');
let visible = true;
if (search && !title.includes(search)) visible = false;
if (server && !servers.includes(server)) visible = false;
if (activeCategory && !cats.includes(activeCategory)) visible = false;
card.style.display = visible ? 'flex' : 'none';
});
}
document.getElementById('wis-search').addEventListener('input', updateGrid);
document.getElementById('server-filter').addEventListener('change', updateGrid);
function addToCart(id, name, price, btn, isOffer) {
const card = btn.closest('.wis-card');
const qty = parseInt(card.querySelector('.wis-quantity-input').value) || 1;
const servers = JSON.parse(card.dataset.servers || '[]');
const existing = cart.find(i => i.id === id);
if (existing) {
existing.quantity += qty;
} else {
cart.push({id, name, price, quantity: qty, servers, is_offer: !!isOffer});
}
updateCartBadge();
btn.textContent = '✅ Hinzugefügt';
btn.style.background = '#28a745';
setTimeout(() => {
btn.textContent = ' In den Warenkorb';
btn.style.background = '';
}, 1500);
card.querySelector('.wis-quantity-input').value = 1;
}
function updateCartBadge() {
const total = cart.reduce((sum, i) => sum + i.quantity, 0);
document.getElementById('cart-count').textContent = total;
}
function openCart() {
renderCart();
document.getElementById('cart-modal').style.display = 'flex';
}
function closeCart() {
document.getElementById('cart-modal').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;
}
});
let discount = 0;
if (couponData && typeof couponData.value !== 'undefined') {
if (couponData.type === 'percent') {
discount = normalSum * (couponData.value / 100);
} else {
discount = couponData.value;
}
}
return Math.max(0, normalSum - discount) + offerSum;
}
function renderCart() {
const content = document.getElementById('cart-content');
const checkout = document.getElementById('cart-checkout');
if (cart.length === 0) {
content.innerHTML = '<div style="text-align:center; padding:40px; color:#999;">Dein Warenkorb ist leer</div>';
checkout.style.display = 'none';
return;
}
let html = '';
cart.forEach((item, idx) => {
html += `
<div class="wis-cart-item">
<div>
<div class="wis-cart-item-title">${item.name}</div>
<div class="wis-cart-item-price">${item.quantity}x × ${item.price} = ${item.price * item.quantity} ${shopCurrency}</div>
</div>
<button class="wis-cart-item-remove" onclick="removeFromCart(${idx})">🗑️</button>
</div>
`;
});
content.innerHTML = html;
document.getElementById('cart-total').textContent = calculateTotal() + ' ' + shopCurrency;
checkout.style.display = 'block';
}
function removeFromCart(idx) {
cart.splice(idx, 1);
updateCartBadge();
renderCart();
}
function validateCoupon() {
const code = document.getElementById('coupon-code').value.trim().toUpperCase();
const msg = document.getElementById('coupon-msg');
if (!code) {
couponData = {};
msg.textContent = '';
renderCart();
return;
}
msg.textContent = 'Prüfe...';
msg.style.color = '#0073aa';
fetch('<?php echo rest_url('wis/v1/validate_coupon'); ?>', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({code, cart})
})
.then(r => r.json())
.then(data => {
if (data.success) {
couponData = {type: data.type, value: data.value};
msg.textContent = '✅ ' + data.message;
msg.style.color = 'green';
} else {
couponData = {};
msg.textContent = '❌ ' + data.message;
msg.style.color = 'red';
}
renderCart();
})
.catch(e => {
couponData = {};
msg.textContent = 'Fehler: ' + e.message;
msg.style.color = 'red';
renderCart();
});
}
function checkout() {
const player = document.getElementById('checkout-player').value.trim();
const server = document.getElementById('checkout-server').value;
const coupon_code = document.getElementById('coupon-code').value.trim();
const alert = document.getElementById('cart-alert');
if (!player) {
alert.textContent = 'Bitte Spielername eingeben!';
alert.className = 'wis-alert error';
alert.style.display = 'block';
return;
}
if (!server) {
alert.textContent = 'Bitte Server auswählen!';
alert.className = 'wis-alert error';
alert.style.display = 'block';
return;
}
const invalidItems = cart.filter(item => !item.servers.includes(server));
if (invalidItems.length > 0) {
alert.textContent = 'Einige Items sind nicht für diesen Server verfügbar!';
alert.className = 'wis-alert error';
alert.style.display = 'block';
return;
}
const btn = document.querySelector('.wis-btn-confirm');
btn.disabled = true;
btn.textContent = '⏳ Speichere...';
fetch('<?php echo rest_url('wis/v1/order'); ?>', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({player, cart, server, coupon_code})
})
.then(r => r.json())
.then(data => {
if (data.success) {
alert.textContent = data.message;
alert.className = 'wis-alert success';
alert.style.display = 'block';
cart = [];
couponData = {};
updateCartBadge();
setTimeout(() => {
closeCart();
location.reload();
},2000);
} else {
alert.textContent = data.message;
alert.className = 'wis-alert error';
alert.style.display = 'block';
}
})
.catch(e => {
alert.textContent = 'Fehler: ' + e.message;
alert.className = 'wis-alert error';
alert.style.display = 'block';
})
.finally(() => {
btn.disabled = false;
btn.textContent = '💰 Kauf abschließen';
});
}
document.getElementById('cart-modal').addEventListener('click', function(e) {
if (e.target === this) closeCart();
});
// FIX: Verschiebt das Modal ans Ende des Body Tags, damit es über allem liegt (Sticky Header, etc.)
document.addEventListener("DOMContentLoaded", function() {
const modal = document.getElementById('cart-modal');
if (modal && document.body) {
document.body.appendChild(modal);
}
});
</script>
<?php
return ob_get_clean();
}
}
// ===========================================================
// SIDEBAR WIDGET
// ===========================================================
class WIS_Sidebar_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'wis_sidebar_offer',
'WIS Shop Angebot',
['description' => 'Zeigt Daily Deal oder Angebot an']
);
}
public function widget($args, $instance) {
echo $args['before_widget'];
if (!empty($instance['title'])) {
echo $args['before_title'] . apply_filters('widget_title', $instance['title']) . $args['after_title'];
}
global $wpdb;
$currency = get_option('wis_currency_name', 'Coins');
$img_base = get_option('wis_image_base_url', '');
// Try daily deal first
$item = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}wis_items WHERE is_daily_deal = 1 AND status = 'publish' LIMIT 1");
// Fallback to regular offer
if (!$item) {
$item = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}wis_items WHERE is_offer = 1 AND status = 'publish' ORDER BY updated_at DESC LIMIT 1");
}
if ($item) {
$price = $item->offer_price > 0 ? $item->offer_price : $item->price;
$show_old = $item->offer_price > 0 && $item->offer_price != $item->price;
$img_name = str_replace(':', '_', $item->item_id) . '.png';
$img_url = $img_base . $img_name;
$target_url = !empty($instance['shop_url']) ? $instance['shop_url'] : home_url();
?>
<div style="background:#fff; border-radius:10px; padding:0; overflow:hidden; box-shadow:0 4px 10px rgba(0,0,0,0.05); text-align:center;">
<div style="position:relative; background:#2d2d2d; padding:15px; height:160px; display:flex; align-items:center; justify-content:center;">
<?php if ($item->is_daily_deal): ?>
<div style="position:absolute; top:10px; left:10px; background:linear-gradient(135deg, #6f42c1, #8e44ad); color:#fff; padding:4px 10px; font-size:10px; border-radius:20px; font-weight:bold; z-index:2;">🎁 DAILY DEAL</div>
<?php elseif ($item->is_offer): ?>
<div style="position:absolute; top:10px; left:10px; background:linear-gradient(135deg, #ff416c, #ff4b2b); color:#fff; padding:4px 10px; font-size:10px; border-radius:20px; font-weight:bold; z-index:2;">🔥 ANGEBOT</div>
<?php endif; ?>
<img src="<?php echo esc_url($img_url); ?>" alt="<?php echo esc_attr($item->name); ?>" style="max-width:90%; max-height:90%; object-fit:contain; filter:drop-shadow(0 4px 8px rgba(0,0,0,0.6));">
</div>
<div style="padding:15px;">
<h4 style="margin:0 0 8px 0; font-size:15px; color:#333; font-weight:700;"><?php echo esc_html($item->name); ?></h4>
<div style="margin-bottom:12px;">
<?php if ($show_old): ?>
<span style="text-decoration:line-through; color:#999; font-size:11px;"><?php echo esc_html($item->price); ?> <?php echo esc_html($currency); ?></span>
<?php endif; ?>
<span style="font-size:20px; font-weight:800; color:#28a745;"><?php echo esc_html($price); ?> <span style="font-size:12px; font-weight:400; color:#666;"><?php echo esc_html($currency); ?></span></span>
</div>
<a href="<?php echo esc_url($target_url); ?>" style="display:block; padding:10px 0; background:linear-gradient(135deg, #667eea 0%, #764ba2 100%); color:#fff; text-decoration:none; border-radius:6px; font-weight:bold; font-size:13px;">
<?php echo esc_html($instance['btn_text'] ?? 'Zum Shop'); ?> 🛒
</a>
</div>
</div>
<?php
} else {
echo '<div style="background:#f8f9fa; border:1px dashed #ddd; border-radius:8px; padding:20px; text-align:center;"><p style="margin:0; color:#888; font-size:13px;">Kein Angebot verfügbar.</p></div>';
}
echo $args['after_widget'];
}
public function form($instance) {
$title = !empty($instance['title']) ? $instance['title'] : '🔥 Angebot des Tages';
$btn_text = !empty($instance['btn_text']) ? $instance['btn_text'] : 'Zum Shop';
$shop_url = !empty($instance['shop_url']) ? $instance['shop_url'] : '';
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>">Titel:</label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('btn_text'); ?>">Button Text:</label>
<input class="widefat" id="<?php echo $this->get_field_id('btn_text'); ?>" name="<?php echo $this->get_field_name('btn_text'); ?>" type="text" value="<?php echo esc_attr($btn_text); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('shop_url'); ?>">Shop URL:</label>
<input class="widefat" id="<?php echo $this->get_field_id('shop_url'); ?>" name="<?php echo $this->get_field_name('shop_url'); ?>" type="text" value="<?php echo esc_attr($shop_url); ?>">
</p>
<?php
}
public function update($new_instance, $old_instance) {
$instance = [];
$instance['title'] = !empty($new_instance['title']) ? sanitize_text_field($new_instance['title']) : '';
$instance['btn_text'] = !empty($new_instance['btn_text']) ? sanitize_text_field($new_instance['btn_text']) : 'Zum Shop';
$instance['shop_url'] = !empty($new_instance['shop_url']) ? esc_url_raw($new_instance['shop_url']) : '';
return $instance;
}
}
// ===========================================================
// INITIALIZATION
// ===========================================================
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('rest_api_init', [WIS_API::class, 'register_routes']);
add_action('init', [WIS_Shortcode::class, 'register']);
add_action('widgets_init', function() {
register_widget('WIS_Sidebar_Widget');
});
add_action('wis_daily_deal_event', [WIS_Activator::class, 'run_daily_deal']);