Update from Git Manager GUI

This commit is contained in:
2026-03-30 20:41:51 +02:00
parent 56f8c01b52
commit f4d0ec73c0
7 changed files with 1384 additions and 112 deletions

View File

@@ -928,12 +928,19 @@ class WBF_Shortcodes {
// Aktiven Tab aus URL lesen (tab=1|2|3), Standard: 1 für eigenes, 2 für fremdes
// Tab-ID: numerisch (14) oder String-Slug (z.B. 'mc' von der Forum-Bridge)
$ptab_raw = $_GET['ptab'] ?? ($is_own ? 1 : 2);
$shop_active = class_exists('WIS_DB');
$shop_tab_id = 'shop';
$allowed_tabs = [1,2,3,4];
if ($is_own && $shop_active) $allowed_tabs[] = $shop_tab_id;
$active_tab = ctype_digit( (string) $ptab_raw ) ? (int) $ptab_raw : sanitize_key( $ptab_raw );
if ( is_int($active_tab) && ! in_array($active_tab, [1,2,3,4]) ) {
if (is_int($active_tab) && !in_array($active_tab, [1,2,3,4])) {
$active_tab = $is_own ? 1 : 2;
}
// Tab 1, 3, 4 und String-Tabs nur für eigenes Profil (außer Tab 2 = Aktivität)
if ( ! $is_own && $active_tab !== 2 ) $active_tab = 2;
if (!is_int($active_tab) && $active_tab !== $shop_tab_id && $active_tab !== 'mc') {
$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)
if (!$is_own && $active_tab !== 2) $active_tab = 2;
ob_start(); ?>
<div class="wbf-wrap">
@@ -948,6 +955,21 @@ class WBF_Shortcodes {
<!-- ── SIDEBAR ─────────────────────────────────────────── -->
<aside class="wbf-profile-sidebar">
<!-- Banner -->
<div class="wbf-profile-banner" id="wbfProfileBannerWrap">
<?php if ( ! empty($profile->banner_url) ) : ?>
<img src="<?php echo esc_url($profile->banner_url); ?>"
alt="" id="wbfProfileBanner" class="wbf-profile-banner__img">
<?php else : ?>
<div class="wbf-profile-banner__placeholder"></div>
<?php endif; ?>
<?php if ($is_own) : ?>
<label class="wbf-banner-upload-btn" title="Banner ändern">
<i class="fas fa-image"></i>
<input type="file" id="wbfBannerFile" accept="image/*" style="display:none">
</label>
<?php endif; ?>
</div>
<div class="wbf-profile-sidebar__avatar-wrap">
<img src="<?php echo esc_url($profile->avatar_url); ?>"
alt="<?php echo esc_attr($profile->display_name); ?>"
@@ -994,13 +1016,13 @@ class WBF_Shortcodes {
<?php if (!empty($profile->bio)): ?>
<div class="wbf-profile-sidebar__section">
<span class="wbf-profile-sidebar__section-label"><i class="fas fa-align-left"></i> Bio</span>
<p><?php echo WBF_BBCode::render($profile->bio); ?></p>
<div class="wbf-profile-sidebar__bio-text"><?php echo WBF_BBCode::render($profile->bio); ?></div>
</div>
<?php endif; ?>
<?php if (!empty($profile->signature)): ?>
<div class="wbf-profile-sidebar__section">
<span class="wbf-profile-sidebar__section-label"><i class="fas fa-pen-nib"></i> Signatur</span>
<p class="wbf-profile-sidebar__sig"><?php echo WBF_BBCode::render($profile->signature); ?></p>
<div class="wbf-profile-sidebar__sig"><?php echo WBF_BBCode::render($profile->signature); ?></div>
</div>
<?php endif; ?>
<!-- Öffentliche Custom Fields — nach Kategorie gruppiert -->
@@ -1113,6 +1135,12 @@ class WBF_Shortcodes {
class="wbf-profile-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-profile-tab<?php echo $active_tab==='shop'?' active':''; ?>">
<i class="fas fa-shopping-cart"></i> Käufe
</a>
<?php endif; ?>
<?php
// „Verbindungen" Tab — immer sichtbar (Discord eingebaut, MC optional)
$wbf_has_connections = true;
@@ -1124,6 +1152,116 @@ class WBF_Shortcodes {
<?php endif; ?>
</div>
<?php endif; ?>
<!-- ══════════════════════════════════════════════════
TAB SHOP — Käufe (Shop-Plugin)
══════════════════════════════════════════════════ -->
<?php if ($is_own && $active_tab === 'shop' && $shop_active): ?>
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-shopping-cart"></i> Deine Käufe
</div>
<div class="wbf-profile-card__body">
<?php
$orders = [];
if (class_exists('WIS_DB')) {
global $wpdb;
$username = $profile->username;
$orders = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wis_orders WHERE player_name = %s ORDER BY created_at DESC",
$username
));
}
?>
<?php if (empty($orders)): ?>
<p class="wbf-profile-empty">Du hast noch keine Käufe getätigt.</p>
<?php else: ?>
<div class="wbf-shop-orders-list">
<table class="wbf-shop-orders-table">
<thead>
<tr>
<th style="text-align:left">Datum</th>
<th style="text-align:center">Anzahl</th>
<th style="text-align:right">Gesamtpreis</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($orders as $i => $order):
$is_cancelled = strtolower($order->status) === 'cancelled' || strtolower($order->status) === 'storniert';
$row_class = $is_cancelled ? 'wbf-shop-order-cancelled' : '';
?>
<tr class="wbf-shop-order-row <?php echo $row_class; ?>" data-idx="<?php echo $i; ?>">
<td><?php echo date_i18n('d.m.Y H:i', strtotime($order->created_at)); ?></td>
<td style="text-align:center"><?php echo (int)$order->quantity; ?></td>
<td style="text-align:right"><?php echo number_format($order->price * $order->quantity); ?> <?php echo esc_html(get_option('wis_currency_name', 'Coins')); ?></td>
<td style="text-align:center">
<button class="wbf-btn wbf-btn--sm wbf-shop-order-toggle" data-idx="<?php echo $i; ?>"><i class="fas fa-chevron-down"></i></button>
</td>
</tr>
<tr class="wbf-shop-order-details" id="wbf-shop-order-details-<?php echo $i; ?>" style="display:none">
<td colspan="4">
<div class="wbf-shop-order-details-inner">
<?php /* Artikel-Zeile entfernt, da Item-Liste folgt */ ?>
<strong>Einzelpreis:</strong> <?php echo number_format($order->price); ?> <?php echo esc_html(get_option('wis_currency_name', 'Coins')); ?><br>
<strong>Status:</strong> <?php echo esc_html(ucfirst($order->status)); ?><br>
<?php if (!empty($order->server)): ?><strong>Server:</strong> <?php echo esc_html($order->server); ?><br><?php endif; ?>
<?php
// Antwort als JSON-Items/Coupon anzeigen
$response = $order->response;
$decoded = null;
if (!empty($response)) {
$decoded = json_decode($response, true);
}
if (is_array($decoded) && isset($decoded['items'])) {
echo '<strong>Gekaufte Items:</strong><ul style="margin:.3em 0 .7em 1.2em">';
foreach ($decoded['items'] as $item) {
$item_id = isset($item['id']) ? $item['id'] : '';
$item_id = preg_replace('/^minecraft:/', '', $item_id);
$amount = isset($item['amount']) ? (int)$item['amount'] : 1;
echo '<li><span style="color:var(--c-primary)">' . esc_html($item_id) . '</span> <span style="color:var(--c-muted)">x' . $amount . '</span></li>';
}
echo '</ul>';
if (isset($decoded['coupon']['code'])) {
$c = $decoded['coupon'];
echo '<div style="margin:.2em 0 .5em 0"><strong>Coupon:</strong> <span style="color:var(--c-success)">' . esc_html($c['code']) . '</span>';
if (isset($c['discount'])) echo ' <span style="color:var(--c-muted)">(' . intval($c['discount']) . '% Rabatt)</span>';
echo '</div>';
}
} elseif (!empty($response)) {
echo '<strong>Antwort:</strong> ' . esc_html($response) . '<br>';
}
?>
<span style="font-size:.85em;color:var(--c-muted)">Bestell-ID: <?php echo (int)$order->id; ?></span>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.wbf-shop-order-toggle').forEach(function(btn) {
btn.addEventListener('click', function() {
var idx = btn.getAttribute('data-idx');
var details = document.getElementById('wbf-shop-order-details-' + idx);
if (details.style.display === 'none') {
details.style.display = '';
btn.querySelector('i').classList.remove('fa-chevron-down');
btn.querySelector('i').classList.add('fa-chevron-up');
} else {
details.style.display = 'none';
btn.querySelector('i').classList.remove('fa-chevron-up');
btn.querySelector('i').classList.add('fa-chevron-down');
}
});
});
});
</script>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<!-- ══════════════════════════════════════════════════
TAB 1 — Profil bearbeiten + Weitere Profilangaben
@@ -1537,6 +1675,122 @@ class WBF_Shortcodes {
</div>
</div>
<!-- ══════════════════════════════════════════════════
2FA — Zwei-Faktor-Authentifizierung (TOTP)
══════════════════════════════════════════════════ -->
<?php if ( class_exists('WBF_TOTP') ) :
$wbf_2fa_active = WBF_TOTP::is_enabled_for($current->id);
?>
<div class="wbf-profile-card wbf-2fa-card" id="wbf2faCard">
<div class="wbf-profile-card__header" style="background:rgba(234,179,8,.07);border-bottom-color:rgba(234,179,8,.2)">
<i class="fas fa-shield-halved" style="color:#eab308"></i>
Zwei-Faktor-Authentifizierung (2FA)
<?php if ( $wbf_2fa_active ): ?>
<span class="wbf-2fa-badge wbf-2fa-badge--on"><i class="fas fa-check-circle"></i> Aktiv</span>
<?php else: ?>
<span class="wbf-2fa-badge wbf-2fa-badge--off"><i class="fas fa-circle-xmark"></i> Inaktiv</span>
<?php endif; ?>
</div>
<div class="wbf-profile-card__body">
<!-- ── 2FA bereits aktiv: Deaktivierungs-Formular ── -->
<?php if ( $wbf_2fa_active ): ?>
<div id="wbf2faActive">
<p style="color:var(--c-muted);font-size:.85rem;margin-bottom:1rem">
<i class="fas fa-info-circle"></i>
Dein Account ist mit einem Authenticator gesichert.
Zum Deaktivieren Passwort und aktuellen Code eingeben.
</p>
<div class="wbf-profile-edit-grid">
<div class="wbf-form-row">
<label>Aktuelles Passwort</label>
<input type="password" id="wbf2faDisablePw" placeholder="••••••" autocomplete="current-password">
</div>
<div class="wbf-form-row">
<label>Authenticator-Code</label>
<input type="text" id="wbf2faDisableCode" placeholder="123456"
maxlength="6" inputmode="numeric" autocomplete="one-time-code"
style="letter-spacing:.2em;font-size:1.15rem;font-family:monospace">
</div>
</div>
<div class="wbf-profile-card__footer">
<button class="wbf-btn" id="wbf2faDisableBtn"
style="background:rgba(220,38,38,.1);color:#dc2626;border-color:rgba(220,38,38,.3)">
<i class="fas fa-shield-xmark"></i> 2FA deaktivieren
</button>
<span class="wbf-msg" id="wbf2faDisableMsg"></span>
</div>
</div>
<?php else: ?>
<!-- ── 2FA noch nicht aktiv: Setup-Wizard ── -->
<div id="wbf2faInactive">
<p style="color:var(--c-muted);font-size:.85rem;margin-bottom:1rem">
<i class="fas fa-info-circle"></i>
Schütze deinen Account zusätzlich mit einer Authenticator-App
(Google Authenticator, Aegis, Bitwarden, Authy, 2FAS…).
</p>
<button class="wbf-btn wbf-btn--primary" id="wbf2faStartBtn">
<i class="fas fa-shield-halved"></i> 2FA einrichten
</button>
</div>
<!-- Schritt 1: QR-Code scannen -->
<div id="wbf2faStep1" style="display:none">
<p style="font-size:.85rem;color:var(--c-muted);margin-bottom:.75rem">
<strong>Schritt 1:</strong> Scanne diesen QR-Code mit deiner Authenticator-App.
</p>
<div id="wbf2faQr" style="display:inline-block;padding:10px;background:#fff;border-radius:8px;margin-bottom:.75rem"></div>
<p style="font-size:.8rem;color:var(--c-muted);margin-bottom:.5rem">
Kein QR-Scanner? Gib diesen Code manuell ein:
</p>
<code id="wbf2faSecret" style="font-size:.9rem;letter-spacing:.1em;background:var(--c-bg-2);padding:4px 10px;border-radius:4px;user-select:all"></code>
<div class="wbf-profile-card__footer" style="margin-top:1rem">
<button class="wbf-btn wbf-btn--primary" id="wbf2faToStep2">
Weiter <i class="fas fa-arrow-right"></i>
</button>
</div>
</div>
<!-- Schritt 2: Code bestätigen -->
<div id="wbf2faStep2" style="display:none">
<p style="font-size:.85rem;color:var(--c-muted);margin-bottom:.75rem">
<strong>Schritt 2:</strong> Gib den 6-stelligen Code aus deiner App ein.
</p>
<div class="wbf-form-row" style="max-width:220px">
<label>Bestätigungs-Code</label>
<input type="text" id="wbf2faVerifyCode" placeholder="123456"
maxlength="6" inputmode="numeric" autocomplete="one-time-code"
style="letter-spacing:.25em;font-size:1.3rem;text-align:center;font-family:monospace">
</div>
<div class="wbf-profile-card__footer">
<button class="wbf-btn" id="wbf2faBackBtn" style="opacity:.7">
<i class="fas fa-arrow-left"></i> Zurück
</button>
<button class="wbf-btn wbf-btn--primary" id="wbf2faVerifyBtn">
<i class="fas fa-check"></i> Bestätigen &amp; aktivieren
</button>
<span class="wbf-msg" id="wbf2faVerifyMsg"></span>
</div>
</div>
<!-- Schritt 3: Erfolg -->
<div id="wbf2faStep3" style="display:none;text-align:center;padding:1.5rem 0">
<div style="font-size:2.5rem;margin-bottom:.5rem">🔒</div>
<strong style="font-size:1rem;color:var(--c-text)">2FA erfolgreich aktiviert!</strong>
<p style="font-size:.85rem;color:var(--c-muted);margin:.5rem 0 0">
Ab jetzt wird beim Login ein Code aus deiner App abgefragt.
</p>
</div>
<?php endif; // 2fa_active ?>
</div>
</div>
<?php endif; // class_exists WBF_TOTP ?>
<?php endif; /* end Tab 4 */ ?>
<!-- ══════════════════════════════════════════════════
@@ -1551,7 +1805,7 @@ class WBF_Shortcodes {
<div class="wbf-profile-card__header">
<i class="fas fa-plug"></i> Verbundene Dienste
</div>
<div class="wbf-profile-card__body" style="padding:0">
<div class="wbf-profile-card__body wbf-connections-body">
<?php if ( class_exists('MC_Gallery_Forum_Bridge') ) :
$mc_content = apply_filters('wbf_profile_tab_content', '', 'minecraft', $profile);
@@ -1561,7 +1815,7 @@ class WBF_Shortcodes {
<i class="fas fa-cubes" style="color:#65a30d"></i>
</div>
<div class="wbf-connection-card__head">
<span class="wbf-connection-card__title">Minecraft</span>
<span class="wbf-connection-card__title">Gallerie Verbindung</span>
</div>
<div class="wbf-connection-card__content">
<?php echo $mc_content; ?>
@@ -1569,6 +1823,201 @@ class WBF_Shortcodes {
</div>
<?php endif; ?>
<?php
// ── StatusAPI Bridge: Account-Verknüpfung & Ingame-Benachrichtigungen ──
$mc_enabled = class_exists( 'WBF_MC_Bridge' ) && WBF_MC_Bridge::is_enabled();
$mc_uuid = $mc_enabled ? WBF_MC_Bridge::get_mc_uuid( $profile->id ) : '';
$mc_name = $mc_enabled ? WBF_MC_Bridge::get_mc_name( $profile->id ) : '';
$mc_linked = ! empty( $mc_uuid );
?>
<div class="wbf-connection-card">
<div class="wbf-connection-card__icon" style="background:rgba(101,163,13,.15);border-color:rgba(101,163,13,.3)">
<i class="fas fa-cubes" style="color:#65a30d"></i>
</div>
<div class="wbf-connection-card__head">
<span class="wbf-connection-card__title">Minecraft InGame Verbindung</span>
<?php if ( ! $mc_enabled ) : ?>
<span class="wbf-connection-badge" style="color:#9ca3af;background:rgba(156,163,175,.1);border-color:rgba(156,163,175,.3)">
<i class="fas fa-circle-xmark"></i> Nicht konfiguriert
</span>
<?php elseif ( $mc_linked ) : ?>
<span class="wbf-connection-badge wbf-connection-badge--connected">
<i class="fas fa-check-circle"></i> Verbunden
</span>
<?php else : ?>
<span class="wbf-connection-badge wbf-connection-badge--disconnected">
<i class="fas fa-circle-xmark"></i> Nicht verbunden
</span>
<?php endif; ?>
</div>
<div class="wbf-connection-card__content">
<?php if ( ! $mc_enabled ) : ?>
<p class="wbf-connection-card__desc" style="color:var(--c-muted)">
<i class="fas fa-info-circle"></i>
Die Minecraft Bridge ist noch nicht eingerichtet.
Ein Admin muss sie zuerst in den Forum-Einstellungen aktivieren.
</p>
<?php elseif ( $mc_linked ) : ?>
<div class="wbf-mc-linked-info" style="display:flex;align-items:center;gap:.75rem;margin-bottom:.75rem">
<img src="https://mc-heads.net/avatar/<?php echo urlencode( $mc_name ?: $mc_uuid ); ?>/40"
alt="" width="40" height="40"
style="border-radius:4px;image-rendering:pixelated">
<div>
<strong style="color:var(--c-text)"><?php echo esc_html( $mc_name ?: $mc_uuid ); ?></strong><br>
<small style="color:var(--c-muted);font-size:.75rem"><?php echo esc_html( $mc_uuid ); ?></small>
</div>
</div>
<p style="font-size:.82rem;color:var(--c-muted);margin:.25rem 0 .75rem">
<i class="fas fa-bell" style="color:#65a30d"></i>
Du erhältst Ingame-Benachrichtigungen bei Antworten, Erwähnungen und PNs.
</p>
<div id="wbf-mc-msg" style="font-size:.82rem;margin-bottom:.5rem"></div>
<button type="button" class="wbf-btn wbf-btn--ghost" id="wbf-mc-unlink-btn"
onclick="wbfMcUnlink()">
<i class="fas fa-unlink"></i> Verknüpfung aufheben
</button>
<?php else : ?>
<p class="wbf-connection-card__desc">
Verknüpfe deinen Minecraft-Account für Ingame-Benachrichtigungen
bei neuen Antworten, Erwähnungen und Privatnachrichten.
</p>
<p style="font-size:.82rem;color:var(--c-muted);margin-bottom:.75rem">
<i class="fas fa-terminal"></i>
Schritt 1: Token generieren &nbsp;→&nbsp;
Schritt 2: <code>/forumlink &lt;token&gt;</code> ingame eingeben
</p>
<div id="wbf-mc-token-box" style="display:none;background:var(--c-surface);border:1px solid var(--c-border);border-radius:8px;padding:.85rem 1rem;margin-bottom:.75rem">
<div style="display:flex;align-items:center;justify-content:space-between;gap:.5rem;margin-bottom:.4rem">
<span style="font-size:.75rem;color:var(--c-muted);text-transform:uppercase;letter-spacing:.05em">Dein Token</span>
<span id="wbf-mc-token-timer" style="font-size:.75rem;color:#f97316;font-weight:600"></span>
</div>
<div style="display:flex;align-items:center;gap:.5rem">
<code id="wbf-mc-token-value"
style="font-size:1.4rem;letter-spacing:.25em;font-weight:700;color:var(--c-accent);flex:1"></code>
<button type="button" class="wbf-btn wbf-btn--sm" id="wbf-mc-copy-btn"
onclick="wbfMcCopyToken()" title="Befehl kopieren">
<i class="fas fa-copy"></i>
</button>
</div>
<div style="margin-top:.5rem;font-size:.8rem;color:var(--c-muted)">
Ingame eingeben: <code id="wbf-mc-cmd-value">/forumlink </code>
</div>
<div style="margin-top:.5rem">
<div style="height:4px;border-radius:2px;background:var(--c-border);overflow:hidden">
<div id="wbf-mc-token-progress"
style="height:100%;background:#65a30d;transition:width 1s linear;width:100%"></div>
</div>
</div>
</div>
<div id="wbf-mc-msg" style="font-size:.82rem;margin-bottom:.5rem"></div>
<button type="button" class="wbf-btn wbf-btn--primary" id="wbf-mc-gen-btn"
onclick="wbfMcGenerateToken()">
<i class="fas fa-key"></i> Token generieren
</button>
<?php endif; ?>
</div>
</div>
<script>
(function(){
var _pollInterval = null;
var _timerInterval = null;
var _expiry = 0;
window.wbfMcGenerateToken = function() {
var btn = document.getElementById('wbf-mc-gen-btn');
var msg = document.getElementById('wbf-mc-msg');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Generiere...';
msg.textContent = '';
jQuery.post(WBF.ajax_url, { action: 'wbf_mc_generate_token', nonce: WBF.nonce }, function(r) {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-rotate"></i> Neuen Token generieren';
if (r.success) {
var token = r.data.token;
_expiry = Math.floor(Date.now() / 1000) + ((r.data.expires_in || 15) * 60);
document.getElementById('wbf-mc-token-value').textContent = token;
document.getElementById('wbf-mc-cmd-value').textContent = '/forumlink ' + token;
document.getElementById('wbf-mc-token-box').style.display = 'block';
wbfMcStartTimer((r.data.expires_in || 15) * 60);
wbfMcStartPolling();
} else {
if (r.data && r.data.linked) {
msg.innerHTML = '<span style="color:#16a34a"><i class="fas fa-check-circle"></i> ' + (r.data.message || 'Bereits verknüpft.') + '</span>';
setTimeout(function(){ location.reload(); }, 1500);
} else {
msg.innerHTML = '<span style="color:#dc2626"><i class="fas fa-circle-xmark"></i> ' + (r.data && r.data.message ? r.data.message : 'Fehler') + '</span>';
}
}
});
};
function wbfMcStartTimer(seconds) {
clearInterval(_timerInterval);
var timerEl = document.getElementById('wbf-mc-token-timer');
var progressEl = document.getElementById('wbf-mc-token-progress');
var total = seconds;
_timerInterval = setInterval(function() {
var remaining = _expiry - Math.floor(Date.now() / 1000);
if (remaining <= 0) {
clearInterval(_timerInterval); clearInterval(_pollInterval);
if (timerEl) timerEl.textContent = 'Abgelaufen';
if (progressEl) progressEl.style.width = '0%';
var msg = document.getElementById('wbf-mc-msg');
if (msg) msg.innerHTML = '<span style="color:#f97316"><i class="fas fa-clock"></i> Token abgelaufen — bitte neuen generieren.</span>';
return;
}
var m = Math.floor(remaining / 60), s = remaining % 60;
if (timerEl) timerEl.textContent = m + ':' + (s < 10 ? '0' : '') + s;
if (progressEl) {
progressEl.style.width = Math.max(0, (remaining / total) * 100) + '%';
progressEl.style.background = remaining < 60 ? '#dc2626' : remaining < 180 ? '#f97316' : '#65a30d';
}
}, 1000);
}
function wbfMcStartPolling() {
clearInterval(_pollInterval);
_pollInterval = setInterval(function() {
jQuery.post(WBF.ajax_url, { action: 'wbf_mc_link_status', nonce: WBF.nonce }, function(r) {
if (r.success && r.data && r.data.linked) {
clearInterval(_pollInterval); clearInterval(_timerInterval);
var msg = document.getElementById('wbf-mc-msg');
if (msg) msg.innerHTML = '<span style="color:#16a34a"><i class="fas fa-check-circle"></i> ✓ Verknüpft mit <strong>' + (r.data.mc_name || r.data.mc_uuid) + '</strong>! Seite lädt neu...</span>';
setTimeout(function(){ location.reload(); }, 1800);
}
});
}, 5000);
}
window.wbfMcCopyToken = function() {
var cmd = document.getElementById('wbf-mc-cmd-value').textContent;
var btn = document.getElementById('wbf-mc-copy-btn');
var done = function() { btn.innerHTML = '<i class="fas fa-check"></i>'; btn.style.color = '#16a34a'; setTimeout(function(){ btn.innerHTML = '<i class="fas fa-copy"></i>'; btn.style.color = ''; }, 2000); };
if (navigator.clipboard) { navigator.clipboard.writeText(cmd).then(done); }
else { var ta = document.createElement('textarea'); ta.value = cmd; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); done(); }
};
window.wbfMcUnlink = function() {
if (!confirm('Minecraft-Verknüpfung wirklich aufheben?')) return;
var btn = document.getElementById('wbf-mc-unlink-btn');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Trenne...';
jQuery.post(WBF.ajax_url, { action: 'wbf_mc_unlink', nonce: WBF.nonce }, function(r) {
if (r.success) {
var msg = document.getElementById('wbf-mc-msg');
if (msg) msg.innerHTML = '<span style="color:#16a34a"><i class="fas fa-check"></i> ' + (r.data.message || 'Verknüpfung aufgehoben.') + '</span>';
setTimeout(function(){ location.reload(); }, 1200);
} else {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-unlink"></i> Verknüpfung aufheben';
var msg = document.getElementById('wbf-mc-msg');
if (msg) msg.innerHTML = '<span style="color:#dc2626">Fehler: ' + (r.data && r.data.message ? r.data.message : 'Unbekannt') + '</span>';
}
});
};
})();
</script>
<?php
// ── Discord-Card (eingebaut, kein extra Plugin nötig) ──────────────
$discord_meta = WBF_DB::get_user_meta( $profile->id );
@@ -1657,6 +2106,7 @@ class WBF_Shortcodes {
</div><!-- /.wbf-profile-layout -->
</div>
</div>
<?php self::render_auth_modal(); ?>
<?php return ob_get_clean();
}
@@ -2579,16 +3029,7 @@ class WBF_Shortcodes {
</div>
</a>
<?php endforeach; ?>
</div>
<?php if (WBF_Roles::level($current->role) >= 80): // Nur Admins/Mods ?>
<div style="margin-top:2.5rem;text-align:center">
<button class="wbf-btn wbf-btn--primary" id="wbf-discord-sync-btn" type="button">
<i class="fab fa-discord"></i> Discord-Rollen-Sync manuell anstoßen
</button>
<span id="wbf-discord-sync-msg" style="margin-left:1rem;font-size:.95em;display:none"></span>
</div>
<?php endif; ?>
<?php if (empty($members)): ?>
<div class="wbf-empty" style="grid-column:1/-1">
<i class="fas fa-users-slash"></i>