Files
WP-Business-Forum/includes/class-forum-abo.php
2026-05-05 21:18:59 +02:00

535 lines
28 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
/**
* WBF_Abo — Abo-Verwaltung im Forum-Profil
* Fly-Abo: WordPress DB (wp_wis_fly_abo_subs)
* Plot-Abo/Slots: Spigot MySQL (optional)
*/
if ( ! defined( 'ABSPATH' ) ) exit;
class WBF_Abo {
private static function get_spigot_db(): ?mysqli {
$s = function_exists('wbf_get_settings') ? wbf_get_settings() : [];
$host = $s['spigot_mysql_host'] ?? '';
$port = (int)($s['spigot_mysql_port'] ?? 3306);
$db = $s['spigot_mysql_database'] ?? '';
$user = $s['spigot_mysql_username'] ?? '';
$pass = $s['spigot_mysql_password'] ?? '';
if ( empty($db) || empty($user) ) return null;
$conn = @new mysqli($host, $user, $pass, $db, $port);
if ( $conn->connect_errno ) return null;
$conn->set_charset('utf8mb4');
return $conn;
}
public static function get_all_abos( string $mc_name ): array {
global $wpdb;
$result = ['fly_used_sec' => 0, 'plot_extra' => 0];
// Fly-Abo aus WordPress DB
$fly = $wpdb->get_row( $wpdb->prepare(
"SELECT label, price, status, cancelled, cancelled_at, expires_at, renewal_count
FROM {$wpdb->prefix}wis_fly_abo_subs
WHERE player_name = %s ORDER BY id DESC LIMIT 1",
$mc_name
), ARRAY_A );
if ( $fly ) {
$expires = new DateTime( $fly['expires_at'] );
$cancelled = (int)$fly['cancelled'] === 1;
$is_active = $fly['status'] === 'active' && $expires > new DateTime();
$result['fly_abo'] = [
'label' => $fly['label'],
'monthly_price' => (int)$fly['price'],
'cancelled' => $cancelled ? 1 : 0,
'cancellation_reason' => $cancelled ? 'user' : null,
'next_billing' => ( new DateTime('first day of next month') )->format('d.m.Y'),
'period_end' => $expires->format('d.m.Y'),
'is_active' => $is_active,
];
}
// Item-Abos aus WordPress DB (wis_item_abo_subs)
$item_abos = $wpdb->get_results( $wpdb->prepare(
"SELECT id, label, item_id, daily_qty, cancelled,
DATE_FORMAT(expires_at, '%%d.%%m.%%Y') AS expires_at_fmt
FROM {$wpdb->prefix}wis_item_abo_subs
WHERE player_name = %s
AND status = 'active'
AND expires_at > NOW()
ORDER BY created_at ASC",
$mc_name
), ARRAY_A );
if ( $item_abos ) {
foreach ( $item_abos as &$row ) {
$row['id'] = (int) $row['id'];
$row['daily_qty'] = (int) $row['daily_qty'];
$row['cancelled'] = (bool) $row['cancelled'];
}
unset($row);
$result['item_abos'] = $item_abos;
} else {
$result['item_abos'] = [];
}
// Spigot MySQL für Fly-Usage + Plot-Daten
$conn = self::get_spigot_db();
if ( $conn ) {
foreach ([
["SELECT used_sec FROM wis_fly_abo_usage WHERE player_name = ? AND usage_date = CURDATE()", 'fly_used_sec', 'used_sec'],
] as [$sql, $key, $field]) {
$stmt = $conn->prepare($sql);
if ($stmt) { $stmt->bind_param('s', $mc_name); $stmt->execute();
$r = $stmt->get_result()->fetch_assoc();
$result[$key] = $r ? (int)$r[$field] : 0; $stmt->close(); }
}
$stmt = $conn->prepare(
"SELECT label, abo_slots, monthly_price, cancelled, cancellation_reason,
DATE_FORMAT(next_billing_date,'%d.%m.%Y') AS next_billing,
DATE_FORMAT(period_end,'%d.%m.%Y') AS period_end
FROM wis_plot_abos WHERE player_name = ? AND period_end >= CURDATE()"
);
if ($stmt) { $stmt->bind_param('s', $mc_name); $stmt->execute();
$r = $stmt->get_result()->fetch_assoc();
if ($r) $result['plot_abo'] = $r; $stmt->close(); }
$stmt = $conn->prepare("SELECT extra_slots FROM wis_plot_extra_slots WHERE player_name = ?");
if ($stmt) { $stmt->bind_param('s', $mc_name); $stmt->execute();
$r = $stmt->get_result()->fetch_assoc();
$result['plot_extra'] = $r ? (int)$r['extra_slots'] : 0; $stmt->close(); }
$conn->close();
}
return $result;
}
public static function cancel_abo( string $mc_name, string $type, int $abo_id = 0 ): bool {
global $wpdb;
if ( $type === 'fly_abo' ) {
$rows = $wpdb->update(
$wpdb->prefix . 'wis_fly_abo_subs',
['cancelled' => 1, 'cancelled_at' => current_time('mysql')],
['player_name' => $mc_name, 'status' => 'active', 'cancelled' => 0]
);
return $rows > 0;
}
if ( $type === 'plot_abo' ) {
$conn = self::get_spigot_db();
if (!$conn) return false;
$stmt = $conn->prepare("UPDATE wis_plot_abos SET cancelled=1, cancellation_reason='user', period_end=LAST_DAY(CURDATE()) WHERE player_name=? AND period_end>=CURDATE() AND cancelled=0");
if (!$stmt) { $conn->close(); return false; }
$stmt->bind_param('s', $mc_name); $stmt->execute();
$a = $stmt->affected_rows; $stmt->close(); $conn->close();
return $a > 0;
}
if ( $type === 'item_abo' ) {
if ( $abo_id <= 0 ) return false;
// Sicherheitscheck: Abo muss dem Spieler gehören
$existing = $wpdb->get_row( $wpdb->prepare(
"SELECT id, label FROM {$wpdb->prefix}wis_item_abo_subs
WHERE id = %d AND player_name = %s AND status = 'active' AND cancelled = 0",
$abo_id, $mc_name
) );
if ( ! $existing ) return false;
$rows = $wpdb->update(
$wpdb->prefix . 'wis_item_abo_subs',
['cancelled' => 1, 'cancelled_at' => current_time('mysql')],
['id' => $abo_id]
);
return $rows > 0;
}
return false;
}
public static function format_seconds( int $sec ): string {
if ($sec >= 3600) { $h = intdiv($sec,3600); $m = intdiv($sec%3600,60); return $h.'h'.($m>0?' '.$m.'min':''); }
if ($sec >= 60) { return intdiv($sec,60).'min'; }
return $sec.'s';
}
public static function render_tab( string $mc_name, int $profile_id ): string {
$abos = self::get_all_abos($mc_name);
$fly_abo = $abos['fly_abo'] ?? null;
$plot_abo = $abos['plot_abo'] ?? null;
$item_abos = $abos['item_abos'] ?? [];
$plot_extra = (int)($abos['plot_extra'] ?? 0);
$fly_used = (int)($abos['fly_used_sec'] ?? 0);
$fly_max = (int) get_option('wis_fly_abo_max_daily_hours', 6) * 3600;
$currency = esc_html(get_option('wis_currency_name', '$'));
$shop_url = esc_url(get_option('wis_shop_url', home_url('/ingame-shop/')));
$nonce = wp_create_nonce('wbf_nonce');
$ajax_url = admin_url('admin-ajax.php');
$has_any = $fly_abo || $plot_abo || $plot_extra > 0 || !empty($item_abos);
ob_start(); ?>
<style>
.wbf-abo-wrap { display:flex; flex-direction:column; gap:1rem; }
.wbf-abo-empty { text-align:center; padding:2.5rem 1rem; color:var(--c-muted); }
.wbf-abo-empty__icon { font-size:2.5rem; margin-bottom:.5rem; }
.wbf-abo-item { border-radius:12px; overflow:hidden; border:1px solid var(--c-border); background:var(--c-bg); }
.wbf-abo-item--active { border-left:4px solid var(--c-success); }
.wbf-abo-item--cancelled { border-left:4px solid var(--c-danger); opacity:.9; }
.wbf-abo-item--info { border-left:4px solid var(--c-primary); }
.wbf-abo-item__head { display:flex; align-items:center; gap:.9rem; padding:.85rem 1.1rem; background:var(--c-bg-alt); border-bottom:1px solid var(--c-border); }
.wbf-abo-item__icon-wrap { flex-shrink:0; width:2.4rem; height:2.4rem; border-radius:50%; background:var(--c-border); display:flex; align-items:center; justify-content:center; font-size:1.15rem; }
.wbf-abo-item__info { flex:1; min-width:0; }
.wbf-abo-item__name { font-weight:700; font-size:.95rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.wbf-abo-item__price { font-size:.8rem; color:var(--c-muted); margin-top:.1rem; }
.wbf-abo-badge { display:inline-flex; align-items:center; gap:.3rem; font-size:.75rem; font-weight:600; padding:.22rem .65rem; border-radius:20px; white-space:nowrap; }
.wbf-abo-badge--active { background:rgba(34,197,94,.15); color:var(--c-success); }
.wbf-abo-badge--cancelled { background:rgba(239,68,68,.13); color:var(--c-danger); }
.wbf-abo-badge--expired { background:rgba(100,100,100,.15); color:var(--c-muted); }
.wbf-abo-item__body { padding:.85rem 1.1rem; }
.wbf-abo-grid { display:grid; grid-template-columns:1fr 1fr; gap:.45rem .9rem; }
.wbf-abo-grid__item--full { grid-column:1/-1; }
.wbf-abo-grid__label { display:block; font-size:.78rem; color:var(--c-muted); margin-bottom:.15rem; }
.wbf-abo-grid__value { font-size:.88rem; font-weight:500; }
.wbf-abo-grid__value--warn { color:var(--c-danger); }
.wbf-abo-progress { margin-top:.75rem; }
.wbf-abo-progress__track { height:6px; background:var(--c-border); border-radius:3px; overflow:hidden; }
.wbf-abo-progress__fill { height:100%; border-radius:3px; transition:width .4s; }
.wbf-abo-progress__labels { display:flex; justify-content:space-between; font-size:.73rem; color:var(--c-muted); margin-top:.25rem; }
.wbf-abo-item__foot { display:flex; align-items:center; gap:.75rem; flex-wrap:wrap; padding:.7rem 1.1rem; border-top:1px solid var(--c-border); background:var(--c-bg-alt); }
.wbf-abo-item__foot--cancelled { background:transparent; }
.wbf-abo-item__hint { font-size:.78rem; color:var(--c-muted); }
.wbf-abo-notice { display:flex; align-items:flex-start; gap:.45rem; font-size:.83rem; padding:.5rem .75rem; border-radius:8px; width:100%; }
.wbf-abo-notice--info { background:rgba(99,102,241,.1); color:var(--c-primary); }
/* Modal */
.wbf-abo-modal-overlay { position:fixed; inset:0; z-index:9999; display:flex; align-items:center; justify-content:center; background:rgba(0,0,0,.55); backdrop-filter:blur(3px); }
.wbf-abo-modal { background:var(--c-bg); border:1px solid var(--c-border); border-radius:14px; width:min(420px,90vw); box-shadow:0 12px 40px rgba(0,0,0,.35); overflow:hidden; }
.wbf-abo-modal__head { display:flex; align-items:center; gap:.5rem; padding:.9rem 1.2rem; background:var(--c-bg-alt); border-bottom:1px solid var(--c-border); font-weight:700; color:var(--c-danger); }
.wbf-abo-modal__body { padding:1.1rem 1.2rem; line-height:1.6; }
.wbf-abo-modal__sub { font-size:.87rem; color:var(--c-muted); margin-top:.5rem; }
.wbf-abo-modal__foot { display:flex; justify-content:flex-end; gap:.6rem; padding:.8rem 1.2rem; background:var(--c-bg-alt); border-top:1px solid var(--c-border); }
@media(max-width:540px) { .wbf-abo-grid { grid-template-columns:1fr; } .wbf-abo-grid__item--full { grid-column:1; } }
</style>
<div class="wbf-abo-wrap">
<?php if (!$has_any): ?>
<div class="wbf-abo-empty">
<div class="wbf-abo-empty__icon">📋</div>
<p>Du hast derzeit keine aktiven Abonnements.</p>
<a href="<?php echo $shop_url; ?>" target="_blank" class="wbf-btn wbf-btn--primary wbf-btn--sm">
<i class="fas fa-shopping-cart"></i> Zum Shop
</a>
</div>
<?php else: ?>
<?php /* FLY-ABO */ if ($fly_abo):
$fa = $fly_abo;
$fcancelled = (int)($fa['cancelled'] ?? 0) === 1;
$factive = !empty($fa['is_active']) && !$fcancelled;
$fly_rem = max(0, $fly_max - $fly_used);
$fly_pct = $fly_max > 0 ? min(100, round(($fly_used / $fly_max) * 100)) : 0;
$pbar_col = $fly_pct >= 90 ? 'var(--c-danger)' : ($fly_pct >= 60 ? '#f59e0b' : 'var(--c-success)');
?>
<div class="wbf-abo-item <?php echo $fcancelled ? 'wbf-abo-item--cancelled' : 'wbf-abo-item--active'; ?>" id="wbf-abo-fly">
<div class="wbf-abo-item__head">
<div class="wbf-abo-item__icon-wrap">✈</div>
<div class="wbf-abo-item__info">
<div class="wbf-abo-item__name"><?php echo esc_html($fa['label']); ?></div>
<div class="wbf-abo-item__price"><?php echo number_format((int)$fa['monthly_price']); ?> <?php echo $currency; ?> / Monat</div>
</div>
<div class="wbf-abo-item__status">
<?php if ($fcancelled): ?>
<span class="wbf-abo-badge wbf-abo-badge--cancelled"><i class="fas fa-clock"></i> Gekündigt</span>
<?php elseif ($factive): ?>
<span class="wbf-abo-badge wbf-abo-badge--active"><i class="fas fa-circle-check"></i> Aktiv</span>
<?php else: ?>
<span class="wbf-abo-badge wbf-abo-badge--expired"><i class="fas fa-ban"></i> Abgelaufen</span>
<?php endif; ?>
</div>
</div>
<div class="wbf-abo-item__body">
<div class="wbf-abo-grid">
<div class="wbf-abo-grid__item">
<span class="wbf-abo-grid__label"><i class="fas fa-calendar-day"></i> Aktiv bis</span>
<span class="wbf-abo-grid__value <?php echo $fcancelled ? 'wbf-abo-grid__value--warn' : ''; ?>"><?php echo esc_html($fa['period_end']); ?></span>
</div>
<?php if ($factive): ?>
<div class="wbf-abo-grid__item">
<span class="wbf-abo-grid__label"><i class="fas fa-rotate"></i> Nächste Abbuchung</span>
<span class="wbf-abo-grid__value"><?php echo esc_html($fa['next_billing']); ?></span>
</div>
<?php endif; ?>
<div class="wbf-abo-grid__item wbf-abo-grid__item--full">
<span class="wbf-abo-grid__label"><i class="fas fa-clock"></i> Fly heute</span>
<span class="wbf-abo-grid__value">
<?php echo self::format_seconds($fly_used); ?> / <?php echo self::format_seconds($fly_max); ?>
&nbsp;<span style="color:<?php echo $fly_rem > 0 ? 'var(--c-success)' : 'var(--c-danger)'; ?>;font-weight:600">(noch <?php echo self::format_seconds($fly_rem); ?>)</span>
</span>
</div>
</div>
<div class="wbf-abo-progress">
<div class="wbf-abo-progress__track">
<div class="wbf-abo-progress__fill" style="width:<?php echo $fly_pct; ?>%;background:<?php echo $pbar_col; ?>"></div>
</div>
<div class="wbf-abo-progress__labels"><span><?php echo $fly_pct; ?>% verbraucht</span><span>Limit: <?php echo self::format_seconds($fly_max); ?>/Tag</span></div>
</div>
</div>
<?php if ($factive): ?>
<div class="wbf-abo-item__foot">
<button class="wbf-btn wbf-btn--danger wbf-btn--sm wbf-abo-cancel-btn"
data-type="fly_abo" data-nonce="<?php echo $nonce; ?>"
data-label="<?php echo esc_attr($fa['label']); ?>"
data-period="<?php echo esc_attr($fa['period_end']); ?>">
<i class="fas fa-xmark"></i> Zum Monatsende kündigen
</button>
<span class="wbf-abo-item__hint">Bleibt bis <?php echo esc_html($fa['period_end']); ?> aktiv</span>
</div>
<?php elseif ($fcancelled): ?>
<div class="wbf-abo-item__foot wbf-abo-item__foot--cancelled">
<div class="wbf-abo-notice wbf-abo-notice--info">
<i class="fas fa-circle-info"></i>
Kündigung vorgemerkt Fly-Abo bleibt bis <strong><?php echo esc_html($fa['period_end']); ?></strong> aktiv.
</div>
<a href="<?php echo $shop_url; ?>" target="_blank" class="wbf-btn wbf-btn--primary wbf-btn--sm" style="white-space:nowrap">
<i class="fas fa-rotate-right"></i> Erneut abonnieren
</a>
</div>
<?php endif; ?>
</div>
<?php endif; // fly_abo ?>
<?php /* PLOT-ABO */ if ($plot_abo):
$pa = $plot_abo;
$pcancelled = (int)($pa['cancelled'] ?? 0) === 1;
$ppayfail = ($pa['cancellation_reason'] ?? '') === 'payment_failed';
?>
<div class="wbf-abo-item <?php echo $pcancelled ? 'wbf-abo-item--cancelled' : 'wbf-abo-item--active'; ?>" id="wbf-abo-plot">
<div class="wbf-abo-item__head">
<div class="wbf-abo-item__icon-wrap">📦</div>
<div class="wbf-abo-item__info">
<div class="wbf-abo-item__name"><?php echo esc_html($pa['label']); ?></div>
<div class="wbf-abo-item__price">+<?php echo (int)$pa['abo_slots']; ?> Slots · <?php echo (int)$pa['monthly_price']; ?> <?php echo $currency; ?>/Monat</div>
</div>
<div class="wbf-abo-item__status">
<?php if ($pcancelled && $ppayfail): ?>
<span class="wbf-abo-badge wbf-abo-badge--cancelled"><i class="fas fa-exclamation-triangle"></i> Zahlung fehlgeschlagen</span>
<?php elseif ($pcancelled): ?>
<span class="wbf-abo-badge wbf-abo-badge--cancelled"><i class="fas fa-clock"></i> Gekündigt</span>
<?php else: ?>
<span class="wbf-abo-badge wbf-abo-badge--active"><i class="fas fa-circle-check"></i> Aktiv</span>
<?php endif; ?>
</div>
</div>
<div class="wbf-abo-item__body">
<div class="wbf-abo-grid">
<div class="wbf-abo-grid__item">
<span class="wbf-abo-grid__label"><i class="fas fa-calendar-day"></i> Aktiv bis</span>
<span class="wbf-abo-grid__value"><?php echo esc_html($pa['period_end']); ?></span>
</div>
<?php if (!$pcancelled): ?>
<div class="wbf-abo-grid__item">
<span class="wbf-abo-grid__label"><i class="fas fa-rotate"></i> Nächste Abbuchung</span>
<span class="wbf-abo-grid__value"><?php echo esc_html($pa['next_billing']); ?></span>
</div>
<?php endif; ?>
</div>
</div>
<?php if (!$pcancelled): ?>
<div class="wbf-abo-item__foot">
<button class="wbf-btn wbf-btn--danger wbf-btn--sm wbf-abo-cancel-btn"
data-type="plot_abo" data-nonce="<?php echo $nonce; ?>"
data-label="<?php echo esc_attr($pa['label']); ?>"
data-period="<?php echo esc_attr($pa['period_end']); ?>">
<i class="fas fa-xmark"></i> Zum Monatsende kündigen
</button>
<span class="wbf-abo-item__hint">Bleibt bis <?php echo esc_html($pa['period_end']); ?> aktiv</span>
</div>
<?php else: ?>
<div class="wbf-abo-item__foot wbf-abo-item__foot--cancelled">
<div class="wbf-abo-notice wbf-abo-notice--info">
<i class="fas fa-circle-info"></i>
Kündigung vorgemerkt Plot-Abo bleibt bis <strong><?php echo esc_html($pa['period_end']); ?></strong> aktiv.
</div>
</div>
<?php endif; ?>
</div>
<?php endif; // plot_abo ?>
<?php /* ITEM-ABOS */ foreach ($item_abos as $idx => $ia):
$ia_nr = $idx + 1;
$ia_id = (int) $ia['id'];
$ia_label = $ia['label'] ?? 'Item-Abo';
$ia_item = $ia['item_id'] ?? '?';
$ia_qty = (int)($ia['daily_qty'] ?? 1);
$ia_expires = $ia['expires_at_fmt'] ?? '?';
$ia_cancelled = !empty($ia['cancelled']);
?>
<div class="wbf-abo-item <?php echo $ia_cancelled ? 'wbf-abo-item--cancelled' : 'wbf-abo-item--active'; ?>" id="wbf-abo-item-<?php echo $ia_id; ?>">
<div class="wbf-abo-item__head">
<div class="wbf-abo-item__icon-wrap">📦</div>
<div class="wbf-abo-item__info">
<div class="wbf-abo-item__name"><?php echo esc_html($ia_label); ?></div>
<div class="wbf-abo-item__price"><?php echo $ia_qty; ?>× täglich · <code style="font-size:.78rem;opacity:.8"><?php echo esc_html($ia_item); ?></code></div>
</div>
<div class="wbf-abo-item__status">
<?php if ($ia_cancelled): ?>
<span class="wbf-abo-badge wbf-abo-badge--cancelled"><i class="fas fa-clock"></i> Gekündigt</span>
<?php else: ?>
<span class="wbf-abo-badge wbf-abo-badge--active"><i class="fas fa-circle-check"></i> Aktiv</span>
<?php endif; ?>
</div>
</div>
<div class="wbf-abo-item__body">
<div class="wbf-abo-grid">
<div class="wbf-abo-grid__item">
<span class="wbf-abo-grid__label"><i class="fas fa-calendar-day"></i> Läuft bis</span>
<span class="wbf-abo-grid__value <?php echo $ia_cancelled ? 'wbf-abo-grid__value--warn' : ''; ?>"><?php echo esc_html($ia_expires); ?></span>
</div>
<div class="wbf-abo-grid__item">
<span class="wbf-abo-grid__label"><i class="fas fa-box-open"></i> Tageslieferung</span>
<span class="wbf-abo-grid__value"><?php echo $ia_qty; ?>× <?php echo esc_html($ia_item); ?></span>
</div>
</div>
</div>
<?php if (!$ia_cancelled): ?>
<div class="wbf-abo-item__foot">
<button class="wbf-btn wbf-btn--danger wbf-btn--sm wbf-abo-cancel-btn"
data-type="item_abo"
data-abo-id="<?php echo $ia_id; ?>"
data-nonce="<?php echo $nonce; ?>"
data-label="<?php echo esc_attr($ia_label); ?>"
data-period="<?php echo esc_attr($ia_expires); ?>">
<i class="fas fa-xmark"></i> Kündigen
</button>
<span class="wbf-abo-item__hint">Bleibt bis <?php echo esc_html($ia_expires); ?> aktiv</span>
</div>
<?php else: ?>
<div class="wbf-abo-item__foot wbf-abo-item__foot--cancelled">
<div class="wbf-abo-notice wbf-abo-notice--info">
<i class="fas fa-circle-info"></i>
Kündigung vorgemerkt Lieferungen laufen bis <strong><?php echo esc_html($ia_expires); ?></strong> weiter.
</div>
</div>
<?php endif; ?>
</div>
<?php endforeach; // item_abos ?>
<?php if ($plot_extra > 0 || $plot_abo): ?>
<div class="wbf-abo-item__head">
<div class="wbf-abo-item__icon-wrap">🗺️</div>
<div class="wbf-abo-item__info">
<div class="wbf-abo-item__name">Plot-Slots Übersicht</div>
<div class="wbf-abo-item__price">Citybuild</div>
</div>
</div>
<div class="wbf-abo-item__body">
<div class="wbf-abo-grid">
<?php if ($plot_extra > 0): ?>
<div class="wbf-abo-grid__item">
<span class="wbf-abo-grid__label"><i class="fas fa-infinity"></i> Permanent gekauft</span>
<span class="wbf-abo-grid__value">+<?php echo $plot_extra; ?> Slots</span>
</div>
<?php endif; ?>
<?php if ($plot_abo): ?>
<div class="wbf-abo-grid__item">
<span class="wbf-abo-grid__label"><i class="fas fa-rotate"></i> Abo-Slots</span>
<span class="wbf-abo-grid__value <?php echo $pcancelled ? 'wbf-abo-grid__value--warn' : ''; ?>">
+<?php echo (int)$pa['abo_slots']; ?> Slots<?php echo $pcancelled ? ' (läuft aus)' : ''; ?>
</span>
</div>
<?php endif; ?>
<div class="wbf-abo-grid__item wbf-abo-grid__item--full" style="border-top:1px solid var(--c-border);margin-top:.4em;padding-top:.6em">
<span class="wbf-abo-grid__label"><i class="fas fa-layer-group"></i> Gesamt Zusatz-Slots</span>
<span class="wbf-abo-grid__value" style="color:var(--c-success);font-weight:700;font-size:1.05em">
+<?php echo $plot_extra + ($plot_abo ? (int)$pa['abo_slots'] : 0); ?> Slots
</span>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php endif; // has_any ?>
</div><!-- .wbf-abo-wrap -->
<!-- Bestätigungs-Modal -->
<div id="wbf-abo-modal" class="wbf-abo-modal-overlay" style="display:none" role="dialog" aria-modal="true">
<div class="wbf-abo-modal">
<div class="wbf-abo-modal__head"><i class="fas fa-triangle-exclamation"></i> Abo kündigen</div>
<div class="wbf-abo-modal__body">
<p>Möchtest du <strong id="wbf-abo-modal-label"></strong> wirklich zum Monatsende kündigen?</p>
<p class="wbf-abo-modal__sub">Das Abo bleibt bis <strong id="wbf-abo-modal-period"></strong> aktiv. Danach werden die Vorteile automatisch deaktiviert.</p>
</div>
<div class="wbf-abo-modal__foot">
<button class="wbf-btn wbf-btn--ghost wbf-btn--sm" id="wbf-abo-modal-no">Abbrechen</button>
<button class="wbf-btn wbf-btn--danger wbf-btn--sm" id="wbf-abo-modal-yes"><i class="fas fa-xmark"></i> Ja, kündigen</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var AJAX = <?php echo json_encode($ajax_url); ?>;
var p = {type:null, nonce:null, btn:null, aboId:0};
var modal = document.getElementById('wbf-abo-modal');
var yesBtn = document.getElementById('wbf-abo-modal-yes');
var noBtn = document.getElementById('wbf-abo-modal-no');
document.querySelectorAll('.wbf-abo-cancel-btn').forEach(function(b) {
b.addEventListener('click', function(e) {
e.preventDefault();
p = {type:b.dataset.type, nonce:b.dataset.nonce, btn:b, aboId:parseInt(b.dataset.aboId||'0',10)};
document.getElementById('wbf-abo-modal-label').textContent = b.dataset.label || 'dieses Abo';
document.getElementById('wbf-abo-modal-period').textContent = b.dataset.period || '';
modal.style.display = 'flex';
yesBtn.disabled = false;
yesBtn.innerHTML = '<i class="fas fa-xmark"></i> Ja, kündigen';
});
});
function closeModal() { modal.style.display = 'none'; }
noBtn.addEventListener('click', closeModal);
modal.addEventListener('click', function(e) { if(e.target===modal) closeModal(); });
yesBtn.addEventListener('click', function() {
yesBtn.disabled = true;
yesBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Kündigen…';
closeModal();
var params = {action:'wbf_cancel_abo', nonce:p.nonce, type:p.type};
if (p.type === 'item_abo' && p.aboId > 0) params.abo_id = p.aboId;
fetch(AJAX, {
method:'POST',
headers:{'Content-Type':'application/x-www-form-urlencoded'},
body: new URLSearchParams(params).toString()
})
.then(function(r){return r.json();})
.then(function(res) {
if (res.success) {
var card;
if (p.type === 'item_abo') {
card = p.aboId > 0 ? document.getElementById('wbf-abo-item-'+p.aboId) : null;
} else {
card = document.getElementById(p.type==='fly_abo'?'wbf-abo-fly':'wbf-abo-plot');
}
if (card) {
card.classList.replace('wbf-abo-item--active','wbf-abo-item--cancelled');
var badge = card.querySelector('.wbf-abo-badge');
if (badge) { badge.className='wbf-abo-badge wbf-abo-badge--cancelled'; badge.innerHTML='<i class="fas fa-clock"></i> Gekündigt'; }
var foot = card.querySelector('.wbf-abo-item__foot');
if (foot) {
foot.className = 'wbf-abo-item__foot wbf-abo-item__foot--cancelled';
foot.innerHTML = '<div class="wbf-abo-notice wbf-abo-notice--info"><i class="fas fa-circle-info"></i> Kündigung vorgemerkt Abo bleibt bis <strong>'+(p.btn?p.btn.dataset.period:'')+'</strong> aktiv.</div>';
}
card.querySelectorAll('.wbf-abo-grid__item').forEach(function(row){
if(row.textContent.includes('Abbuchung')) row.style.display='none';
});
}
} else {
alert((res.data&&res.data.message)?res.data.message:'Fehler beim Kündigen.');
}
yesBtn.disabled=false;
yesBtn.innerHTML='<i class="fas fa-xmark"></i> Ja, kündigen';
p={type:null,nonce:null,btn:null,aboId:0};
})
.catch(function(){ alert('Verbindungsfehler. Bitte ingame kündigen.'); yesBtn.disabled=false; p={type:null,nonce:null,btn:null,aboId:0}; });
});
});
</script>
<?php
return ob_get_clean();
}
}