Upload via Git Manager GUI
This commit is contained in:
@@ -235,6 +235,30 @@ class WIS_Activator {
|
||||
$wpdb->query("ALTER TABLE $table ADD COLUMN custom_image_url varchar(500) DEFAULT NULL AFTER categories");
|
||||
}
|
||||
|
||||
// Ankauf-Spalten nachrüsten (ab v6.5)
|
||||
$col_sell = $wpdb->get_var("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '$table' AND COLUMN_NAME = 'sell_enabled'");
|
||||
if (!$col_sell) {
|
||||
$wpdb->query("ALTER TABLE $table ADD COLUMN sell_enabled tinyint(1) NOT NULL DEFAULT 0 AFTER status");
|
||||
$wpdb->query("ALTER TABLE $table ADD COLUMN sell_price_mode varchar(20) NOT NULL DEFAULT 'percent' AFTER sell_enabled");
|
||||
$wpdb->query("ALTER TABLE $table ADD COLUMN sell_price_value int(11) NOT NULL DEFAULT 80 AFTER sell_price_mode");
|
||||
}
|
||||
|
||||
// Sell-Log-Tabelle anlegen
|
||||
$wpdb->query("CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wis_sell_log (
|
||||
id mediumint(9) NOT NULL AUTO_INCREMENT,
|
||||
player_name varchar(100) NOT NULL,
|
||||
server varchar(100) NOT NULL,
|
||||
item_id varchar(100) NOT NULL,
|
||||
item_name varchar(255) NOT NULL,
|
||||
quantity int(11) NOT NULL DEFAULT 1,
|
||||
price_per_item decimal(10,2) NOT NULL,
|
||||
total_paid decimal(10,2) NOT NULL,
|
||||
sold_at datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY player_name (player_name),
|
||||
KEY sold_at (sold_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;");
|
||||
|
||||
self::create_tables();
|
||||
self::set_default_options();
|
||||
self::create_default_categories();
|
||||
@@ -997,6 +1021,86 @@ class WIS_DB {
|
||||
// ADMIN PAGES
|
||||
// ===========================================================
|
||||
class WIS_Admin {
|
||||
// Wird auf admin_init gefeuert – vor jeder HTML-Ausgabe, damit wp_redirect() funktioniert
|
||||
public static function handle_save_item() {
|
||||
if (!isset($_POST['wis_save_item'])) return;
|
||||
if (!current_user_can('manage_options')) return;
|
||||
check_admin_referer('wis_item_form');
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$item_type = sanitize_text_field($_POST['item_type'] ?? 'minecraft');
|
||||
if ($item_type === 'fly') {
|
||||
$resolved_item_id = sanitize_text_field($_POST['fly_duration'] ?? 'fly_5min');
|
||||
} elseif ($item_type === 'rank') {
|
||||
$rank_id = preg_replace('/[^a-z0-9_\-]/', '', strtolower($_POST['rank_id'] ?? 'vip'));
|
||||
$lp_group = preg_replace('/[^a-zA-Z0-9_\-]/', '', $_POST['lp_group'] ?? $rank_id);
|
||||
$default_group = preg_replace('/[^a-zA-Z0-9_\-]/', '', $_POST['default_group'] ?? 'default');
|
||||
$rank_days = max(0, intval($_POST['rank_days'] ?? 30));
|
||||
if (empty($lp_group)) $lp_group = $rank_id;
|
||||
if (empty($default_group)) $default_group = 'default';
|
||||
$resolved_item_id = 'rank_' . $rank_id . '_' . $lp_group . '_' . $default_group . '_' . $rank_days;
|
||||
} elseif ($item_type === 'fly_abo') {
|
||||
$abo_days = max(1, intval($_POST['abo_days'] ?? 30));
|
||||
$resolved_item_id = 'fly_abo_' . $abo_days;
|
||||
} else {
|
||||
$resolved_item_id = sanitize_text_field($_POST['item_id'] ?? '');
|
||||
}
|
||||
|
||||
if (empty($resolved_item_id) || empty(trim($_POST['name'] ?? ''))) {
|
||||
// Fehler als transient speichern, damit page_items() ihn anzeigen kann
|
||||
set_transient('wis_save_error_' . get_current_user_id(), '❌ Name und Item-ID sind Pflichtfelder.', 30);
|
||||
wp_redirect(wp_get_referer() ?: admin_url('admin.php?page=wis_items'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'item_id' => $resolved_item_id,
|
||||
'name' => sanitize_text_field($_POST['name']),
|
||||
'description' => sanitize_textarea_field($_POST['description'] ?? ''),
|
||||
'price' => intval($_POST['price'] ?? 0),
|
||||
'offer_price' => intval($_POST['offer_price'] ?? 0),
|
||||
'is_offer' => isset($_POST['is_offer']) ? 1 : 0,
|
||||
'servers' => isset($_POST['servers']) ? json_encode(array_map('sanitize_text_field', $_POST['servers'])) : '[]',
|
||||
'categories' => isset($_POST['categories']) ? json_encode(array_map('sanitize_text_field', $_POST['categories'])) : '[]',
|
||||
'custom_image_url' => esc_url_raw($_POST['custom_image_url'] ?? ''),
|
||||
'sell_enabled' => isset($_POST['sell_enabled']) ? 1 : 0,
|
||||
'sell_price_mode' => in_array($_POST['sell_price_mode'] ?? '', ['percent','fixed','minus']) ? $_POST['sell_price_mode'] : 'percent',
|
||||
'sell_price_value' => max(0, intval($_POST['sell_price_value'] ?? 80)),
|
||||
'status' => (intval($_POST['price'] ?? 0) > 0 || $item_type === 'fly' || $item_type === 'rank' || $item_type === 'fly_abo') ? 'publish' : 'draft',
|
||||
];
|
||||
|
||||
if (isset($_GET['edit'])) {
|
||||
$result = WIS_DB::update_item(intval($_GET['edit']), $data);
|
||||
if ($result !== false) {
|
||||
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . intval($_GET['edit']) . '&saved=1'));
|
||||
exit;
|
||||
} else {
|
||||
set_transient('wis_save_error_' . get_current_user_id(), '❌ Fehler beim Speichern: ' . $wpdb->last_error, 30);
|
||||
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . intval($_GET['edit'])));
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
$existing = WIS_DB::get_item_by_item_id($resolved_item_id);
|
||||
if ($existing) {
|
||||
WIS_DB::update_item($existing->id, $data);
|
||||
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . $existing->id . '&saved=1'));
|
||||
exit;
|
||||
}
|
||||
$result = WIS_DB::insert_item($data);
|
||||
if ($result) {
|
||||
$new_id = $wpdb->insert_id;
|
||||
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . $new_id . '&created=1'));
|
||||
exit;
|
||||
} else {
|
||||
$err = $wpdb->last_error ?: 'Unbekannter Fehler.';
|
||||
set_transient('wis_save_error_' . get_current_user_id(), '❌ Fehler beim Erstellen: ' . $err, 30);
|
||||
wp_redirect(admin_url('admin.php?page=wis_items&add=1'));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function register_menu() {
|
||||
add_menu_page(
|
||||
'Ingame Shop',
|
||||
@@ -1205,6 +1309,14 @@ class WIS_Admin {
|
||||
$data['categories'] = json_encode($_POST['item_cat_assignments'][$id]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'sell':
|
||||
$data['sell_enabled'] = isset($_POST['item_sell_enabled'][$id]) ? 1 : 0;
|
||||
$mode = sanitize_text_field($_POST['item_sell_mode'][$id] ?? 'percent');
|
||||
if (!in_array($mode, ['percent', 'fixed', 'minus'])) $mode = 'percent';
|
||||
$data['sell_price_mode'] = $mode;
|
||||
$data['sell_price_value'] = max(0, intval($_POST['item_sell_value'][$id] ?? 80));
|
||||
break;
|
||||
}
|
||||
|
||||
if (!empty($data)) {
|
||||
@@ -1437,6 +1549,100 @@ class WIS_Admin {
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php elseif ($action === 'sell'): ?>
|
||||
<tr>
|
||||
<th colspan="2"><h3>Ankauf pro Item konfigurieren</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:30%">Item Name</th>
|
||||
<th style="width:12%">VK-Preis</th>
|
||||
<th style="width:13%">Ankauf?</th>
|
||||
<th style="width:18%">Modus</th>
|
||||
<th style="width:12%">Wert</th>
|
||||
<th style="width:15%">Ankaufspreis</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($items_to_edit as $item):
|
||||
$s_enabled = !empty($item->sell_enabled);
|
||||
$s_mode = $item->sell_price_mode ?? 'percent';
|
||||
$s_value = $item->sell_price_value ?? 80;
|
||||
?>
|
||||
<tr id="sell_row_<?php echo $item->id; ?>">
|
||||
<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_sell_enabled[<?php echo $item->id; ?>]"
|
||||
value="1"
|
||||
<?php checked($s_enabled); ?>
|
||||
onchange="wisUpdateSellRow(<?php echo $item->id; ?>, <?php echo intval($item->price); ?>)">
|
||||
</td>
|
||||
<td>
|
||||
<select name="item_sell_mode[<?php echo $item->id; ?>]"
|
||||
id="sell_mode_<?php echo $item->id; ?>"
|
||||
onchange="wisUpdateSellRow(<?php echo $item->id; ?>, <?php echo intval($item->price); ?>)"
|
||||
style="width:100%">
|
||||
<option value="percent" <?php selected($s_mode, 'percent'); ?>>% vom VK</option>
|
||||
<option value="minus" <?php selected($s_mode, 'minus'); ?>>VK minus</option>
|
||||
<option value="fixed" <?php selected($s_mode, 'fixed'); ?>>Fixpreis</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input type="number"
|
||||
name="item_sell_value[<?php echo $item->id; ?>]"
|
||||
id="sell_value_<?php echo $item->id; ?>"
|
||||
value="<?php echo esc_attr($s_value); ?>"
|
||||
min="0" style="width:70px"
|
||||
oninput="wisUpdateSellRow(<?php echo $item->id; ?>, <?php echo intval($item->price); ?>)">
|
||||
</td>
|
||||
<td id="sell_preview_<?php echo $item->id; ?>" style="color:#0073aa;font-weight:bold;">
|
||||
<?php
|
||||
if ($s_enabled) {
|
||||
if ($s_mode === 'percent') $sp = round($item->price * $s_value / 100, 2);
|
||||
elseif ($s_mode === 'minus') $sp = max(0, $item->price - $s_value);
|
||||
else $sp = max(0, $s_value);
|
||||
echo number_format($sp, 2) . ' ' . esc_html($currency);
|
||||
} else {
|
||||
echo '<span style="color:#aaa">–</span>';
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script>
|
||||
function wisUpdateSellRow(id, vk) {
|
||||
var enabled = document.querySelector('input[name="item_sell_enabled[' + id + ']"]').checked;
|
||||
var mode = document.getElementById('sell_mode_' + id).value;
|
||||
var val = parseFloat(document.getElementById('sell_value_' + id).value) || 0;
|
||||
var preview = document.getElementById('sell_preview_' + id);
|
||||
if (!enabled) { preview.innerHTML = '<span style="color:#aaa">–</span>'; return; }
|
||||
var price = 0;
|
||||
if (mode === 'percent') price = Math.max(0, Math.round(vk * val) / 100);
|
||||
else if (mode === 'minus') price = Math.max(0, vk - val);
|
||||
else price = Math.max(0, val);
|
||||
preview.textContent = price.toFixed(2) + ' <?php echo esc_js($currency); ?>';
|
||||
}
|
||||
// Initiale Vorschau für alle Zeilen
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
<?php foreach ($items_to_edit as $item): ?>
|
||||
wisUpdateSellRow(<?php echo $item->id; ?>, <?php echo intval($item->price); ?>);
|
||||
<?php endforeach; ?>
|
||||
});
|
||||
</script>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</table>
|
||||
|
||||
@@ -1469,72 +1675,11 @@ class WIS_Admin {
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['wis_save_item'])) {
|
||||
check_admin_referer('wis_item_form');
|
||||
global $wpdb;
|
||||
|
||||
// item_id je nach Typ ermitteln
|
||||
$item_type = sanitize_text_field($_POST['item_type'] ?? 'minecraft');
|
||||
if ($item_type === 'fly') {
|
||||
$resolved_item_id = sanitize_text_field($_POST['fly_duration'] ?? 'fly_5min');
|
||||
} elseif ($item_type === 'rank') {
|
||||
$rank_id = preg_replace('/[^a-z0-9_\-]/', '', strtolower($_POST['rank_id'] ?? 'vip'));
|
||||
$lp_group = preg_replace('/[^a-zA-Z0-9_\-]/', '', $_POST['lp_group'] ?? $rank_id);
|
||||
$default_group = preg_replace('/[^a-zA-Z0-9_\-]/', '', $_POST['default_group'] ?? 'default');
|
||||
$rank_days = max(0, intval($_POST['rank_days'] ?? 30));
|
||||
if (empty($lp_group)) $lp_group = $rank_id;
|
||||
if (empty($default_group)) $default_group = 'default';
|
||||
// Format: rank_{rank_id}_{lp_group}_{default_group}_{days}
|
||||
$resolved_item_id = 'rank_' . $rank_id . '_' . $lp_group . '_' . $default_group . '_' . $rank_days;
|
||||
} else {
|
||||
$resolved_item_id = sanitize_text_field($_POST['item_id'] ?? '');
|
||||
}
|
||||
|
||||
// Pflichtfeld-Prüfung
|
||||
if (empty($resolved_item_id) || empty(trim($_POST['name'] ?? ''))) {
|
||||
echo '<div class="error"><p>❌ Name und Item-ID sind Pflichtfelder.</p></div>';
|
||||
} else {
|
||||
$data = [
|
||||
'item_id' => $resolved_item_id,
|
||||
'name' => sanitize_text_field($_POST['name']),
|
||||
'description' => sanitize_textarea_field($_POST['description'] ?? ''),
|
||||
'price' => intval($_POST['price'] ?? 0),
|
||||
'offer_price' => intval($_POST['offer_price'] ?? 0),
|
||||
'is_offer' => isset($_POST['is_offer']) ? 1 : 0,
|
||||
'servers' => isset($_POST['servers']) ? json_encode(array_map('sanitize_text_field', $_POST['servers'])) : '[]',
|
||||
'categories' => isset($_POST['categories']) ? json_encode(array_map('sanitize_text_field', $_POST['categories'])) : '[]',
|
||||
'custom_image_url' => esc_url_raw($_POST['custom_image_url'] ?? ''),
|
||||
'status' => (intval($_POST['price'] ?? 0) > 0 || $item_type === 'fly' || $item_type === 'rank') ? 'publish' : 'draft',
|
||||
];
|
||||
|
||||
if (isset($_GET['edit'])) {
|
||||
$result = WIS_DB::update_item(intval($_GET['edit']), $data);
|
||||
if ($result !== false) {
|
||||
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . intval($_GET['edit']) . '&saved=1'));
|
||||
exit;
|
||||
} else {
|
||||
echo '<div class="error"><p>❌ Fehler beim Speichern: ' . esc_html($wpdb->last_error) . '</p></div>';
|
||||
}
|
||||
} else {
|
||||
// Prüfen ob item_id bereits existiert → direkt zum Edit weiterleiten
|
||||
$existing = WIS_DB::get_item_by_item_id($resolved_item_id);
|
||||
if ($existing) {
|
||||
// Bestehenden Artikel aktualisieren statt Duplikat-Fehler
|
||||
WIS_DB::update_item($existing->id, $data);
|
||||
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . $existing->id . '&saved=1'));
|
||||
exit;
|
||||
}
|
||||
$result = WIS_DB::insert_item($data);
|
||||
if ($result) {
|
||||
$new_id = $wpdb->insert_id;
|
||||
wp_redirect(admin_url('admin.php?page=wis_items&edit=' . $new_id . '&created=1'));
|
||||
exit;
|
||||
} else {
|
||||
$err = $wpdb->last_error ?: 'Unbekannter Fehler.';
|
||||
echo '<div class="error"><p>❌ Fehler beim Erstellen: ' . esc_html($err) . '</p></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fehlermeldung aus handle_save_item() anzeigen (falls Validierung fehlschlug)
|
||||
$save_error = get_transient('wis_save_error_' . get_current_user_id());
|
||||
if ($save_error) {
|
||||
delete_transient('wis_save_error_' . get_current_user_id());
|
||||
echo '<div class="error"><p>' . esc_html($save_error) . '</p></div>';
|
||||
}
|
||||
|
||||
if (isset($_GET['created'])) {
|
||||
@@ -1580,21 +1725,24 @@ class WIS_Admin {
|
||||
// Format: rank_{rank_id}_{lp_group}_{default_group}_{days}
|
||||
// Fallback für altes Format rank_{rank_id}_{days}
|
||||
$is_rank_old = !$is_rank && $item && preg_match('/^rank_(.+)_(\d+)$/', $item->item_id, $rm_old);
|
||||
$is_fly_abo = $item && preg_match('/^fly_abo_(\d+)$/', $item->item_id, $abo_m);
|
||||
$cur_abo_days = $is_fly_abo ? intval($abo_m[1]) : 30;
|
||||
$cur_rank_id = $is_rank ? $rm[1] : ($is_rank_old ? $rm_old[1] : 'vip');
|
||||
$cur_lp_group = $is_rank ? $rm[2] : $cur_rank_id;
|
||||
$cur_default_group = $is_rank ? $rm[3] : 'default';
|
||||
$cur_rank_days = $is_rank ? intval($rm[4]) : ($is_rank_old ? intval($rm_old[2]) : 30);
|
||||
$cur_label = $item ? esc_attr($item->name) : '';
|
||||
$detected_type = $is_fly ? 'fly' : (($is_rank || $is_rank_old) ? 'rank' : 'minecraft');
|
||||
$detected_type = $is_fly ? 'fly' : (($is_rank || $is_rank_old) ? 'rank' : ($is_fly_abo ? 'fly_abo' : 'minecraft'));
|
||||
?>
|
||||
<select id="item_type" name="item_type" onchange="wisToggleItemType(this.value)" style="margin-bottom:8px;">
|
||||
<option value="minecraft" <?php echo $detected_type === 'minecraft' ? 'selected' : ''; ?>>Minecraft Item</option>
|
||||
<option value="fly" <?php echo $detected_type === 'fly' ? 'selected' : ''; ?>>✈ Fly-Gutschein</option>
|
||||
<option value="rank" <?php echo $detected_type === 'rank' ? 'selected' : ''; ?>>👑 Rang (LuckPerms)</option>
|
||||
<option value="fly_abo" <?php echo $detected_type === 'fly_abo' ? 'selected' : ''; ?>>✈ Fly-Abo (tägl. Limit)</option>
|
||||
</select>
|
||||
|
||||
<div id="wis_item_minecraft">
|
||||
<input type="text" id="item_id" name="item_id" value="<?php echo ($item && !$is_fly && !$is_rank) ? esc_attr($item->item_id) : ''; ?>" class="regular-text" placeholder="minecraft:diamond">
|
||||
<input type="text" id="item_id" name="item_id" value="<?php echo ($item && !$is_fly && !$is_rank && !$is_fly_abo) ? esc_attr($item->item_id) : ''; ?>" class="regular-text" placeholder="minecraft:diamond">
|
||||
<p class="description">Z.B.: minecraft:diamond (Kategorien werden automatisch zugewiesen)</p>
|
||||
</div>
|
||||
|
||||
@@ -1641,11 +1789,31 @@ class WIS_Admin {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="wis_item_fly_abo" style="display:none;">
|
||||
<table style="border-collapse:collapse;">
|
||||
<tr>
|
||||
<td style="padding:4px 10px 4px 0;"><label for="abo_days"><strong>Laufzeit (Tage):</strong></label></td>
|
||||
<td>
|
||||
<input type="number" id="abo_days" name="abo_days"
|
||||
value="<?php echo esc_attr($cur_abo_days); ?>"
|
||||
min="1" style="width:80px;">
|
||||
<span style="margin-left:6px; color:#666;">Tage</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p class="description" style="margin-top:6px;">
|
||||
Spieler erhalten täglich bis zu <strong>6 Stunden</strong> Fly-Zeit (konfigurierbar per <code>fly-abo.max-daily-hours</code> im Plugin).<br>
|
||||
Mehrfachkauf verlängert das bestehende Abo kumulativ.<br>
|
||||
Gespeicherte Item-ID: <code>fly_abo_{tage}</code> – z.B. <code>fly_abo_30</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function wisToggleItemType(val) {
|
||||
document.getElementById('wis_item_minecraft').style.display = (val === 'minecraft') ? '' : 'none';
|
||||
document.getElementById('wis_item_fly').style.display = (val === 'fly') ? '' : 'none';
|
||||
document.getElementById('wis_item_rank').style.display = (val === 'rank') ? '' : 'none';
|
||||
document.getElementById('wis_item_fly_abo').style.display = (val === 'fly_abo') ? '' : 'none';
|
||||
// item_id Pflichtfeld nur bei Minecraft-Items
|
||||
document.getElementById('item_id').required = (val === 'minecraft');
|
||||
}
|
||||
@@ -1702,6 +1870,56 @@ class WIS_Admin {
|
||||
<p class="description">Optional: Wenn gesetzt, wird der normale Preis durchgestrichen</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="sell_enabled">Ankauf aktivieren</label></th>
|
||||
<td>
|
||||
<label>
|
||||
<input type="checkbox" id="sell_enabled" name="sell_enabled" value="1"
|
||||
<?php echo ($item && !empty($item->sell_enabled)) ? 'checked' : ''; ?>
|
||||
onchange="wisToggleSell(this.checked)">
|
||||
Spieler können dieses Item an den Shop verkaufen
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="wis_sell_row" <?php echo ($item && !empty($item->sell_enabled)) ? '' : 'style="display:none"'; ?>>
|
||||
<th><label for="sell_price_mode">Ankaufspreis</label></th>
|
||||
<td>
|
||||
<select id="sell_price_mode" name="sell_price_mode" onchange="wisUpdateSellPreview()">
|
||||
<option value="percent" <?php echo ($item && $item->sell_price_mode === 'percent') ? 'selected' : ''; ?>>% vom Verkaufspreis</option>
|
||||
<option value="minus" <?php echo ($item && $item->sell_price_mode === 'minus') ? 'selected' : ''; ?>>Verkaufspreis minus fixer Betrag</option>
|
||||
<option value="fixed" <?php echo ($item && $item->sell_price_mode === 'fixed') ? 'selected' : ''; ?>>Fixer Preis</option>
|
||||
</select>
|
||||
|
||||
<input type="number" id="sell_price_value" name="sell_price_value" min="0"
|
||||
value="<?php echo $item ? esc_attr($item->sell_price_value ?? 80) : '80'; ?>"
|
||||
style="width:80px" oninput="wisUpdateSellPreview()">
|
||||
<span id="wis_sell_preview" style="margin-left:8px;color:#0073aa;"></span>
|
||||
<p class="description">
|
||||
Beispiele: 80 % → 80 % des VK-Preises |
|
||||
Modus «minus 10» → VK-Preis − 10 |
|
||||
Fixer Preis 15 → immer 15 <?php echo esc_html($currency); ?>
|
||||
</p>
|
||||
<script>
|
||||
function wisToggleSell(on) {
|
||||
document.getElementById('wis_sell_row').style.display = on ? '' : 'none';
|
||||
if (on) wisUpdateSellPreview();
|
||||
}
|
||||
function wisUpdateSellPreview() {
|
||||
var vk = parseFloat(document.getElementById('price').value) || 0;
|
||||
var mode = document.getElementById('sell_price_mode').value;
|
||||
var val = parseFloat(document.getElementById('sell_price_value').value) || 0;
|
||||
var price = 0;
|
||||
if (mode === 'percent') price = Math.max(0, Math.round(vk * val) / 100);
|
||||
else if (mode === 'minus') price = Math.max(0, vk - val);
|
||||
else price = Math.max(0, val);
|
||||
document.getElementById('wis_sell_preview').textContent =
|
||||
'→ Ankaufspreis: ' + price.toFixed(2) + ' <?php echo esc_js($currency); ?>';
|
||||
}
|
||||
document.getElementById('price').addEventListener('input', wisUpdateSellPreview);
|
||||
wisUpdateSellPreview();
|
||||
</script>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Markierungen</th>
|
||||
<td>
|
||||
@@ -1854,6 +2072,7 @@ class WIS_Admin {
|
||||
<option value="server">Server zuweisen</option>
|
||||
<option value="category">Kategorie zuweisen</option>
|
||||
<option value="status">Aktivieren/Deaktivieren</option>
|
||||
<option value="sell">Ankauf konfigurieren</option>
|
||||
</select>
|
||||
<input type="submit" name="wis_bulk_apply" class="button action" value="Anwenden">
|
||||
</div>
|
||||
@@ -1916,6 +2135,8 @@ class WIS_Admin {
|
||||
} elseif (preg_match('/^rank_(.+)_(\d+)$/', $item->item_id, $rm2)) {
|
||||
$rd = intval($rm2[2]);
|
||||
echo '<span title="' . esc_attr($item->item_id) . '">👑 <code>' . esc_html($rm2[1]) . '</code> — ' . ($rd === 0 ? '<em>dauerhaft</em>' : esc_html($rd) . ' Tage') . '</span>';
|
||||
} elseif (preg_match('/^fly_abo_(\d+)$/', $item->item_id, $abo_m2)) {
|
||||
echo '<span title="' . esc_attr($item->item_id) . '">✈ Fly-Abo — ' . esc_html($abo_m2[1]) . ' Tage</span>';
|
||||
} else {
|
||||
echo '<code>' . esc_html($item->item_id) . '</code>';
|
||||
}
|
||||
@@ -2669,6 +2890,158 @@ class WIS_API {
|
||||
'callback' => [self::class, 'get_orders_history'],
|
||||
'permission_callback' => [WIS_Activator::class, 'spigot_permission'],
|
||||
]);
|
||||
|
||||
// Ankauf-Endpunkte (ab v6.5)
|
||||
register_rest_route('wis/v1', '/sell_items', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [self::class, 'get_sell_items'],
|
||||
'permission_callback' => [WIS_Activator::class, 'spigot_permission'],
|
||||
]);
|
||||
register_rest_route('wis/v1', '/sell_item', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [self::class, 'process_sell'],
|
||||
'permission_callback' => [WIS_Activator::class, 'spigot_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
// =========================================================
|
||||
// ANKAUF – Endpunkte (ab v6.5)
|
||||
// =========================================================
|
||||
|
||||
/**
|
||||
* GET /wis/v1/sell_items?server=<slug>
|
||||
* Liefert alle Items die auf dem angegebenen Server ankaufbar sind.
|
||||
*/
|
||||
public static function get_sell_items($request) {
|
||||
global $wpdb;
|
||||
$server = sanitize_text_field($request->get_param('server') ?? '');
|
||||
$currency = get_option('wis_currency_name', 'Coins');
|
||||
$table = $wpdb->prefix . 'wis_items';
|
||||
|
||||
$items = $wpdb->get_results(
|
||||
"SELECT id, item_id, name, price, sell_price_mode, sell_price_value
|
||||
FROM $table
|
||||
WHERE status = 'publish' AND sell_enabled = 1"
|
||||
);
|
||||
|
||||
$result = [];
|
||||
foreach ($items as $item) {
|
||||
// Ankaufspreis berechnen
|
||||
$sell_price = self::calc_sell_price(
|
||||
(int) $item->price,
|
||||
$item->sell_price_mode,
|
||||
(int) $item->sell_price_value
|
||||
);
|
||||
if ($sell_price <= 0) continue;
|
||||
|
||||
// Server-Filter
|
||||
if (!empty($server)) {
|
||||
$servers = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT servers FROM $table WHERE id = %d", $item->id
|
||||
));
|
||||
$srv_list = json_decode($servers, true) ?: [];
|
||||
if (!empty($srv_list) && !in_array(strtolower($server), array_map('strtolower', $srv_list))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'item_id' => $item->item_id,
|
||||
'name' => $item->name,
|
||||
'buy_price' => (int) $item->price,
|
||||
'sell_price' => $sell_price,
|
||||
];
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'items' => $result,
|
||||
'currency' => $currency,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /wis/v1/sell_item
|
||||
* Body: { "player": "Steve", "server": "survival", "item_id": "minecraft:diamond", "quantity": 5 }
|
||||
* Antwortet mit dem Betrag der gutgeschrieben werden soll.
|
||||
*/
|
||||
public static function process_sell($request) {
|
||||
global $wpdb;
|
||||
$player = sanitize_text_field($request->get_param('player') ?? '');
|
||||
$server = sanitize_text_field($request->get_param('server') ?? '');
|
||||
$item_id = sanitize_text_field($request->get_param('item_id') ?? '');
|
||||
$quantity = max(1, intval($request->get_param('quantity') ?? 1));
|
||||
|
||||
if (!$player || !$item_id) {
|
||||
return new WP_REST_Response(['success' => false, 'message' => 'Fehlende Parameter'], 400);
|
||||
}
|
||||
|
||||
$table = $wpdb->prefix . 'wis_items';
|
||||
|
||||
// Item suchen (case-insensitive, mit/ohne minecraft: prefix)
|
||||
$clean_id = strtolower(str_replace('minecraft:', '', $item_id));
|
||||
$row = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT id, item_id, name, price, sell_enabled, sell_price_mode, sell_price_value, servers
|
||||
FROM $table
|
||||
WHERE sell_enabled = 1
|
||||
AND (LOWER(item_id) = %s OR LOWER(REPLACE(item_id,'minecraft:','')) = %s)
|
||||
LIMIT 1",
|
||||
strtolower($item_id), $clean_id
|
||||
));
|
||||
|
||||
if (!$row) {
|
||||
return new WP_REST_Response(['success' => false, 'message' => 'Item nicht ankaufbar'], 404);
|
||||
}
|
||||
|
||||
$sell_price = self::calc_sell_price(
|
||||
(int) $row->price,
|
||||
$row->sell_price_mode,
|
||||
(int) $row->sell_price_value
|
||||
);
|
||||
|
||||
if ($sell_price <= 0) {
|
||||
return new WP_REST_Response(['success' => false, 'message' => 'Ankaufspreis ist 0'], 422);
|
||||
}
|
||||
|
||||
$total = round($sell_price * $quantity, 2);
|
||||
|
||||
// Sell-Log schreiben
|
||||
$wpdb->insert($wpdb->prefix . 'wis_sell_log', [
|
||||
'player_name' => $player,
|
||||
'server' => $server,
|
||||
'item_id' => $row->item_id,
|
||||
'item_name' => $row->name,
|
||||
'quantity' => $quantity,
|
||||
'price_per_item' => $sell_price,
|
||||
'total_paid' => $total,
|
||||
]);
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'item_id' => $row->item_id,
|
||||
'item_name' => $row->name,
|
||||
'quantity' => $quantity,
|
||||
'price_per_item' => $sell_price,
|
||||
'total' => $total,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet den Ankaufspreis aus Verkaufspreis + Modus.
|
||||
* mode = "percent" → value ist ein Prozentwert des VK-Preises (z.B. 80 = 80 %)
|
||||
* mode = "fixed" → value ist ein absoluter Festpreis
|
||||
* mode = "minus" → value ist ein absoluter Abzug vom VK-Preis
|
||||
*/
|
||||
private static function calc_sell_price(int $buy_price, string $mode, int $value): float {
|
||||
switch ($mode) {
|
||||
case 'fixed':
|
||||
return max(0, $value);
|
||||
case 'minus':
|
||||
return max(0, $buy_price - $value);
|
||||
case 'percent':
|
||||
default:
|
||||
return max(0, round($buy_price * $value / 100, 2));
|
||||
}
|
||||
}
|
||||
|
||||
public static function get_shop_items($request) {
|
||||
@@ -3068,6 +3441,16 @@ class WIS_API {
|
||||
];
|
||||
}
|
||||
$title_parts[] = $item['qty'] . 'x ' . $item['title'];
|
||||
} elseif (preg_match('/^fly_abo_(\d+)$/', $item_id, $abo_m)) {
|
||||
// Fly-Abo: qty > 1 verlängert das Abo (addiert Tage)
|
||||
$abo_days = intval($abo_m[1]);
|
||||
$total_days = $abo_days * intval($item['qty']);
|
||||
$commands_payload[] = [
|
||||
'type' => 'fly_abo',
|
||||
'days' => $total_days,
|
||||
'label' => $item['title'],
|
||||
];
|
||||
$title_parts[] = $item['qty'] . 'x ' . $item['title'];
|
||||
} else {
|
||||
// Normales Item
|
||||
$items_payload[] = [
|
||||
@@ -3392,8 +3775,12 @@ class WIS_Shortcode {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.wis-cat-btn').forEach(function(b){ b.classList.remove('active'); });
|
||||
btn.classList.add('active');
|
||||
currentCat = btn.getAttribute('data-cat');
|
||||
currentPage = 1;
|
||||
currentCat = btn.getAttribute('data-cat');
|
||||
currentPage = 1;
|
||||
// Suche beim Kategoriewechsel leeren
|
||||
currentSearch = '';
|
||||
var searchInput = document.getElementById('wis-search');
|
||||
if (searchInput) searchInput.value = '';
|
||||
loadItems(1);
|
||||
});
|
||||
});
|
||||
@@ -3406,6 +3793,18 @@ class WIS_Shortcode {
|
||||
searchTimer = setTimeout(function() {
|
||||
currentSearch = searchInput.value.trim();
|
||||
currentPage = 1;
|
||||
// Bei aktiver Suche: Kategorie-Filter deaktivieren (kategorienübergreifend suchen)
|
||||
if (currentSearch) {
|
||||
document.querySelectorAll('.wis-cat-btn').forEach(function(b){ b.classList.remove('active'); });
|
||||
currentCat = '';
|
||||
} else {
|
||||
// Suche geleert: erste Kategorie wieder aktivieren
|
||||
var firstBtn = document.querySelector('.wis-cat-btn');
|
||||
if (firstBtn) {
|
||||
firstBtn.classList.add('active');
|
||||
currentCat = firstBtn.getAttribute('data-cat');
|
||||
}
|
||||
}
|
||||
loadItems(1);
|
||||
}, 350);
|
||||
});
|
||||
@@ -3860,6 +4259,7 @@ register_activation_hook(__FILE__, [WIS_Activator::class, 'activate']);
|
||||
register_deactivation_hook(__FILE__, [WIS_Activator::class, 'deactivate']);
|
||||
|
||||
add_action('admin_menu', [WIS_Admin::class, 'register_menu']);
|
||||
add_action('admin_init', [WIS_Admin::class, 'handle_save_item']);
|
||||
add_action('rest_api_init',[WIS_API::class, 'register_routes']);
|
||||
add_action('init', [WIS_Shortcode::class, 'register']);
|
||||
add_action('widgets_init', function() {
|
||||
|
||||
Reference in New Issue
Block a user