Files
WP-Business-Forum/admin/forum-settings.php
2026-03-30 20:41:48 +02:00

880 lines
49 KiB
PHP

<?php
/**
* forum-settings.php
* Admin-Einstellungsseite für WP Business Forum.
* Wird von wp-business-forum.php per require_once eingebunden.
*/
if ( ! defined( 'ABSPATH' ) ) exit;
// ── Hilfsfunktion ─────────────────────────────────────────────────────────────
// Gibt alle Forum-Einstellungen zurück (mit Fallback auf Standardwerte).
// Verwendung überall im Plugin: wbf_get_settings()['hero_title']
if ( ! function_exists('wbf_get_settings') ) {
function wbf_get_settings() {
$defaults = [
// Hero-Bereich
'hero_title' => 'Community Forum',
'hero_subtitle' => 'Diskutiere, teile Ideen und bleib immer informiert.',
// Topbar
'topbar_brand' => 'Community',
// Statistik-Labels
'stat_threads' => 'Threads',
'stat_posts' => 'Beiträge',
'stat_members' => 'Mitglieder',
// Abschnitt-Überschriften
'section_cats' => 'Kategorien',
'section_recent' => 'Neue Threads',
// Buttons
'btn_new_thread' => 'Neuer Thread',
'btn_login' => 'Einloggen',
'btn_register' => 'Registrieren',
'btn_logout' => 'Logout',
// Sidebar
'sidebar_profile' => 'Mein Profil',
'sidebar_login' => 'Login / Registrieren',
// Sicherheit
'auto_logout_minutes' => '30',
// Wartungsmodus
'maintenance_mode' => '0',
'maintenance_title' => 'Wartungsarbeiten',
'maintenance_message' => 'Das Forum wird gerade gewartet. Bitte versuche es später erneut.',
'registration_enabled' => '1',
'registration_mode' => 'open',
'post_edit_limit' => '30',
'profile_public_default' => '1',
'spam_min_seconds' => '30', // open | invite | disabled
'flood_interval' => '30', // Flood Control: Sekunden zwischen Posts
'invite_message' => 'Registrierung ist aktuell nur auf Einladung möglich.',
// Forum-Regeln / Nutzungsbedingungen
'rules_enabled' => '1',
'rules_accept_required' => '1',
'rules_title' => 'Forum-Regeln & Nutzungsbedingungen',
'rules_content' => "**1. Respektvoller Umgang**\nBehandle alle Mitglieder freundlich und respektvoll. Beleidigungen, Mobbing und Diskriminierung sind nicht toleriert.\n\n**2. Keine Spam-Inhalte**\nWerbung, Spam und irrelevante Links sind verboten.\n\n**3. Keine illegalen Inhalte**\nJegliche Inhalte, die gegen geltendes Recht verstoßen, sind streng verboten.\n\n**4. Themenrelevanz**\nBeiträge sollten zur jeweiligen Kategorie passen.\n\n**5. Urheberrecht**\nVeröffentliche keine Inhalte, an denen du keine Rechte besitzt.\n\n**6. Datenschutz**\nTeile keine persönlichen Daten anderer Personen ohne deren Zustimmung.\n\n**7. Moderations-Entscheidungen**\nEntscheidungen der Moderatoren sind zu respektieren. Bei Fragen wende dich direkt ans Team.\n\nVerstöße können zur Verwarnung oder dauerhaften Sperrung führen.",
// Ignore/Block-System: Rollen die nicht geblockt werden können (kommagetrennte Schlüssel)
'ignore_blocked_roles' => 'superadmin,admin,moderator',
// Discord-Integration
'discord_bot_token' => '',
'discord_guild_id' => '',
'discord_client_id' => '',
'discord_client_secret' => '',
'discord_role_sync' => '0', // Rollen-Sync aktiviert?
'discord_role_map' => '', // JSON: {"discord_role_id":"forum_role_key"}
// Minecraft Bridge
'mc_bridge_enabled' => '0',
'mc_bridge_api_url' => '',
'mc_bridge_api_secret' => '',
];
$saved = get_option( 'wbf_settings', [] );
// Fehlende Keys mit Defaults auffüllen, leere Strings ignorieren
// Keine Filterung mehr, damit auch bewusst geleerte Felder gespeichert werden
return array_merge( $defaults, (array) $saved );
}
}
// ── Admin-Seite ───────────────────────────────────────────────────────────────
/**
* Gibt ein Array der Rollen-Keys zurück die nicht geblockt/ignoriert werden können.
* Superadmin ist immer enthalten — unabhängig von der Einstellung.
*
* @return string[] z.B. ['superadmin', 'admin', 'moderator']
*/
if ( ! function_exists('wbf_get_ignore_blocked_roles') ) {
function wbf_get_ignore_blocked_roles() {
$raw = wbf_get_settings()['ignore_blocked_roles'] ?? 'superadmin,admin,moderator';
$keys = array_filter( array_map( 'trim', explode( ',', $raw ) ) );
// superadmin immer schützen
if ( ! in_array('superadmin', $keys, true) ) {
$keys[] = 'superadmin';
}
return array_values( $keys );
}
}
/**
* Prüft ob ein User ignoriert/geblockt werden darf.
*
* @param object $target Forum-User-Objekt
* @return bool true = darf ignoriert werden, false = nicht erlaubt
*/
if ( ! function_exists('wbf_can_be_ignored') ) {
function wbf_can_be_ignored( $target ) {
if ( ! $target ) return false;
$blocked_roles = wbf_get_ignore_blocked_roles();
return ! in_array( $target->role, $blocked_roles, true );
}
}
if ( ! function_exists('wbf_admin_settings') ) {
function wbf_admin_settings() {
// Speichern
if ( isset( $_POST['wbf_save_settings'] ) && check_admin_referer( 'wbf_settings_nonce' ) ) {
$fields = [
'hero_title', 'hero_subtitle',
'topbar_brand',
'stat_threads', 'stat_posts', 'stat_members',
'section_cats', 'section_recent',
'btn_new_thread', 'btn_login', 'btn_register', 'btn_logout',
'sidebar_profile', 'sidebar_login',
'auto_logout_minutes', 'post_edit_limit', 'spam_min_seconds', 'flood_interval', 'profile_public_default',
'registration_enabled', 'registration_mode', 'invite_message',
'maintenance_mode', 'maintenance_title', 'maintenance_message',
'post_edit_limit',
'profile_public_default',
'spam_min_seconds',
'rules_enabled', 'rules_accept_required', 'rules_title',
];
$settings = [];
foreach ( $fields as $key ) {
if ( in_array( $key, ['maintenance_message', 'rules_content'] ) ) {
$settings[$key] = sanitize_textarea_field($_POST[$key] ?? '');
} else {
$settings[ $key ] = sanitize_text_field( $_POST[ $key ] ?? '' );
}
}
// rules_content separat (nicht in $fields, da textarea mit eigener Behandlung)
$settings['rules_content'] = sanitize_textarea_field( $_POST['rules_content'] ?? '' );
// Discord-Einstellungen gesondert speichern (sensitiv — niemals in wbf_settings öffentlich)
$discord_fields = ['discord_bot_token', 'discord_guild_id', 'discord_client_id', 'discord_client_secret'];
foreach ( $discord_fields as $df ) {
$settings[$df] = sanitize_text_field( $_POST[$df] ?? '' );
}
$settings['discord_role_sync'] = isset($_POST['discord_role_sync']) && $_POST['discord_role_sync'] === '1' ? '1' : '0';
// Discord-Rollen-Map: Array von discord_role_id => forum_role_key
$role_map = [];
$dc_ids = array_map('sanitize_text_field', (array)($_POST['discord_role_id'] ?? []));
$fr_keys = array_map('sanitize_key', (array)($_POST['discord_forum_role'] ?? []));
$valid_roles = array_keys(WBF_Roles::get_all());
foreach ( $dc_ids as $i => $dc_id ) {
$dc_id = trim($dc_id);
$fr_key = $fr_keys[$i] ?? '';
if ( $dc_id !== '' && in_array($fr_key, $valid_roles, true) ) {
$role_map[$dc_id] = $fr_key;
}
}
$settings['discord_role_map'] = json_encode($role_map);
// ── Minecraft Bridge ──────────────────────────────────────────────────
$settings['mc_bridge_api_url'] = esc_url_raw( trim( $_POST['mc_bridge_api_url'] ?? '' ) );
$settings['mc_bridge_api_secret'] = sanitize_text_field( $_POST['mc_bridge_api_secret'] ?? '' );
// Checkbox-Felder explizit als '0' speichern wenn nicht angehakt,
// damit array_filter(...,'strlen') sie nicht wegwirft und der Default '1' greift.
$checkbox_fields = ['maintenance_mode', 'rules_enabled', 'rules_accept_required', 'mc_bridge_enabled'];
foreach ( $checkbox_fields as $cb ) {
$settings[$cb] = isset($_POST[$cb]) && $_POST[$cb] === '1' ? '1' : '0';
}
// ignore_blocked_roles: kommagetrennte Liste der gewählten Rollen-Keys
$all_role_keys = array_keys( WBF_Roles::get_all() );
$checked_roles = array_intersect(
array_map( 'sanitize_key', (array)( $_POST['ignore_blocked_roles'] ?? [] ) ),
$all_role_keys
);
// superadmin ist immer blockiert — kann nicht entfernt werden
if ( ! in_array('superadmin', $checked_roles, true) ) {
$checked_roles[] = 'superadmin';
}
$settings['ignore_blocked_roles'] = implode( ',', $checked_roles );
update_option( 'wbf_settings', $settings );
// Superadmin WP-User-ID separat speichern (außerhalb von wbf_settings)
$sa_wp_id = (int) ( $_POST['superadmin_wp_id'] ?? 1 );
if ( $sa_wp_id < 1 ) $sa_wp_id = 1;
update_option( 'wbf_superadmin_wp_id', $sa_wp_id );
echo '<div class="notice notice-success is-dismissible"><p>✅ Einstellungen gespeichert!</p></div>';
}
$s = wbf_get_settings();
// Inline-Hilfsfunktion für eine Tabellenzeile
$row = function( $label, $name, $placeholder, $desc = '' ) use ( $s ) {
$val = esc_attr( $s[ $name ] ?? '' );
echo "
<tr>
<th scope='row'><label for='wbf_{$name}'>{$label}</label></th>
<td>
<input type='text'
id='wbf_{$name}'
name='{$name}'
value='{$val}'
placeholder='" . esc_attr( $placeholder ) . "'
class='regular-text'>
" . ( $desc ? "<p class='description'>{$desc}</p>" : '' ) . "
</td>
</tr>";
};
?>
<div class="wrap">
<h1>⚙️ Forum-Einstellungen</h1>
<p style="color:#666;margin-bottom:1.5rem">
Alle sichtbaren Texte des Forums — kein Code nötig.
</p>
<form method="post">
<?php wp_nonce_field( 'wbf_settings_nonce' ); ?>
<!-- ── Hero-Bereich ───────────────────────────────── -->
<h2 style="border-bottom:1px solid #ddd;padding-bottom:.4rem;margin-top:1.5rem">
🏠 Hero-Bereich (Startseite)
</h2>
<table class="form-table" role="presentation">
<?php $row(
'Titel', 'hero_title', 'Community Forum',
'Der große Titel im Banner oben auf der Startseite.'
); ?>
<?php $row(
'Untertitel', 'hero_subtitle',
'Diskutiere, teile Ideen und bleib immer informiert.',
'Der kleine Text direkt unter dem Titel.'
); ?>
</table>
<!-- ── Topbar ─────────────────────────────────────── -->
<h2 style="border-bottom:1px solid #ddd;padding-bottom:.4rem;margin-top:1.5rem">
🔝 Topbar (Navigationsleiste)
</h2>
<table class="form-table" role="presentation">
<?php $row(
'Forum-Name / Brand', 'topbar_brand', 'Community',
'Angezeigt als Markenname links in der Navigationsleiste.'
); ?>
<?php $row( 'Button „Einloggen"', 'btn_login', 'Einloggen' ); ?>
<?php $row( 'Button „Registrieren"', 'btn_register', 'Registrieren' ); ?>
<?php $row( 'Button „Logout"', 'btn_logout', 'Logout' ); ?>
</table>
<!-- ── Statistik-Labels ───────────────────────────── -->
<h2 style="border-bottom:1px solid #ddd;padding-bottom:.4rem;margin-top:1.5rem">
📊 Statistik-Labels
</h2>
<table class="form-table" role="presentation">
<?php $row( 'Label „Threads"', 'stat_threads', 'Threads' ); ?>
<?php $row( 'Label „Beiträge"', 'stat_posts', 'Beiträge' ); ?>
<?php $row( 'Label „Mitglieder"', 'stat_members', 'Mitglieder' ); ?>
</table>
<!-- ── Abschnitt-Überschriften ───────────────────── -->
<h2 style="border-bottom:1px solid #ddd;padding-bottom:.4rem;margin-top:1.5rem">
📂 Abschnitt-Überschriften
</h2>
<table class="form-table" role="presentation">
<?php $row( 'Kategorien-Überschrift', 'section_cats', 'Kategorien' ); ?>
<?php $row( 'Neue Threads (Sidebar)', 'section_recent', 'Neue Threads' ); ?>
<?php $row( 'Button „Neuer Thread"', 'btn_new_thread', 'Neuer Thread' ); ?>
</table>
<!-- ── Sidebar ────────────────────────────────────── -->
<h2 style="border-bottom:1px solid #ddd;padding-bottom:.4rem;margin-top:1.5rem">
👤 Sidebar
</h2>
<table class="form-table" role="presentation">
<?php $row(
'Sidebar-Titel (eingeloggt)', 'sidebar_profile', 'Mein Profil'
); ?>
<?php $row(
'Sidebar-Titel (ausgeloggt)', 'sidebar_login', 'Login / Registrieren'
); ?>
</table>
<!-- ── Sicherheit ─────────────────────────────────── -->
<h2 style="border-bottom:1px solid #ddd;padding-bottom:.4rem;margin-top:1.5rem">
🔒 Sicherheit
</h2>
<table class="form-table" role="presentation">
<!-- ── Superadmin WP-User-ID ─────────────────── -->
<tr>
<th scope="row">
<label for="wbf_superadmin_wp_id">Superadmin WordPress-User-ID</label>
</th>
<td>
<?php
$sa_id = (int) get_option( 'wbf_superadmin_wp_id', 1 );
$sa_wpuser = get_userdata( $sa_id );
?>
<input type="number" id="wbf_superadmin_wp_id" name="superadmin_wp_id"
value="<?php echo $sa_id; ?>"
min="1" step="1"
style="width:80px">
<?php if ( $sa_wpuser ) : ?>
<span style="margin-left:10px;color:#16a34a;font-weight:600">
✅ <?php echo esc_html( $sa_wpuser->display_name ); ?>
&lt;<?php echo esc_html( $sa_wpuser->user_email ); ?>&gt;
</span>
<?php else : ?>
<span style="margin-left:10px;color:#dc2626;font-weight:600">
⚠️ Kein WordPress-User mit dieser ID gefunden!
</span>
<?php endif; ?>
<p class="description">
Nur dieser WordPress-User erhält automatisch die Forum-Rolle <strong>Superadmin</strong>
und kann sie nicht verlieren. Alle anderen WordPress-Admins können normale Forum-Rollen
haben und im Mitglieder-Bereich frei zugewiesen werden.<br>
<em>Standard: 1 (erster bei der WP-Installation angelegter User)</em>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wbf_auto_logout_minutes">Auto-Logout nach Inaktivität</label>
</th>
<td>
<select id="wbf_auto_logout_minutes" name="auto_logout_minutes">
<?php
$current_val = $s['auto_logout_minutes'] ?? '30';
$options = [
'0' => 'Deaktiviert',
'5' => '5 Minuten',
'10' => '10 Minuten',
'15' => '15 Minuten',
'30' => '30 Minuten',
'60' => '1 Stunde',
'120' => '2 Stunden',
'480' => '8 Stunden',
];
foreach ( $options as $val => $label ):
?>
<option value="<?php echo esc_attr($val); ?>"<?php selected($current_val, (string)$val); ?>>
<?php echo esc_html($label); ?>
</option>
<?php endforeach; ?>
</select>
<p class="description">
Nach dieser Zeit ohne Aktivität wird der Nutzer automatisch ausgeloggt und erhält eine Warnung
(30 Sek. vor dem Logout). Gilt nur für Forum-Benutzer, nicht für WordPress-Admins.
</p>
</td>
</tr>
</table>
<!-- ── Post-Bearbeitung & Spam-Schutz ──────────────── -->
<h2 style="border-bottom:1px solid #ddd;padding-bottom:.4rem;margin-top:1.5rem">
✏️ Beiträge & Spam-Schutz
</h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><label>Post-Bearbeitung begrenzen</label></th>
<td>
<select name="post_edit_limit">
<?php
$el = $s['post_edit_limit'] ?? '30';
$opts = ['0'=>'Unbegrenzt','5'=>'5 Min.','10'=>'10 Min.','15'=>'15 Min.','30'=>'30 Min.','60'=>'1 Std.','120'=>'2 Std.','1440'=>'24 Std.'];
foreach ($opts as $v=>$l): ?>
<option value="<?php echo $v; ?>"<?php selected($el,$v); ?>><?php echo esc_html($l); ?></option>
<?php endforeach; ?>
</select>
<p class="description">Wie lange können Nutzer ihre eigenen Posts bearbeiten? Moderatoren sind ausgenommen.</p>
</td>
</tr>
<tr>
<th scope="row"><label>Spam-Schutz (Honeypot + Zeitlimit)</label></th>
<td>
<select name="spam_min_seconds">
<?php
$ss = $s['spam_min_seconds'] ?? '30';
$sopts = ['0'=>'Deaktiviert','10'=>'10 Sekunden','30'=>'30 Sekunden','60'=>'1 Minute','300'=>'5 Minuten'];
foreach ($sopts as $v=>$l): ?>
<option value="<?php echo $v; ?>"<?php selected($ss,(string)$v); ?>><?php echo esc_html($l); ?></option>
<?php endforeach; ?>
</select>
<p class="description">Mindestzeit zwischen Seitenaufruf und Absenden des Registrierungsformulars. Plus verstecktes Honeypot-Feld.</p>
</td>
</tr>
<tr>
<th scope="row"><label>Flood Control (Post-Cooldown)</label></th>
<td>
<select name="flood_interval">
<?php
$fi = $s['flood_interval'] ?? '30';
$fopts = ['0'=>'Deaktiviert','10'=>'10 Sekunden','30'=>'30 Sekunden','60'=>'1 Minute','120'=>'2 Minuten','300'=>'5 Minuten'];
foreach ($fopts as $v=>$l): ?>
<option value="<?php echo $v; ?>"<?php selected($fi,(string)$v); ?>><?php echo esc_html($l); ?></option>
<?php endforeach; ?>
</select>
<p class="description">Wartezeit zwischen zwei Beiträgen (Thread oder Antwort). Moderatoren sind ausgenommen.</p>
</td>
</tr>
</table>
<!-- ── Wartungsmodus ────────────────────────────────── -->
<h2 style="border-bottom:1px solid #ddd;padding-bottom:.4rem;margin-top:1.5rem">
🔧 Wartungsmodus
</h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><label>Wartungsmodus</label></th>
<td>
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" name="maintenance_mode" value="1"
<?php checked($s['maintenance_mode'] ?? '0', '1'); ?>
style="width:18px;height:18px;accent-color:#ef4444">
<span style="font-weight:600;color:#dc2626">Wartungsmodus aktivieren</span>
</label>
<p class="description">
Wenn aktiv sehen alle Besucher die Wartungsseite — außer Moderatoren und Admins.
</p>
<?php if (($s['maintenance_mode'] ?? '0') === '1'): ?>
<div style="margin-top:6px;padding:6px 10px;background:#fef2f2;border:1px solid #fca5a5;border-radius:5px;font-size:.82rem;color:#dc2626">
⚠️ <strong>Wartungsmodus ist gerade aktiv!</strong> Das Forum ist für normale Besucher nicht erreichbar.
</div>
<?php endif; ?>
</td>
</tr>
<tr>
<th scope="row"><label>Wartungs-Titel</label></th>
<td>
<input type="text" name="maintenance_title"
value="<?php echo esc_attr($s['maintenance_title'] ?? 'Wartungsarbeiten'); ?>"
class="regular-text" placeholder="Wartungsarbeiten">
</td>
</tr>
<tr>
<th scope="row"><label>Wartungs-Nachricht</label></th>
<td>
<textarea name="maintenance_message" rows="3" class="large-text"
placeholder="Das Forum wird gerade gewartet..."><?php echo esc_textarea($s['maintenance_message'] ?? ''); ?></textarea>
<p class="description">Wird den Besuchern auf der Wartungsseite angezeigt.</p>
</td>
</tr>
</table>
<!-- ── Registrierung ────────────────────────────────── -->
<h2 style="border-bottom:1px solid #ddd;padding-bottom:.4rem;margin-top:1.5rem">
📝 Registrierung
</h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><label>Registrierungsmodus</label></th>
<td>
<select name="registration_mode">
<?php
$reg_mode = $s['registration_mode'] ?? 'open';
$modes = ['open'=>'Offen (jeder kann sich registrieren)','invite'=>'Nur Einladung (zeigt Hinweistext)','disabled'=>'Gesperrt (kein Login, kein Register)'];
foreach ($modes as $val=>$lbl): ?>
<option value="<?php echo esc_attr($val); ?>"<?php selected($reg_mode,$val); ?>><?php echo esc_html($lbl); ?></option>
<?php endforeach; ?>
</select>
<p class="description">Steuert ob neue Nutzer sich selbst registrieren können.</p>
</td>
</tr>
<tr>
<th scope="row"><label>Hinweis-Text</label></th>
<td>
<input type="text" name="invite_message"
value="<?php echo esc_attr($s['invite_message'] ?? ''); ?>"
class="large-text"
placeholder="Registrierung ist aktuell nur auf Einladung möglich.">
<p class="description">Wird angezeigt wenn Modus = "Nur Einladung". Kein HTML.</p>
</td>
</tr>
</table>
<!-- ── Forum-Regeln / Nutzungsbedingungen ───────────── -->
<h2 style="border-bottom:1px solid #ddd;padding-bottom:.4rem;margin-top:1.5rem">
📜 Forum-Regeln / Nutzungsbedingungen
</h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row">Regeln aktivieren</th>
<td>
<label>
<input type="checkbox" name="rules_enabled" value="1"
<?php checked( $s['rules_enabled'] ?? '1', '1' ); ?>>
Regelseite im Forum anzeigen (<code>?forum_rules</code>)
</label>
</td>
</tr>
<tr>
<th scope="row">Akzeptierung Pflicht</th>
<td>
<label>
<input type="checkbox" name="rules_accept_required" value="1"
<?php checked( $s['rules_accept_required'] ?? '1', '1' ); ?>>
Nutzer müssen Regeln bei Registrierung akzeptieren
</label>
</td>
</tr>
<tr>
<th scope="row"><label for="wbf_rules_title">Seiten-Titel</label></th>
<td>
<input type="text" id="wbf_rules_title" name="rules_title"
value="<?php echo esc_attr( $s['rules_title'] ?? 'Forum-Regeln & Nutzungsbedingungen' ); ?>"
class="large-text" placeholder="Forum-Regeln & Nutzungsbedingungen">
</td>
</tr>
<tr>
<th scope="row"><label for="wbf_rules_content">Regeltext</label></th>
<td>
<textarea id="wbf_rules_content" name="rules_content"
rows="16" class="large-text"
placeholder="Schreibe deine Forum-Regeln hier…"
style="font-family:monospace;font-size:.85rem"><?php echo esc_textarea( $s['rules_content'] ?? '' ); ?></textarea>
<p class="description">
Unterstützt einfaches Markdown-ähnliches Formatting:
<code>**fett**</code>, Leerzeile = neuer Absatz, Zeilen die mit <code>**1.</code> beginnen werden als Abschnitt-Überschriften dargestellt.
</p>
</td>
</tr>
</table>
<!-- ── Ignore / Block-System ────────────────────────── -->
<h2 style="border-bottom:1px solid #ddd;padding-bottom:.4rem;margin-top:1.5rem">
🚫 Ignore / Block-System
</h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><label>Nicht blockierbare Rollen</label></th>
<td>
<?php
$blocked_roles = array_filter( array_map('trim', explode(',', $s['ignore_blocked_roles'] ?? 'superadmin,admin,moderator')) );
$all_roles = WBF_Roles::get_sorted();
foreach ( $all_roles as $key => $role ):
$is_superadmin = ($key === 'superadmin');
$is_checked = in_array($key, $blocked_roles, true);
$rc = esc_attr($role['color']);
$rb = esc_attr($role['bg_color']);
?>
<label style="display:flex;align-items:center;gap:8px;margin-bottom:7px;cursor:<?php echo $is_superadmin?'not-allowed':'pointer'; ?>">
<input type="checkbox"
name="ignore_blocked_roles[]"
value="<?php echo esc_attr($key); ?>"
<?php checked($is_checked, true); ?>
<?php echo $is_superadmin ? 'disabled' : ''; ?>
style="width:16px;height:16px;accent-color:<?php echo $rc; ?>">
<?php if ($is_superadmin): ?>
<!-- superadmin immer als hidden mitschicken da disabled nicht übermittelt wird -->
<input type="hidden" name="ignore_blocked_roles[]" value="superadmin">
<?php endif; ?>
<span style="display:inline-flex;align-items:center;gap:5px;padding:2px 9px;border-radius:20px;font-size:.78rem;font-weight:700;color:<?php echo $rc; ?>;background:<?php echo $rb; ?>;border:1px solid <?php echo $rc; ?>">
<i class="<?php echo esc_attr($role['icon'] ?? 'fas fa-user'); ?>"></i>
<?php echo esc_html($role['label']); ?>
</span>
<?php if ($is_superadmin): ?>
<span style="font-size:.72rem;color:#999">(immer geschützt)</span>
<?php endif; ?>
</label>
<?php endforeach; ?>
<p class="description" style="margin-top:8px">
Nutzer mit diesen Rollen können von anderen Mitgliedern <strong>nicht</strong> geblockt oder ignoriert werden.
Superadmin ist permanent geschützt und kann nicht abgewählt werden.
</p>
</td>
</tr>
</table>
<!-- ══════════════════════════════════════════════════════════
DISCORD-INTEGRATION
══════════════════════════════════════════════════════════ -->
<h2 style="border-bottom:1px solid #ddd;padding-bottom:.4rem;margin-top:2rem">
<span style="color:#5865f2">🎮</span> Discord-Integration
</h2>
<p class="description" style="margin-bottom:1rem">
Bot-Token und Guild-ID findest du im <a href="https://discord.com/developers/applications" target="_blank">Discord Developer Portal</a>.
Der Bot muss Mitglied deines Servers sein und die Berechtigung <strong>Direct Messages lesen/senden</strong> sowie
<strong>Server-Mitglieder verwalten</strong> besitzen.
</p>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><label for="wbf_discord_bot_token">Bot-Token</label></th>
<td>
<input type="password" id="wbf_discord_bot_token" name="discord_bot_token"
value="<?php echo esc_attr($s['discord_bot_token']); ?>"
class="regular-text" autocomplete="off" placeholder="Bot-Token aus dem Developer Portal">
<p class="description">Niemals öffentlich teilen! Wird verschlüsselt in der Datenbank gespeichert.</p>
</td>
</tr>
<tr>
<th scope="row"><label for="wbf_discord_guild_id">Server-ID (Guild ID)</label></th>
<td>
<input type="text" id="wbf_discord_guild_id" name="discord_guild_id"
value="<?php echo esc_attr($s['discord_guild_id']); ?>"
class="regular-text" placeholder="z. B. 123456789012345678">
<p class="description">Rechtsklick auf deinen Server → ID kopieren (Entwicklermodus muss aktiv sein).</p>
</td>
</tr>
<tr>
<th scope="row"><label for="wbf_discord_client_id">Client ID (optional)</label></th>
<td>
<input type="text" id="wbf_discord_client_id" name="discord_client_id"
value="<?php echo esc_attr($s['discord_client_id']); ?>"
class="regular-text" placeholder="Application ID">
<p class="description">Für zukünftige OAuth2-Unterstützung. Aktuell optional.</p>
</td>
</tr>
<tr>
<th scope="row"><label for="wbf_discord_client_secret">Client Secret (optional)</label></th>
<td>
<input type="password" id="wbf_discord_client_secret" name="discord_client_secret"
value="<?php echo esc_attr($s['discord_client_secret']); ?>"
class="regular-text" autocomplete="off" placeholder="Client Secret">
</td>
</tr>
<tr>
<th scope="row">Rollen-Sync aktivieren</th>
<td>
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" name="discord_role_sync" value="1"
<?php checked('1', $s['discord_role_sync'] ?? '0'); ?>>
Discord-Serverrollen automatisch auf Forum-Rollen mappen
</label>
<p class="description">
Wenn aktiviert, wird bei jedem Login und stündlich per Cron die Discord-Rolle des Nutzers
geprüft und die Forum-Rolle entsprechend der unten definierten Zuordnung aktualisiert.
</p>
</td>
</tr>
</table>
<!-- Discord Rollen-Map -->
<h3 style="margin-top:1.5rem">🔗 Discord-Rollen → Forum-Rollen Zuordnung</h3>
<p class="description" style="margin-bottom:.75rem">
Trage die Discord-Rollen-ID und die gewünschte Forum-Rolle ein.
Mehrere Einträge werden der Reihe nach geprüft — der erste Treffer gewinnt.
</p>
<?php
$role_map_raw = $s['discord_role_map'] ?? '{}';
$role_map = json_decode($role_map_raw, true) ?: [];
$forum_roles = WBF_Roles::get_sorted();
// Sicherstellen dass mindestens eine leere Zeile zum Hinzufügen da ist
if ( empty($role_map) ) $role_map[''] = '';
?>
<table class="widefat" id="wbf-discord-role-map" style="max-width:680px;margin-bottom:.75rem">
<thead><tr>
<th style="width:50%">Discord Rollen-ID</th>
<th style="width:40%">Forum-Rolle</th>
<th style="width:10%"></th>
</tr></thead>
<tbody>
<?php foreach ( $role_map as $dc_id => $fr_key ) : ?>
<tr class="wbf-role-map-row">
<td><input type="text" name="discord_role_id[]"
value="<?php echo esc_attr($dc_id); ?>"
placeholder="Discord Rollen-ID"
class="widefat" style="font-family:monospace"></td>
<td>
<select name="discord_forum_role[]" class="widefat">
<option value="">— wählen —</option>
<?php foreach ( $forum_roles as $rk => $role ) :
if ( $rk === 'superadmin' ) continue; ?>
<option value="<?php echo esc_attr($rk); ?>"
<?php selected($rk, $fr_key); ?>>
<?php echo esc_html($role['label']); ?>
</option>
<?php endforeach; ?>
</select>
</td>
<td><button type="button" class="button button-small wbf-rm-role-row"
style="color:#c00">✕</button></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<button type="button" class="button" id="wbf-add-role-row">+ Zeile hinzufügen</button>
<script>
(function(){
document.getElementById('wbf-add-role-row').addEventListener('click', function(){
var tbody = document.querySelector('#wbf-discord-role-map tbody');
var row = document.querySelector('.wbf-role-map-row').cloneNode(true);
row.querySelectorAll('input').forEach(function(i){i.value='';});
row.querySelectorAll('select').forEach(function(s){s.selectedIndex=0;});
tbody.appendChild(row);
});
document.addEventListener('click', function(e){
if (e.target.classList.contains('wbf-rm-role-row')) {
var rows = document.querySelectorAll('.wbf-role-map-row');
if (rows.length > 1) e.target.closest('tr').remove();
}
});
})();
</script>
<!-- Test-Verbindung -->
<div style="margin-top:1.25rem;padding:1rem;background:#f0f7ff;border:1px solid #c3dafe;border-radius:6px;max-width:680px">
<strong>🔌 Verbindungstest</strong><br>
<p style="margin:.4rem 0 .75rem;color:#374151;font-size:.9rem">
Speichere zuerst die Einstellungen, dann klicke „Testen" um zu prüfen ob der Bot erreichbar ist.
</p>
<button type="button" class="button button-secondary" id="wbf-discord-test-btn">
🔌 Discord-Verbindung testen
</button>
<span id="wbf-discord-test-result" style="margin-left:10px;font-weight:600"></span>
</div>
<script>
document.getElementById('wbf-discord-test-btn').addEventListener('click', function(){
var btn = this;
var res = document.getElementById('wbf-discord-test-result');
btn.disabled = true;
res.textContent = '⏳ Teste…';
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: 'action=wbf_discord_test&nonce=<?php echo wp_create_nonce("wbf_discord_test"); ?>'
})
.then(r => r.json())
.then(function(d){
if (d.success) {
res.style.color = '#16a34a';
res.textContent = '✅ ' + (d.data.message || 'Verbunden!');
} else {
res.style.color = '#dc2626';
res.textContent = '❌ ' + ((d.data && d.data.message) || 'Fehler');
}
btn.disabled = false;
})
.catch(function(){ res.style.color='#dc2626'; res.textContent='❌ Netzwerkfehler'; btn.disabled=false; });
});
</script>
<!-- ══════════════════════════════════════════════════════════
Minecraft Bridge
════════════════════════════════════════════════════════════ -->
<div class="wbf-settings-box" style="margin-top:2rem;padding:1.5rem;border:1px solid #e5e7eb;border-radius:8px;background:#f9fafb">
<h2 style="margin-top:0;display:flex;align-items:center;gap:8px">
<span style="font-size:1.3em">⛏️</span> Minecraft Bridge
</h2>
<p class="description" style="margin-bottom:1.2rem;color:#6b7280">
Verbindet das Forum mit deinem BungeeCord-Server (StatusAPI Plugin).
Spieler können ihren Forum-Account mit <code>/forumlink &lt;token&gt;</code> verknüpfen
und erhalten dann Ingame-Benachrichtigungen bei neuen Antworten, Erwähnungen und PNs.
</p>
<table class="form-table" role="presentation">
<tr>
<th scope="row">Aktiviert</th>
<td>
<label>
<input type="checkbox" name="mc_bridge_enabled" value="1"
<?php checked( '1', $s['mc_bridge_enabled'] ?? '0' ); ?>>
Minecraft Bridge aktivieren
</label>
</td>
</tr>
<tr>
<th scope="row"><label for="wbf_mc_api_url">StatusAPI URL</label></th>
<td>
<input type="url" id="wbf_mc_api_url" name="mc_bridge_api_url"
value="<?php echo esc_attr( $s['mc_bridge_api_url'] ?? '' ); ?>"
class="regular-text"
placeholder="http://dein-server:9191">
<p class="description">
IP + Port deines BungeeCord StatusAPI Servers.
Beispiel: <code>http://192.168.1.100:9191</code>
</p>
</td>
</tr>
<tr>
<th scope="row"><label for="wbf_mc_api_secret">API Secret</label></th>
<td>
<input type="password" id="wbf_mc_api_secret" name="mc_bridge_api_secret"
value="<?php echo esc_attr( $s['mc_bridge_api_secret'] ?? '' ); ?>"
class="regular-text"
autocomplete="new-password"
placeholder="Gemeinsames Passwort">
<p class="description">
Muss identisch sein mit <code>forum.api_secret</code> in der
<code>verify.properties</code> des StatusAPI Plugins.
</p>
</td>
</tr>
<tr>
<th scope="row">Verbindungstest</th>
<td>
<button type="button" id="wbf-mc-test-btn" class="button"
onclick="wbfTestMcConnection()">
🔌 Verbindung testen
</button>
<span id="wbf-mc-test-result" style="margin-left:10px;font-weight:600"></span>
<script>
function wbfTestMcConnection() {
var btn = document.getElementById('wbf-mc-test-btn');
var result = document.getElementById('wbf-mc-test-result');
var url = document.getElementById('wbf_mc_api_url').value.replace(/\/$/, '');
if (!url) { result.style.color='#dc2626'; result.textContent = '❌ Bitte erst eine URL eingeben.'; return; }
btn.disabled = true;
result.style.color = '#6b7280';
result.textContent = '⏳ Teste Verbindung...';
// Test gegen WordPress REST-Endpoint (sicherer als direkter BungeeCord-Aufruf vom Browser)
fetch('<?php echo esc_url( rest_url("mc-bridge/v1/status") ); ?>')
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.success) {
result.style.color = '#16a34a';
result.innerHTML = '✅ <strong>WordPress-Endpoint aktiv!</strong> Plugin v' + (d.version || '?');
} else {
result.style.color = '#dc2626';
result.textContent = '⚠️ Endpoint antwortet, aber Fehler: ' + JSON.stringify(d);
}
})
.catch(function(e) {
result.style.color = '#dc2626';
result.textContent = '❌ Nicht erreichbar: ' + e.message;
})
.finally(function() { btn.disabled = false; });
}
</script>
<p class="description" style="margin-top:.5rem">
Testet ob der WordPress REST-Endpoint <code>/wp-json/mc-bridge/v1/status</code> erreichbar ist.
Danach in <code>verify.properties</code>: <code>forum.wp_url</code> und <code>forum.api_secret</code> eintragen.
</p>
</td>
</tr>
</table>
<div style="background:#fffbeb;border:1px solid #fcd34d;border-radius:6px;padding:1rem;margin-top:1rem;font-size:.875rem">
<strong>⚙️ Einrichtung in 3 Schritten:</strong>
<ol style="margin:.5rem 0 0 1.2rem;padding:0;line-height:1.8">
<li>API Secret hier festlegen und Einstellungen speichern.</li>
<li>In <code>verify.properties</code> des BungeeCord-Plugins setzen:
<br><code>forum.enabled=true</code>
<br><code>forum.wp_url=<?php echo esc_html( get_site_url() ); ?></code>
<br><code>forum.api_secret=DEIN_SECRET</code>
</li>
<li>Spieler können sich nun mit <strong><code>/forumlink &lt;token&gt;</code></strong> ingame verknüpfen.
Den Token generieren sie in ihrem Forum-Profil unter dem Tab <em>Verbindungen</em>.</li>
</ol>
</div>
</div>
<?php submit_button(
'💾 Einstellungen speichern',
'primary',
'wbf_save_settings',
true,
[ 'style' => 'margin-top:1rem' ]
); ?>
<!-- ── Vorschau-Tabelle ──────────────────────────────── -->
<hr style="margin-top:2.5rem">
<h3>📋 Aktuelle Werte</h3>
<table class="widefat striped" style="max-width:700px">
<thead>
<tr><th>Schlüssel</th><th>Aktueller Wert</th></tr>
</thead>
<tbody>
<?php foreach ( $s as $key => $val ): ?>
<tr>
<td><code><?php echo esc_html( $key ); ?></code></td>
<td><?php echo esc_html( $val ); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php
}
} // end function_exists wbf_admin_settings