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

3385 lines
168 KiB
PHP
Raw Permalink 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.2
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.2');
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();
self::create_default_categories();
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',
'wis_api_key' => bin2hex(random_bytes(24)),
];
foreach ($defaults as $key => $value) {
if (get_option($key) === false) {
add_option($key, $value);
}
}
}
public static function check_api_key($request) {
$key = $request->get_header('X-WIS-Key');
if (empty($key)) {
$key = $request->get_param('api_key');
}
$stored = get_option('wis_api_key', '');
return ($stored !== '' && hash_equals($stored, (string) $key));
}
public static function spigot_permission($request) {
if (!self::check_api_key($request)) {
return new WP_Error('wis_unauthorized', 'Ungültiger oder fehlender API-Key.', ['status' => 401]);
}
return true;
}
private static function create_default_categories() {
$default_categories = [
['name' => 'Baublöcke', 'slug' => 'baublocke'],
['name' => 'Dekorationsblöcke', 'slug' => 'dekorationsblocke'],
['name' => 'Redstone', 'slug' => 'redstone'],
['name' => 'Transport', 'slug' => 'transport'],
['name' => 'Natur', 'slug' => 'natur'],
['name' => 'Werkzeuge & Hilfsmittel', 'slug' => 'werkzeuge-hilfsmittel'],
['name' => 'Kampf', 'slug' => 'kampf'],
['name' => 'Nahrung & Tränke', 'slug' => 'nahrung-tranke'],
['name' => 'Zutaten', 'slug' => 'zutaten'],
['name' => 'Spawn-Eier', 'slug' => 'spawn-eier']
];
global $wpdb;
foreach ($default_categories as $cat) {
$exists = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM {$wpdb->prefix}wis_categories WHERE slug = %s",
$cat['slug']
));
if (!$exists) {
$wpdb->insert($wpdb->prefix . 'wis_categories', [
'name' => $cat['name'],
'slug' => $cat['slug']
]);
}
}
}
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));
$wpdb->update($table, ['is_daily_deal' => 0], ['is_daily_deal' => 1]);
$item = $wpdb->get_row("SELECT * FROM $table WHERE status = 'publish' AND price > 0 ORDER BY RAND() LIMIT 1");
if ($item) {
$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();
self::create_default_categories();
return true;
}
}
// ===========================================================
// ITEM CATEGORIZER
// ===========================================================
class WIS_Item_Categorizer {
public static function auto_categorize($item_id) {
$item_id = strtolower($item_id);
$item_id = str_replace('minecraft:', '', $item_id);
if (strpos($item_id, 'spawn_egg') !== false) {
return ['spawn-eier'];
}
if (preg_match('/(wooden|stone|iron|golden|diamond|netherite|copper)_(pickaxe|axe|shovel|hoe)/', $item_id)) {
return ['werkzeuge-hilfsmittel'];
}
if (in_array($item_id, [
'shears', 'fishing_rod', 'flint_and_steel', 'bucket', 'water_bucket', 'lava_bucket',
'milk_bucket', 'powder_snow_bucket', 'axolotl_bucket', 'cod_bucket', 'pufferfish_bucket',
'salmon_bucket', 'tadpole_bucket', 'tropical_fish_bucket',
'compass', 'recovery_compass', 'clock', 'spyglass', 'map', 'filled_map',
'brush', 'lead', 'name_tag', 'saddle', 'carrot_on_a_stick', 'warped_fungus_on_a_stick'
])) {
return ['werkzeuge-hilfsmittel'];
}
if (preg_match('/(wooden|stone|iron|golden|diamond|netherite|copper)_(sword|spear)/', $item_id)) {
return ['kampf'];
}
if (preg_match('/(leather|chainmail|iron|golden|diamond|netherite|copper|turtle)_(helmet|chestplate|leggings|boots|cap|tunic|pants|shell)/', $item_id)) {
return ['kampf'];
}
if (in_array($item_id, [
'bow', 'crossbow', 'arrow', 'spectral_arrow', 'tipped_arrow',
'shield', 'trident', 'mace',
'totem_of_undying', 'elytra',
'horse_armor', 'iron_horse_armor', 'golden_horse_armor', 'diamond_horse_armor',
'wolf_armor'
]) || strpos($item_id, 'horse_armor') !== false) {
return ['kampf'];
}
if (preg_match('/(raw_|cooked_)?(beef|porkchop|mutton|chicken|rabbit|cod|salmon)/', $item_id)) {
return ['nahrung-tranke'];
}
if (in_array($item_id, [
'apple', 'golden_apple', 'enchanted_golden_apple',
'melon_slice', 'glow_berries', 'sweet_berries', 'chorus_fruit',
'carrot', 'golden_carrot', 'potato', 'baked_potato', 'poisonous_potato',
'beetroot',
'bread', 'cookie', 'cake', 'pumpkin_pie',
'dried_kelp',
'tropical_fish', 'pufferfish', 'rotten_flesh', 'spider_eye'
])) {
return ['nahrung-tranke'];
}
if (strpos($item_id, '_stew') !== false || strpos($item_id, '_soup') !== false) {
return ['nahrung-tranke'];
}
if (strpos($item_id, 'potion') !== false || in_array($item_id, [
'honey_bottle', 'milk_bucket', 'glass_bottle', 'dragon_breath',
'experience_bottle', 'ominous_bottle'
])) {
return ['nahrung-tranke'];
}
if (strpos($item_id, '_boat') !== false || strpos($item_id, '_raft') !== false) {
return ['transport'];
}
if (strpos($item_id, 'minecart') !== false) {
return ['transport'];
}
if (in_array($item_id, ['elytra', 'saddle', 'lead'])) {
return ['transport'];
}
if (strpos($item_id, 'redstone') !== false && $item_id !== 'redstone_ore') {
return ['redstone'];
}
if (in_array($item_id, [
'repeater', 'comparator', 'observer',
'piston', 'sticky_piston',
'dispenser', 'dropper', 'hopper',
'lever', 'tripwire_hook', 'daylight_detector',
'tnt', 'target', 'lightning_rod'
]) || strpos($item_id, 'button') !== false || strpos($item_id, 'pressure_plate') !== false) {
return ['redstone'];
}
if (strpos($item_id, '_rail') !== false || $item_id === 'rail') {
return ['redstone'];
}
$pure_materials = [
'stick', 'coal', 'charcoal', 'diamond', 'emerald', 'lapis_lazuli',
'iron_ingot', 'gold_ingot', 'copper_ingot', 'netherite_ingot',
'iron_nugget', 'gold_nugget', 'copper_nugget',
'raw_iron', 'raw_gold', 'raw_copper',
'netherite_scrap', 'netherite_upgrade',
'amethyst_shard', 'prismarine_shard', 'prismarine_crystals',
'quartz', 'nether_quartz', 'echo_shard', 'disc_fragment',
'string', 'feather', 'leather', 'rabbit_hide',
'slimeball', 'ender_pearl', 'ender_eye',
'blaze_rod', 'blaze_powder', 'magma_cream', 'ghast_tear',
'nether_star', 'nether_brick',
'nautilus_shell', 'heart_of_the_sea', 'scute', 'turtle_scute', 'armadillo_scute',
'bone', 'bone_meal', 'gunpowder', 'glowstone_dust', 'sugar',
'phantom_membrane', 'ink_sac', 'glow_ink_sac',
'paper', 'book', 'flint',
'fermented_spider_eye', 'glistering_melon_slice', 'rabbit_foot',
'nether_wart', 'breeze_rod',
'clay_ball', 'brick', 'firework_star',
'shulker_shell', 'popped_chorus_fruit'
];
if (in_array($item_id, $pure_materials)) {
return ['zutaten'];
}
if (strpos($item_id, '_pottery_sherd') !== false || strpos($item_id, '_armor_trim') !== false) {
return ['zutaten'];
}
if (in_array($item_id, [
'dirt', 'coarse_dirt', 'rooted_dirt', 'grass_block', 'podzol', 'mycelium',
'farmland', 'dirt_path',
'sand', 'red_sand', 'gravel', 'clay',
'suspicious_sand', 'suspicious_gravel'
])) {
return ['natur'];
}
if (strpos($item_id, '_leaves') !== false || strpos($item_id, '_sapling') !== false ||
strpos($item_id, 'azalea') !== false) {
return ['natur'];
}
$flowers = [
'dandelion', 'poppy', 'blue_orchid', 'allium', 'azure_bluet',
'red_tulip', 'orange_tulip', 'white_tulip', 'pink_tulip',
'oxeye_daisy', 'cornflower', 'lily_of_the_valley', 'wither_rose',
'sunflower', 'lilac', 'rose_bush', 'peony',
'pitcher_plant', 'pitcher_pod', 'torchflower', 'torchflower_seeds',
'pink_petals', 'spore_blossom'
];
if (in_array($item_id, $flowers)) {
return ['natur'];
}
if (strpos($item_id, 'mushroom') !== false || strpos($item_id, 'fungus') !== false ||
in_array($item_id, ['short_grass', 'tall_grass', 'fern', 'large_fern', 'dead_bush'])) {
return ['natur'];
}
if (in_array($item_id, [
'seagrass', 'tall_seagrass', 'kelp', 'dried_kelp', 'sea_pickle',
'vine', 'weeping_vines', 'twisting_vines', 'cave_vines', 'glow_lichen',
'hanging_roots', 'mangrove_roots', 'muddy_mangrove_roots'
])) {
return ['natur'];
}
if (strpos($item_id, '_seeds') !== false || in_array($item_id, [
'wheat', 'beetroot', 'carrot', 'potato',
'melon', 'pumpkin', 'carved_pumpkin',
'sugar_cane', 'bamboo', 'cocoa_beans',
'sweet_berries', 'glow_berries', 'sweet_berry_bush',
'nether_wart', 'cactus',
'mangrove_propagule'
])) {
return ['natur'];
}
if (in_array($item_id, [
'crimson_roots', 'warped_roots', 'nether_sprouts',
'crimson_nylium', 'warped_nylium'
])) {
return ['natur'];
}
if (in_array($item_id, [
'moss_block', 'moss_carpet',
'big_dripleaf', 'small_dripleaf',
'lily_pad', 'bee_nest', 'honeycomb', 'honeycomb_block',
'snow', 'snowball', 'powder_snow'
])) {
return ['natur'];
}
if (strpos($item_id, 'glass') !== false && strpos($item_id, '_pane') === false) {
return ['dekorationsblocke'];
}
if (strpos($item_id, 'glass_pane') !== false || strpos($item_id, 'stained_glass_pane') !== false) {
return ['dekorationsblocke'];
}
if (strpos($item_id, '_door') !== false || strpos($item_id, '_trapdoor') !== false ||
strpos($item_id, '_fence_gate') !== false) {
return ['dekorationsblocke'];
}
if ((strpos($item_id, '_fence') !== false && strpos($item_id, '_fence_gate') === false) ||
(strpos($item_id, '_wall') !== false && !in_array($item_id, ['wall_banner', 'wall_sign', 'wall_torch'])) ||
$item_id === 'iron_bars') {
return ['dekorationsblocke'];
}
if (strpos($item_id, '_stairs') !== false || strpos($item_id, '_slab') !== false) {
return ['dekorationsblocke'];
}
if (strpos($item_id, '_carpet') !== false) {
return ['dekorationsblocke'];
}
if (in_array($item_id, [
'torch', 'soul_torch', 'lantern', 'soul_lantern',
'campfire', 'soul_campfire', 'end_rod',
'shroomlight', 'froglight', 'sea_lantern'
]) || strpos($item_id, '_candle') !== false || $item_id === 'candle') {
return ['dekorationsblocke'];
}
if (in_array($item_id, [
'crafting_table', 'furnace', 'blast_furnace', 'smoker',
'chest', 'trapped_chest', 'ender_chest', 'barrel',
'enchanting_table', 'anvil', 'chipped_anvil', 'damaged_anvil',
'grindstone', 'smithing_table', 'cartography_table', 'fletching_table',
'loom', 'stonecutter', 'brewing_stand', 'cauldron', 'composter',
'lectern', 'bookshelf', 'chiseled_bookshelf',
'bell', 'beacon', 'conduit', 'lodestone', 'respawn_anchor'
]) || strpos($item_id, '_bed') !== false || strpos($item_id, 'shulker_box') !== false) {
return ['dekorationsblocke'];
}
if (strpos($item_id, '_sign') !== false || strpos($item_id, '_hanging_sign') !== false ||
strpos($item_id, '_banner') !== false ||
in_array($item_id, ['item_frame', 'glow_item_frame', 'painting', 'armor_stand'])) {
return ['dekorationsblocke'];
}
if (in_array($item_id, [
'ladder', 'scaffolding', 'chain',
'flower_pot', 'decorated_pot',
'dragon_egg', 'dragon_head',
'note_block', 'jukebox'
]) || strpos($item_id, '_head') !== false || strpos($item_id, '_skull') !== false ||
strpos($item_id, 'coral') !== false) {
return ['dekorationsblocke'];
}
if (in_array($item_id, [
'stone', 'cobblestone', 'mossy_cobblestone',
'granite', 'polished_granite', 'diorite', 'polished_diorite',
'andesite', 'polished_andesite', 'calcite', 'tuff',
'smooth_stone'
]) || strpos($item_id, 'stone_brick') !== false) {
return ['baublocke'];
}
if (strpos($item_id, 'deepslate') !== false) {
return ['baublocke'];
}
if (strpos($item_id, 'brick') !== false && $item_id !== 'brick' && $item_id !== 'nether_brick') {
return ['baublocke'];
}
if (strpos($item_id, 'sandstone') !== false) {
return ['baublocke'];
}
if (strpos($item_id, 'quartz_') !== false && strpos($item_id, 'nether_quartz') === false) {
return ['baublocke'];
}
if (strpos($item_id, 'prismarine') !== false || strpos($item_id, 'purpur') !== false) {
return ['baublocke'];
}
if (strpos($item_id, 'concrete') !== false || strpos($item_id, 'terracotta') !== false) {
return ['baublocke'];
}
if (strpos($item_id, '_wool') !== false) {
return ['baublocke'];
}
if (strpos($item_id, '_planks') !== false ||
(strpos($item_id, '_log') !== false && strpos($item_id, 'stripped') === false) ||
(strpos($item_id, '_wood') !== false && strpos($item_id, 'stripped') === false)) {
return ['baublocke'];
}
if (strpos($item_id, 'stripped_') !== false) {
return ['baublocke'];
}
if (in_array($item_id, ['bamboo_block', 'bamboo_mosaic', 'crimson_stem', 'warped_stem',
'crimson_hyphae', 'warped_hyphae'])) {
return ['baublocke'];
}
if (strpos($item_id, 'copper_') !== false && strpos($item_id, '_ingot') === false &&
strpos($item_id, '_nugget') === false && $item_id !== 'copper_door' && $item_id !== 'copper_trapdoor') {
return ['baublocke'];
}
if (strpos($item_id, '_block') !== false) {
return ['baublocke'];
}
if (in_array($item_id, [
'netherrack', 'soul_sand', 'soul_soil',
'basalt', 'polished_basalt', 'smooth_basalt',
'end_stone', 'obsidian', 'crying_obsidian'
]) || strpos($item_id, 'blackstone') !== false || strpos($item_id, 'end_stone_brick') !== false) {
return ['baublocke'];
}
if (in_array($item_id, [
'glowstone', 'sponge', 'wet_sponge',
'ice', 'packed_ice', 'blue_ice',
'magma_block', 'slime_block', 'honey_block',
'hay_block', 'dried_kelp_block',
'mud', 'packed_mud', 'mud_bricks',
'dripstone_block', 'amethyst_block', 'budding_amethyst'
])) {
return ['baublocke'];
}
return ['baublocke'];
}
}
// ===========================================================
// DATABASE HELPER
// ===========================================================
class WIS_DB {
public static function get_items($args = []) {
global $wpdb;
$table = $wpdb->prefix . 'wis_items';
$where_parts = ["1=1"];
if (isset($args['status'])) {
$where_parts[] = $wpdb->prepare("status = %s", $args['status']);
}
if (isset($args['category_slug']) && !empty($args['category_slug'])) {
$search_pattern = '%"' . $args['category_slug'] . '"%';
$where_parts[] = $wpdb->prepare("categories LIKE %s", $search_pattern);
}
if (isset($args['ids']) && is_array($args['ids']) && !empty($args['ids'])) {
$ids = array_map('intval', $args['ids']);
$placeholders = implode(',', array_fill(0, count($ids), '%d'));
$where_parts[] = sprintf("id IN ($placeholders)", ...$ids);
}
if (isset($args['search']) && !empty($args['search'])) {
$search_like = '%' . $wpdb->esc_like($args['search']) . '%';
$where_parts[] = $wpdb->prepare("(name LIKE %s OR item_id LIKE %s)", $search_like, $search_like);
}
$where = implode(" AND ", $where_parts);
$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 $where $orderby $limit");
}
public static function count_items($args = []) {
global $wpdb;
$table = $wpdb->prefix . 'wis_items';
$where_parts = ["1=1"];
if (isset($args['status'])) {
$where_parts[] = $wpdb->prepare("status = %s", $args['status']);
}
if (isset($args['category_slug']) && !empty($args['category_slug'])) {
$search_pattern = '%"' . $args['category_slug'] . '"%';
$where_parts[] = $wpdb->prepare("categories LIKE %s", $search_pattern);
}
if (isset($args['search']) && !empty($args['search'])) {
$search_like = '%' . $wpdb->esc_like($args['search']) . '%';
$where_parts[] = $wpdb->prepare("(name LIKE %s OR item_id LIKE %s)", $search_like, $search_like);
}
$where = implode(" AND ", $where_parts);
return (int) $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE $where");
}
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;
if (empty($data['categories']) || $data['categories'] === '[]') {
$auto_cats = WIS_Item_Categorizer::auto_categorize($data['item_id']);
$data['categories'] = json_encode($auto_cats);
}
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]);
}
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]);
}
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]);
}
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]);
}
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() {
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']));
update_option('wis_offline_queue_enabled', isset($_POST['wis_offline_queue_enabled']) ? '1' : '0');
echo '<div class="updated"><p>✅ Einstellungen gespeichert!</p></div>';
}
if (isset($_POST['wis_regen_key']) && check_admin_referer('wis_regen_key')) {
update_option('wis_api_key', bin2hex(random_bytes(24)));
echo '<div class="updated"><p>🔑 Neuer API-Key generiert! Bitte in der config.yml des Spigot-Plugins aktualisieren.</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>
<tr>
<th>Offline-Queue</th>
<td>
<label>
<input type="checkbox" name="wis_offline_queue_enabled" value="1" <?php checked(get_option('wis_offline_queue_enabled', '0'), '1'); ?>>
Items auch an offline Spieler liefern (beim nächsten Login)
</label>
<p class="description">Erfordert Spigot-Plugin v6.3+ mit Offline-Queue-Unterstützung</p>
</td>
</tr>
</table>
<p class="submit">
<input type="submit" name="wis_save_settings" class="button button-primary" value="Einstellungen speichern">
</p>
</form>
</div>
<div class="card" style="max-width:800px; margin-top:20px; padding:20px; border-left:4px solid #0073aa;">
<h2>🔑 Spigot API-Key</h2>
<p>Dieser Key muss in der <code>config.yml</code> des Spigot-Plugins als <code>api-key</code> eingetragen werden.</p>
<div style="display:flex; align-items:center; gap:10px; margin:10px 0;">
<input type="text"
id="wis-api-key-field"
value="<?php echo esc_attr(get_option('wis_api_key', '')); ?>"
class="large-text code"
readonly
style="font-family:monospace; background:#f0f4f8;">
<button type="button" class="button" onclick="
const f = document.getElementById('wis-api-key-field');
f.type='text'; f.select();
document.execCommand('copy');
this.textContent='✅ Kopiert!';
setTimeout(()=>this.textContent='📋 Kopieren',2000);
">📋 Kopieren</button>
</div>
<form method="post" onsubmit="return confirm('Neuen Key generieren? Das Spigot-Plugin muss danach neu konfiguriert werden!');">
<?php wp_nonce_field('wis_regen_key'); ?>
<input type="submit" name="wis_regen_key" class="button button-secondary" value="🔄 Neuen Key generieren">
</form>
<p class="description" style="margin-top:10px;">
Geschützte Endpunkte: <code>pending_orders</code>, <code>execute_order</code>, <code>complete_order</code>, <code>cancel_order</code>, <code>pending_offline</code>
</p>
</div>
</div>
<?php
}
public static function page_items() {
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':
if (isset($_POST['item_prices'][$id])) {
$new_price = intval($_POST['item_prices'][$id]);
if ($new_price >= 0) {
$data['price'] = $new_price;
}
}
break;
case 'offer':
$is_offer = isset($_POST['item_offers'][$id]) ? 1 : 0;
$data['is_offer'] = $is_offer;
if (isset($_POST['item_offer_prices'][$id])) {
$offer_price = intval($_POST['item_offer_prices'][$id]);
$data['offer_price'] = $is_offer ? $offer_price : 0;
} else 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':
if (isset($_POST['item_servers'][$id])) {
$data['servers'] = json_encode($_POST['item_servers'][$id]);
} else {
$data['servers'] = '[]';
}
break;
case 'category':
if (isset($_POST['item_cat_assignments'][$id])) {
$data['categories'] = json_encode($_POST['item_cat_assignments'][$id]);
}
break;
}
if (!empty($data)) {
WIS_DB::update_item($id, $data);
$count++;
}
}
echo '<div class="updated"><p>✅ ' . $count . ' Items erfolgreich aktualisiert!</p></div>';
}
}
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']);
$all_servers = WIS_DB::get_servers();
$all_categories = WIS_DB::get_categories();
$currency = get_option('wis_currency_name', 'Coins');
$items_to_edit = WIS_DB::get_items(['ids' => $selected_ids]);
?>
<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:2000px; 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); ?>">
<?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 colspan="2"><h3>Preis pro Item festlegen</h3></th>
</tr>
<tr>
<td colspan="2" style="padding:0;">
<div style="max-height:800px; overflow-y:auto; border:1px solid #ddd; padding:10px;">
<table class="widefat striped">
<thead>
<tr>
<th style="width:50%">Item Name</th>
<th style="width:25%">Aktueller Preis</th>
<th style="width:25%">Neuer Preis (<?php echo esc_html($currency); ?>)</th>
</tr>
</thead>
<tbody>
<?php foreach($items_to_edit as $item): ?>
<tr>
<td>
<strong><?php echo esc_html($item->name); ?></strong><br>
<small style="color:#666"><?php echo esc_html($item->item_id); ?></small>
</td>
<td><?php echo esc_html($item->price); ?> <?php echo esc_html($currency); ?></td>
<td>
<input type="number"
name="item_prices[<?php echo $item->id; ?>]"
value="<?php echo esc_attr($item->price); ?>"
min="0"
style="width:100%; padding:5px;">
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</td>
</tr>
<?php elseif ($action === 'offer'): ?>
<tr>
<th colspan="2"><h3>Angebot pro Item festlegen</h3></th>
</tr>
<tr>
<td colspan="2" style="padding:0;">
<div style="max-height:800px; overflow-y:auto; border:1px solid #ddd; padding:10px;">
<table class="widefat striped">
<thead>
<tr>
<th style="width:40%">Item Name</th>
<th style="width:20%">Normalpreis</th>
<th style="width:20%">Als Angebot?</th>
<th style="width:20%">Angebotspreis</th>
</tr>
</thead>
<tbody>
<?php foreach($items_to_edit as $item): ?>
<tr>
<td>
<strong><?php echo esc_html($item->name); ?></strong><br>
<small style="color:#666"><?php echo esc_html($item->item_id); ?></small>
</td>
<td><?php echo esc_html($item->price); ?> <?php echo esc_html($currency); ?></td>
<td>
<input type="checkbox"
name="item_offers[<?php echo $item->id; ?>]"
value="1"
<?php checked($item->is_offer, 1); ?>
onchange="toggleOfferPrice(this, <?php echo $item->id; ?>)">
</td>
<td>
<input type="number"
name="item_offer_prices[<?php echo $item->id; ?>]"
id="offer_price_<?php echo $item->id; ?>"
value="<?php echo esc_attr($item->offer_price); ?>"
min="0"
style="width:100%; padding:5px;"
<?php if(!$item->is_offer): ?>disabled<?php endif; ?>>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<script>
function toggleOfferPrice(checkbox, itemId) {
const input = document.getElementById('offer_price_' + itemId);
input.disabled = !checkbox.checked;
}
</script>
</td>
</tr>
<?php elseif ($action === 'status'): ?>
<tr>
<th><label>Neuer Status für alle ausgewählten Items</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 colspan="2"><h3>Server pro Item zuweisen</h3></th>
</tr>
<tr>
<td colspan="2" style="padding:0;">
<?php if (empty($all_servers)): ?>
<p>Keine Server vorhanden.</p>
<?php else: ?>
<div style="max-height:800px; overflow-y:auto; border:1px solid #ddd; padding:10px;">
<table class="widefat striped">
<thead>
<tr>
<th style="width:40%">Item Name</th>
<th style="width:60%">Server (Mehrfachauswahl möglich)</th>
</tr>
</thead>
<tbody>
<?php foreach($items_to_edit as $item):
$current_servers = json_decode($item->servers, true) ?: [];
?>
<tr>
<td>
<strong><?php echo esc_html($item->name); ?></strong><br>
<small style="color:#666"><?php echo esc_html($item->item_id); ?></small>
</td>
<td>
<?php foreach ($all_servers as $s):
$checked = in_array($s->slug, $current_servers) ? 'checked' : '';
?>
<label style="display:inline-block; margin-right:15px; margin-bottom:5px;">
<input type="checkbox"
name="item_servers[<?php echo $item->id; ?>][]"
value="<?php echo esc_attr($s->slug); ?>"
<?php echo $checked; ?>>
<?php echo esc_html($s->name); ?>
</label>
<?php endforeach; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</td>
</tr>
<?php elseif ($action === 'category'): ?>
<tr>
<th colspan="2"><h3>Kategorien pro Item zuweisen</h3></th>
</tr>
<tr>
<td colspan="2" style="padding:0;">
<div style="max-height:800px; overflow-y:auto; border:1px solid #ddd; padding:10px;">
<?php if(empty($items_to_edit)): ?>
<p>Fehler beim Laden der Items.</p>
<?php else: ?>
<table class="widefat striped">
<thead>
<tr>
<th style="width:35%">Item Name</th>
<th style="width:65%">Kategorie (Mehrfachauswahl mit STRG+Klick)</th>
</tr>
</thead>
<tbody>
<?php foreach($items_to_edit as $item):
$current_cats = json_decode($item->categories, true) ?: [];
?>
<tr>
<td>
<strong><?php echo esc_html($item->name); ?></strong><br>
<small style="color:#666"><?php echo esc_html($item->item_id); ?></small>
</td>
<td>
<select name="item_cat_assignments[<?php echo $item->id; ?>][]" multiple style="height:250px; width:100%; font-size:14px; padding:5px;">
<?php foreach($all_categories as $cat):
$selected = in_array($cat->slug, $current_cats) ? 'selected' : '';
?>
<option value="<?php echo esc_attr($cat->slug); ?>" <?php echo $selected; ?>>
<?php echo esc_html($cat->name); ?>
</option>
<?php endforeach; ?>
</select>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</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;
}
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>';
}
}
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>';
}
}
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) : [];
if (!$item && isset($_GET['add'])) {
$item_cats = [];
}
?>
<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 (Kategorien werden automatisch zugewiesen)</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.</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; ?>
<p class="description">Bei neuem Item werden Kategorien automatisch basierend auf der Item-ID gesetzt</p>
<?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 mit Suche + Pagination
$categories = WIS_DB::get_categories();
$current_category = isset($_GET['wis_category']) ? sanitize_text_field($_GET['wis_category']) : '';
$search_query = isset($_GET['wis_search']) ? sanitize_text_field($_GET['wis_search']) : '';
$per_page = 24;
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$fetch_args = ['status' => 'publish'];
if (!empty($current_category)) $fetch_args['category_slug'] = $current_category;
if (!empty($search_query)) $fetch_args['search'] = $search_query;
$total_items = WIS_DB::count_items($fetch_args);
$total_pages = max(1, (int) ceil($total_items / $per_page));
$current_page = min($current_page, $total_pages);
$offset = ($current_page - 1) * $per_page;
global $wpdb;
$table_items = $wpdb->prefix . 'wis_items';
$where_parts = ["status = 'publish'"];
if (!empty($current_category)) {
$search_pattern = '%"' . $current_category . '"%';
$where_parts[] = $wpdb->prepare("categories LIKE %s", $search_pattern);
}
if (!empty($search_query)) {
$search_like = '%' . $wpdb->esc_like($search_query) . '%';
$where_parts[] = $wpdb->prepare("(name LIKE %s OR item_id LIKE %s)", $search_like, $search_like);
}
$where_sql = implode(" AND ", $where_parts);
$items = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $table_items WHERE $where_sql ORDER BY name ASC LIMIT %d OFFSET %d",
$per_page,
$offset
));
$currency = get_option('wis_currency_name', 'Coins');
$base_url = admin_url('admin.php?page=wis_items');
if (!empty($current_category)) $base_url .= '&wis_category=' . urlencode($current_category);
if (!empty($search_query)) $base_url .= '&wis_search=' . urlencode($search_query);
?>
<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>
<div style="margin:10px 0 15px; display:flex; align-items:center; gap:10px; flex-wrap:wrap;">
<form method="get" style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
<input type="hidden" name="page" value="wis_items">
<?php if (!empty($current_category)): ?>
<input type="hidden" name="wis_category" value="<?php echo esc_attr($current_category); ?>">
<?php endif; ?>
<input
type="search"
name="wis_search"
value="<?php echo esc_attr($search_query); ?>"
placeholder="🔍 Item suchen (Name oder ID)…"
class="regular-text"
style="min-width:280px; padding:6px 10px;">
<input type="submit" class="button" value="Suchen">
<?php if (!empty($search_query)): ?>
<a href="<?php echo esc_url(admin_url('admin.php?page=wis_items' . (!empty($current_category) ? '&wis_category=' . urlencode($current_category) : ''))); ?>" class="button">✕ Zurücksetzen</a>
<?php endif; ?>
</form>
<span style="color:#666; font-size:13px;">
<?php echo $total_items; ?> Item(s) gefunden
<?php if (!empty($search_query)): ?>
Suche: <strong><?php echo esc_html($search_query); ?></strong>
<?php endif; ?>
</span>
</div>
<nav class="nav-tab-wrapper">
<a href="<?php echo admin_url('admin.php?page=wis_items' . (!empty($search_query) ? '&wis_search=' . urlencode($search_query) : '')); ?>" class="nav-tab <?php echo $current_category === '' ? 'nav-tab-active' : ''; ?>">
Alle
</a>
<?php foreach ($categories as $cat): ?>
<a href="<?php echo admin_url('admin.php?page=wis_items&wis_category=' . $cat->slug . (!empty($search_query) ? '&wis_search=' . urlencode($search_query) : '')); ?>" class="nav-tab <?php echo $current_category === $cat->slug ? 'nav-tab-active' : ''; ?>">
<?php echo esc_html($cat->name); ?>
</a>
<?php endforeach; ?>
</nav>
<br>
<form method="post">
<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="price">Preis ändern</option>
<option value="offer">Angebot ändern</option>
<option value="server">Server zuweisen</option>
<option value="category">Kategorie zuweisen</option>
<option value="status">Aktivieren/Deaktivieren</option>
</select>
<input type="submit" name="wis_bulk_apply" class="button action" value="Anwenden">
</div>
<div class="tablenav-pages" style="float:right; margin-top:4px;">
<?php if ($total_pages > 1): ?>
<span class="displaying-num"><?php echo $total_items; ?> Einträge</span>
<span class="pagination-links">
<?php if ($current_page > 1): ?>
<a class="button" href="<?php echo esc_url($base_url . '&paged=1'); ?>">«</a>
<a class="button" href="<?php echo esc_url($base_url . '&paged=' . ($current_page - 1)); ?>"></a>
<?php endif; ?>
<span class="paging-input" style="margin:0 6px;">
Seite <strong><?php echo $current_page; ?></strong> von <strong><?php echo $total_pages; ?></strong>
</span>
<?php if ($current_page < $total_pages): ?>
<a class="button" href="<?php echo esc_url($base_url . '&paged=' . ($current_page + 1)); ?>"></a>
<a class="button" href="<?php echo esc_url($base_url . '&paged=' . $total_pages); ?>">»</a>
<?php endif; ?>
</span>
<?php else: ?>
<span class="displaying-num"><?php echo $total_items; ?> Einträge</span>
<?php endif; ?>
</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;">
<?php echo !empty($search_query) ? 'Keine Items für diese Suche gefunden.' : 'Keine Items in dieser Kategorie gefunden.'; ?>
</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>
<?php if ($total_pages > 1): ?>
<div class="tablenav bottom">
<div class="tablenav-pages" style="float:right; margin-top:8px;">
<span class="displaying-num"><?php echo $total_items; ?> Einträge</span>
<span class="pagination-links">
<?php if ($current_page > 1): ?>
<a class="button" href="<?php echo esc_url($base_url . '&paged=1'); ?>">«</a>
<a class="button" href="<?php echo esc_url($base_url . '&paged=' . ($current_page - 1)); ?>"></a>
<?php endif; ?>
<span class="paging-input" style="margin:0 6px;">
Seite <strong><?php echo $current_page; ?></strong> von <strong><?php echo $total_pages; ?></strong>
</span>
<?php if ($current_page < $total_pages): ?>
<a class="button" href="<?php echo esc_url($base_url . '&paged=' . ($current_page + 1)); ?>"></a>
<a class="button" href="<?php echo esc_url($base_url . '&paged=' . $total_pages); ?>">»</a>
<?php endif; ?>
</span>
</div>
<br class="clear">
</div>
<?php endif; ?>
</form>
</div>
<script>
(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() {
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>';
}
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() {
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>';
}
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() {
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']);
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>';
}
}
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>';
}
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;
}
$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() {
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>';
}
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>';
}
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'
];
?>
<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;
}
$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',
'claimed' => '📡 Abgeholt',
'processing' => 'Geben...',
'completed' => 'Fertig',
'cancelled' => 'Abgebrochen',
'failed' => 'Fehler'
];
$status_colors = [
'pending' => '#ffc107',
'claimed' => '#17a2b8',
'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() {
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_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>
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';
});
});
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',
]);
register_rest_route('wis/v1', '/shop_items', [
'methods' => 'GET',
'callback' => [self::class, 'get_shop_items'],
'permission_callback' => '__return_true',
]);
register_rest_route('wis/v1', '/pending_orders', [
'methods' => 'GET',
'callback' => [self::class, 'get_pending_orders'],
'permission_callback' => [WIS_Activator::class, 'spigot_permission'],
]);
register_rest_route('wis/v1', '/execute_order', [
'methods' => 'POST',
'callback' => [self::class, 'execute_order'],
'permission_callback' => [WIS_Activator::class, 'spigot_permission'],
]);
register_rest_route('wis/v1', '/complete_order', [
'methods' => 'POST',
'callback' => [self::class, 'complete_order'],
'permission_callback' => [WIS_Activator::class, 'spigot_permission'],
]);
register_rest_route('wis/v1', '/cancel_order', [
'methods' => 'POST',
'callback' => [self::class, 'cancel_order'],
'permission_callback' => [WIS_Activator::class, 'spigot_permission'],
]);
register_rest_route('wis/v1', '/pending_offline', [
'methods' => 'GET',
'callback' => [self::class, 'get_pending_offline'],
'permission_callback' => [WIS_Activator::class, 'spigot_permission'],
]);
register_rest_route('wis/v1', '/orders_history', [
'methods' => 'GET',
'callback' => [self::class, 'get_orders_history'],
'permission_callback' => [WIS_Activator::class, 'spigot_permission'],
]);
}
public static function get_shop_items($request) {
$page = max(1, intval($request->get_param('page') ?? 1));
$per_page = 24;
$category = sanitize_text_field($request->get_param('category') ?? '');
$search = sanitize_text_field($request->get_param('search') ?? '');
global $wpdb;
$table = $wpdb->prefix . 'wis_items';
$where_parts = ["status = 'publish'"];
if (!empty($category)) {
$search_pattern = '%"' . $category . '"%';
$where_parts[] = $wpdb->prepare("categories LIKE %s", $search_pattern);
}
if (!empty($search)) {
$search_like = '%' . $wpdb->esc_like($search) . '%';
$where_parts[] = $wpdb->prepare("(name LIKE %s OR item_id LIKE %s)", $search_like, $search_like);
}
$where_sql = implode(" AND ", $where_parts);
$total = (int) $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE $where_sql");
$offset = ($page - 1) * $per_page;
$items = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $table WHERE $where_sql ORDER BY name ASC LIMIT %d OFFSET %d",
$per_page, $offset
));
$img_base = get_option('wis_image_base_url', '');
$currency = get_option('wis_currency_name', 'Coins');
$result = [];
foreach ($items as $item) {
$img_name = str_replace(':', '_', $item->item_id) . '.png';
$result[] = [
'id' => $item->id,
'item_id' => $item->item_id,
'name' => $item->name,
'description' => $item->description,
'price' => intval($item->price),
'offer_price' => intval($item->offer_price),
'is_offer' => (bool) $item->is_offer,
'is_daily_deal' => (bool) $item->is_daily_deal,
'servers' => json_decode($item->servers, true) ?: [],
'categories' => json_decode($item->categories, true) ?: [],
'image' => $img_base . $img_name,
];
}
return new WP_REST_Response([
'items' => $result,
'total' => $total,
'page' => $page,
'per_page' => $per_page,
'total_pages' => max(1, (int) ceil($total / $per_page)),
'currency' => $currency,
]);
}
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' => []]);
}
$table = $wpdb->prefix . 'wis_orders';
$results = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $table
WHERE player_name = %s AND status = 'pending'
ORDER BY created_at ASC LIMIT 5",
$player
));
if (!empty($results)) {
$ids = implode(',', array_map(fn($o) => intval($o->id), $results));
$wpdb->query("UPDATE $table SET status = 'claimed' WHERE id IN ($ids) AND status = 'pending'");
}
return new WP_REST_Response(['orders' => $results]);
}
public static function get_pending_offline($request) {
if (get_option('wis_offline_queue_enabled', '0') !== '1') {
return new WP_REST_Response(['orders' => [], 'message' => 'Offline-Queue deaktiviert']);
}
global $wpdb;
$player = sanitize_text_field($request->get_param('player'));
if (!$player) {
return new WP_REST_Response(['orders' => []]);
}
$table = $wpdb->prefix . 'wis_orders';
$results = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $table
WHERE player_name = %s AND status = 'pending'
ORDER BY created_at ASC LIMIT 10",
$player
));
if (!empty($results)) {
$ids = implode(',', array_map(fn($o) => intval($o->id), $results));
$wpdb->query("UPDATE $table SET status = 'claimed' WHERE id IN ($ids) AND status = 'pending'");
}
return new WP_REST_Response(['orders' => $results]);
}
public static function get_orders_history($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 id, item_title, price, status, created_at
FROM {$wpdb->prefix}wis_orders
WHERE player_name = %s
ORDER BY created_at DESC 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->query($wpdb->prepare(
"UPDATE {$wpdb->prefix}wis_orders SET status = 'processing'
WHERE id = %d AND status IN ('pending','claimed')",
$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]);
}
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,
'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_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;
$items_payload = [];
$title_parts = [];
foreach ($valid_cart as $item) {
$items_payload[] = [
'id' => $item['id'],
'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();
$categories = WIS_DB::get_categories();
$currency = get_option('wis_currency_name', 'Coins');
$header_text = get_option('wis_header_text', '');
$exclude_offers = get_option('wis_coupon_exclude_offers', '0');
$first_category = !empty($categories) ? $categories[0]->slug : '';
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; min-height: 200px; }
.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); }
.wis-pagination { display: flex; justify-content: center; align-items: center; gap: 8px; margin: 30px 0 10px; flex-wrap: wrap; }
.wis-page-btn { padding: 8px 14px; border: 2px solid #ddd; background: #fff; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; transition: all 0.2s; color: #333; }
.wis-page-btn:hover { border-color: #667eea; color: #667eea; }
.wis-page-btn.active { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-color: #667eea; color: #fff; }
.wis-page-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.wis-page-info { font-size: 14px; color: #666; margin: 0 8px; }
.wis-loading { text-align: center; padding: 60px 20px; color: #888; font-size: 1.1rem; }
.wis-loading-spinner { display: inline-block; width: 36px; height: 36px; border: 4px solid #ddd; border-top-color: #667eea; border-radius: 50%; animation: wis-spin 0.8s linear infinite; margin-bottom: 12px; }
@keyframes wis-spin { to { transform: rotate(360deg); } }
.wis-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 2147483647; display: none; align-items: center; justify-content: center; transform: translateZ(0); }
.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" id="wis-open-cart-btn">
🛒 Warenkorb
<span class="wis-cart-badge" id="cart-count">0</span>
</button>
</div>
<?php if (!empty($categories)): ?>
<div class="wis-cat-tabs">
<?php $is_first = true; ?>
<?php foreach ($categories as $cat): ?>
<button class="wis-cat-btn <?php echo $is_first ? 'active' : ''; ?>"
data-cat="<?php echo esc_attr($cat->slug); ?>">
<?php echo esc_html($cat->name); ?>
</button>
<?php $is_first = false; ?>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="wis-grid" id="wis-grid">
<div class="wis-loading" style="grid-column:1/-1;">
<div class="wis-loading-spinner"></div><br>Lade Items…
</div>
</div>
<div class="wis-pagination" id="wis-pagination"></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 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" id="wis-coupon-btn">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" id="wis-checkout-btn">💰 Kauf abschließen</button>
<button class="wis-modal-btn wis-btn-cancel" id="wis-close-cart-btn">Abbrechen</button>
</div>
<div id="cart-alert" class="wis-alert"></div>
</div>
</div>
</div>
<script>
(function() {
const shopCurrency = <?php echo json_encode($currency); ?>;
const shopExcludeOffers = <?php echo $exclude_offers === '1' ? 'true' : 'false'; ?>;
const apiBase = <?php echo json_encode(rest_url('wis/v1')); ?>;
const serverList = <?php echo json_encode(array_map(function($s){ return ['slug'=>$s->slug,'name'=>$s->name]; }, $servers)); ?>;
let cart = [];
let couponData = {};
let currentCat = <?php echo json_encode($first_category); ?>;
let currentSearch = '';
let currentPage = 1;
let totalPages = 1;
let searchTimer = null;
let allItems = [];
let itemMap = {}; // id (int) -> item object
// -------------------------------------------------------
// INIT
// -------------------------------------------------------
function init() {
// Modal an <body> hängen damit z-index korrekt greift
const modal = document.getElementById('cart-modal');
if (modal && document.body) document.body.appendChild(modal);
// Erste Items laden
loadItems(1);
// ---- Event Delegation: Warenkorb-Buttons im Grid ----
document.getElementById('wis-grid').addEventListener('click', function(e) {
// "In den Warenkorb" Button
const addBtn = e.target.closest('.wis-btn-add');
if (addBtn) {
const card = addBtn.closest('.wis-card');
const id = parseInt(addBtn.getAttribute('data-item-id'), 10);
const item = itemMap[id];
if (!item || !card) return;
const qtyInput = card.querySelector('.wis-quantity-input');
const qty = parseInt(qtyInput ? qtyInput.value : '1', 10) || 1;
addToCartItem(item, qty, addBtn, card);
return;
}
// Mengen-Buttons
const qtyBtn = e.target.closest('.wis-quantity-btn');
if (qtyBtn) {
const delta = parseInt(qtyBtn.getAttribute('data-delta'), 10);
const ctrl = qtyBtn.closest('.wis-quantity-control');
if (!ctrl) return;
const input = ctrl.querySelector('.wis-quantity-input');
if (input) input.value = Math.max(1, Math.min(999, (parseInt(input.value, 10) || 1) + delta));
}
});
// ---- Kategorie-Tabs ----
document.querySelectorAll('.wis-cat-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
document.querySelectorAll('.wis-cat-btn').forEach(function(b){ b.classList.remove('active'); });
btn.classList.add('active');
currentCat = btn.getAttribute('data-cat');
currentPage = 1;
loadItems(1);
});
});
// ---- Suche ----
var searchInput = document.getElementById('wis-search');
if (searchInput) {
searchInput.addEventListener('input', function() {
clearTimeout(searchTimer);
searchTimer = setTimeout(function() {
currentSearch = searchInput.value.trim();
currentPage = 1;
loadItems(1);
}, 350);
});
}
// ---- Server-Filter ----
var serverSelect = document.getElementById('server-filter');
if (serverSelect) {
serverSelect.addEventListener('change', function() {
renderGrid(allItems);
});
}
// ---- Warenkorb öffnen ----
var openBtn = document.getElementById('wis-open-cart-btn');
if (openBtn) openBtn.addEventListener('click', openCart);
// ---- Warenkorb schließen ----
var closeBtn = document.getElementById('wis-close-cart-btn');
if (closeBtn) closeBtn.addEventListener('click', closeCart);
// ---- Modal-Overlay klick ----
var cartModal = document.getElementById('cart-modal');
if (cartModal) {
cartModal.addEventListener('click', function(e) {
if (e.target === cartModal) closeCart();
});
}
// ---- Gutschein einlösen ----
var couponBtn = document.getElementById('wis-coupon-btn');
if (couponBtn) couponBtn.addEventListener('click', validateCoupon);
// ---- Kauf abschließen ----
var checkoutBtn = document.getElementById('wis-checkout-btn');
if (checkoutBtn) checkoutBtn.addEventListener('click', checkout);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// -------------------------------------------------------
// ITEMS LADEN
// -------------------------------------------------------
function loadItems(page) {
currentPage = page;
var grid = document.getElementById('wis-grid');
grid.innerHTML = '<div class="wis-loading" style="grid-column:1/-1;"><div class="wis-loading-spinner"></div><br>Lade Items…</div>';
document.getElementById('wis-pagination').innerHTML = '';
var url = apiBase + '/shop_items?page=' + page;
if (currentCat) url += '&category=' + encodeURIComponent(currentCat);
if (currentSearch) url += '&search=' + encodeURIComponent(currentSearch);
fetch(url)
.then(function(r){ return r.json(); })
.then(function(data) {
allItems = data.items || [];
totalPages = data.total_pages || 1;
itemMap = {};
allItems.forEach(function(i){ itemMap[i.id] = i; });
renderGrid(allItems);
renderPagination(data.page, data.total_pages, data.total);
})
.catch(function() {
grid.innerHTML = '<div class="wis-loading" style="grid-column:1/-1; color:#dc3545;">Fehler beim Laden der Items.</div>';
});
}
// -------------------------------------------------------
// GRID RENDERN — KEIN INLINE-ONCLICK, nur data-item-id
// -------------------------------------------------------
function renderGrid(items) {
var grid = document.getElementById('wis-grid');
var serverSlug = document.getElementById('server-filter').value;
var filtered = serverSlug
? items.filter(function(i){ return i.servers.indexOf(serverSlug) !== -1; })
: items;
if (filtered.length === 0) {
grid.innerHTML = '<div style="grid-column:1/-1; text-align:center; padding:40px; background:#fff; border-radius:10px; color:#888;">Keine Items gefunden.</div>';
return;
}
grid.innerHTML = filtered.map(function(item) {
var price = item.offer_price > 0 ? item.offer_price : item.price;
var showOld = item.offer_price > 0 && item.offer_price !== item.price;
var serverNames = item.servers
.map(function(slug) {
var s = serverList.find(function(x){ return x.slug === slug; });
return s ? s.name : slug;
})
.join(', ') || 'Kein Server';
var badge = item.is_daily_deal
? '<div class="wis-daily-badge">🎁 Angebot des Tages</div>'
: (item.is_offer ? '<div class="wis-offer-badge">🔥 Angebot</div>' : '');
var oldPrice = showOld
? '<div class="wis-card-price-old">' + item.price + ' ' + shopCurrency + '</div>'
: '';
var desc = item.description
? '<div class="wis-card-desc">' + escHtml(item.description) + '</div>'
: '';
// Wichtig: data-item-id statt onclick mit JSON
return '<div class="wis-card' + (item.is_offer ? ' offer' : '') + '">'
+ badge
+ '<div class="wis-card-img">'
+ '<img src="' + escHtml(item.image) + '" alt="' + escHtml(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="' + escHtml(item.name) + '">' + escHtml(item.name) + '</h3>'
+ desc
+ '<div class="wis-card-price-container">'
+ oldPrice
+ '<div class="wis-card-price">' + price + ' ' + shopCurrency + '</div>'
+ '</div>'
+ '<div class="wis-card-servers">📡 ' + escHtml(serverNames) + '</div>'
+ '<div class="wis-quantity-control">'
+ '<button class="wis-quantity-btn" data-delta="-1" type="button">-</button>'
+ '<input type="number" class="wis-quantity-input" value="1" min="1" max="999">'
+ '<button class="wis-quantity-btn" data-delta="1" type="button">+</button>'
+ '</div>'
+ '<button class="wis-btn-add" data-item-id="' + item.id + '" type="button"> In den Warenkorb</button>'
+ '</div>'
+ '</div>';
}).join('');
}
// -------------------------------------------------------
// PAGINATION
// -------------------------------------------------------
function renderPagination(page, pages, total) {
var el = document.getElementById('wis-pagination');
if (pages <= 1) { el.innerHTML = ''; return; }
var html = '';
html += '<button class="wis-page-btn" ' + (page <= 1 ? 'disabled' : '') + ' data-page="' + (page - 1) + '"> Zurück</button>';
var start = Math.max(1, page - 2);
var end = Math.min(pages, page + 2);
if (start > 1) {
html += '<button class="wis-page-btn" data-page="1">1</button>';
if (start > 2) html += '<span style="color:#999">…</span>';
}
for (var p = start; p <= end; p++) {
html += '<button class="wis-page-btn' + (p === page ? ' active' : '') + '" data-page="' + p + '">' + p + '</button>';
}
if (end < pages) {
if (end < pages - 1) html += '<span style="color:#999">…</span>';
html += '<button class="wis-page-btn" data-page="' + pages + '">' + pages + '</button>';
}
html += '<button class="wis-page-btn" ' + (page >= pages ? 'disabled' : '') + ' data-page="' + (page + 1) + '">Weiter </button>';
html += '<span class="wis-page-info">' + total + ' Items</span>';
el.innerHTML = html;
// Pagination-Buttons via Event Delegation
el.querySelectorAll('.wis-page-btn:not([disabled])').forEach(function(btn) {
btn.addEventListener('click', function() {
var p = parseInt(btn.getAttribute('data-page'), 10);
if (p) {
loadItems(p);
document.querySelector('.wis-shop').scrollIntoView({behavior: 'smooth', block: 'start'});
}
});
});
}
// -------------------------------------------------------
// WARENKORB LOGIK
// -------------------------------------------------------
function addToCartItem(item, qty, btn, card) {
var price = item.offer_price > 0 ? item.offer_price : item.price;
var existing = cart.find(function(i){ return i.id === item.id; });
if (existing) {
existing.quantity += qty;
} else {
cart.push({
id: item.id,
name: item.name,
price: price,
quantity: qty,
servers: item.servers,
is_offer: !!item.is_offer
});
}
updateCartBadge();
btn.textContent = '✅ Hinzugefügt';
btn.style.background = '#28a745';
setTimeout(function() {
btn.textContent = ' In den Warenkorb';
btn.style.background = '';
}, 1500);
var qtyInput = card.querySelector('.wis-quantity-input');
if (qtyInput) qtyInput.value = 1;
}
function updateCartBadge() {
var total = cart.reduce(function(s, i){ return s + 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() {
var normal = 0, offer = 0;
cart.forEach(function(item) {
var t = item.price * item.quantity;
if (shopExcludeOffers && item.is_offer) offer += t; else normal += t;
});
var discount = 0;
if (couponData && typeof couponData.value !== 'undefined') {
discount = couponData.type === 'percent'
? Math.floor(normal * couponData.value / 100)
: couponData.value;
}
return Math.max(0, normal - discount) + offer;
}
function renderCart() {
var content = document.getElementById('cart-content');
var 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;
}
content.innerHTML = cart.map(function(item, idx) {
return '<div class="wis-cart-item">'
+ '<div>'
+ '<div class="wis-cart-item-title">' + escHtml(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" data-cart-idx="' + idx + '" type="button">🗑️</button>'
+ '</div>';
}).join('');
// Remove-Buttons
content.querySelectorAll('.wis-cart-item-remove').forEach(function(btn) {
btn.addEventListener('click', function() {
var idx = parseInt(btn.getAttribute('data-cart-idx'), 10);
cart.splice(idx, 1);
updateCartBadge();
renderCart();
});
});
document.getElementById('cart-total').textContent = calculateTotal() + ' ' + shopCurrency;
checkout.style.display = 'block';
}
function validateCoupon() {
var code = document.getElementById('coupon-code').value.trim().toUpperCase();
var msgEl = document.getElementById('coupon-msg');
if (!code) { couponData = {}; msgEl.textContent = ''; renderCart(); return; }
msgEl.textContent = 'Prüfe…';
msgEl.style.color = '#0073aa';
fetch(apiBase + '/validate_coupon', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({code: code, cart: cart})
})
.then(function(r){ return r.json(); })
.then(function(data) {
if (data.success) {
couponData = {type: data.type, value: data.value};
msgEl.textContent = '✅ ' + data.message;
msgEl.style.color = 'green';
} else {
couponData = {};
msgEl.textContent = '❌ ' + data.message;
msgEl.style.color = 'red';
}
renderCart();
});
}
function checkout() {
var player = document.getElementById('checkout-player').value.trim();
var server = document.getElementById('checkout-server').value;
var couponCode = document.getElementById('coupon-code').value.trim();
var alertEl = document.getElementById('cart-alert');
var btn = document.getElementById('wis-checkout-btn');
if (!player) { alertEl.textContent = 'Bitte Spielername eingeben!'; alertEl.className = 'wis-alert error'; alertEl.style.display = 'block'; return; }
if (!server) { alertEl.textContent = 'Bitte Server auswählen!'; alertEl.className = 'wis-alert error'; alertEl.style.display = 'block'; return; }
var invalid = cart.filter(function(i){ return i.servers.indexOf(server) === -1; });
if (invalid.length) { alertEl.textContent = 'Einige Items sind nicht für diesen Server!'; alertEl.className = 'wis-alert error'; alertEl.style.display = 'block'; return; }
btn.disabled = true;
btn.textContent = '⏳ Speichere…';
fetch(apiBase + '/order', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({player: player, cart: cart, server: server, coupon_code: couponCode})
})
.then(function(r){ return r.json(); })
.then(function(data) {
if (data.success) {
alertEl.textContent = data.message;
alertEl.className = 'wis-alert success';
alertEl.style.display = 'block';
cart = []; couponData = {};
updateCartBadge();
setTimeout(function(){ closeCart(); location.reload(); }, 2000);
} else {
alertEl.textContent = data.message;
alertEl.className = 'wis-alert error';
alertEl.style.display = 'block';
}
})
.finally(function() {
btn.disabled = false;
btn.textContent = '💰 Kauf abschließen';
});
}
// -------------------------------------------------------
// HILFSFUNKTIONEN
// -------------------------------------------------------
function escHtml(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
})();
</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', '');
$item = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}wis_items WHERE is_daily_deal = 1 AND status = 'publish' LIMIT 1");
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']);