Upload folder via GUI - includes

This commit is contained in:
Git Manager GUI
2026-04-29 21:07:48 +02:00
parent e32ea0e9d7
commit 269dc17f57
3 changed files with 491 additions and 2 deletions

View File

@@ -0,0 +1,428 @@
<?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,
];
}
// 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 ): 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;
}
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;
$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;
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 if ($plot_extra > 0 || $plot_abo): ?>
<div class="wbf-abo-item wbf-abo-item--info">
<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};
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};
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();
fetch(AJAX, {
method:'POST',
headers:{'Content-Type':'application/x-www-form-urlencoded'},
body: new URLSearchParams({action:'wbf_cancel_abo', nonce:p.nonce, type:p.type}).toString()
})
.then(function(r){return r.json();})
.then(function(res) {
if (res.success) {
var 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.\nAlternativ: /flyabocancel confirm ingame');
}
yesBtn.disabled=false;
yesBtn.innerHTML='<i class="fas fa-xmark"></i> Ja, kündigen';
p={type:null,nonce:null,btn:null};
})
.catch(function(){ alert('Verbindungsfehler. Bitte ingame kündigen.'); yesBtn.disabled=false; p={type:null,nonce:null,btn:null}; });
});
});
</script>
<?php
return ob_get_clean();
}
}

View File

@@ -28,6 +28,7 @@ class WBF_Ajax {
'wbf_2fa_setup_verify',
'wbf_2fa_disable',
'wbf_2fa_verify_login',
'wbf_cancel_abo',
];
foreach ($actions as $action) {
add_action('wp_ajax_nopriv_' . $action, [__CLASS__, str_replace('wbf_','handle_',$action)]);
@@ -1833,6 +1834,47 @@ class WBF_Ajax {
] );
}
// ── Abo kündigen ──────────────────────────────────────────────────────────
public static function handle_cancel_abo() {
self::verify();
$current = WBF_Auth::get_current_user();
if ( ! $current ) {
wp_send_json_error(['message' => 'Nicht eingeloggt.']);
return;
}
$type = sanitize_key($_POST['type'] ?? '');
if ( ! in_array($type, ['fly_abo', 'plot_abo'], true) ) {
wp_send_json_error(['message' => 'Ungültiger Abo-Typ.']);
return;
}
// MC-Name ermitteln (Spielername = Forum-Username oder verknüpfter MC-Name)
$mc_name = '';
if ( class_exists('WBF_MC_Bridge') ) {
$mc_name = WBF_MC_Bridge::get_mc_name($current->id) ?: '';
}
if ( empty($mc_name) ) {
// Fallback: Forum-Username = Minecraft-Name
$mc_name = $current->username;
}
if ( ! class_exists('WBF_Abo') ) {
wp_send_json_error(['message' => 'Abo-Modul nicht geladen.']);
return;
}
$ok = WBF_Abo::cancel_abo($mc_name, $type);
if ($ok) {
wp_send_json_success(['message' => 'Abo erfolgreich gekündigt.']);
} else {
wp_send_json_error(['message' => 'Kein aktives Abo gefunden oder bereits gekündigt.']);
}
}
}
add_action( 'init', [ 'WBF_Ajax', 'init' ] );

View File

@@ -937,7 +937,7 @@ class WBF_Shortcodes {
if (is_int($active_tab) && !in_array($active_tab, [1,2,3,4])) {
$active_tab = $is_own ? 1 : 2;
}
if (!is_int($active_tab) && $active_tab !== $shop_tab_id && $active_tab !== 'mc' && $active_tab !== 'plugins') {
if (!is_int($active_tab) && $active_tab !== $shop_tab_id && $active_tab !== 'mc' && $active_tab !== 'plugins' && $active_tab !== 'abos') {
$active_tab = $is_own ? 1 : 2;
}
// Tab 1, 3, 4, "shop" und String-Tabs nur für eigenes Profil (außer Tab 2 = Aktivität)
@@ -1041,6 +1041,9 @@ class WBF_Shortcodes {
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=4" class="wbf-pv-tab<?php echo $active_tab===4?' active':''; ?>"><i class="fas fa-lock"></i> Sicherheit</a>
<?php if ($shop_active): ?>
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=shop" class="wbf-pv-tab<?php echo $active_tab==='shop'?' active':''; ?>"><i class="fas fa-shopping-cart"></i> Käufe</a>
<?php if ($is_own): ?>
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=abos" class="wbf-pv-tab<?php echo $active_tab==='abos'?' active':''; ?>"><i class="fas fa-receipt"></i> Abonnements</a>
<?php endif; ?>
<?php endif; ?>
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=mc" class="wbf-pv-tab<?php echo $active_tab==='mc'?' active':''; ?>"><i class="fas fa-plug"></i> Verbindungen</a>
<?php if ( function_exists('vmcp_bridge_is_available') && vmcp_bridge_is_available() ) : ?>
@@ -1280,8 +1283,24 @@ class WBF_Shortcodes {
<?php endif; ?>
<!-- ══════════════════════════════════════════════════
TAB 1 — Profil bearbeiten + Weitere Profilangaben
TAB ABOS — Abonnement-Verwaltung
══════════════════════════════════════════════════ -->
<?php if ($is_own && $active_tab === 'abos'): ?>
<?php
// MC-Namen ermitteln: verknüpfter Name (MC-Bridge) oder Forum-Username
$abo_mc_name = '';
if ($mc_enabled && class_exists('WBF_MC_Bridge')) {
$abo_mc_name = WBF_MC_Bridge::get_mc_name($profile->id) ?: '';
}
if (empty($abo_mc_name)) $abo_mc_name = $profile->username;
if (class_exists('WBF_Abo')) {
echo WBF_Abo::render_tab($abo_mc_name, $profile->id);
} else {
echo '<p class="wbf-notice">Abo-Modul nicht verfügbar.</p>';
}
?>
<?php endif; ?>
<?php if ($is_own && $active_tab === 1): ?>
<!-- Profil bearbeiten -->