Files
WP-Business-Forum/includes/class-forum-shortcodes.php
2026-03-29 22:25:37 +02:00

2742 lines
164 KiB
PHP
Raw 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
if ( ! defined( 'ABSPATH' ) ) exit;
class WBF_Shortcodes {
public static function init() {
add_shortcode('business_forum', [__CLASS__, 'forum_main']);
}
// ── Helpers ───────────────────────────────────────────────────────────────
/** Alter aus Geburtsdatum berechnen */
public static function calc_age( $date_str ) {
if ( ! $date_str || ! preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_str) ) return null;
$birth = new DateTime( $date_str );
$today = new DateTime();
if ( $birth > $today ) return null;
return (int) $birth->diff($today)->y;
}
public static function time_ago( $datetime ) {
$diff = time() - strtotime($datetime);
if ($diff < 60) return 'Gerade eben';
if ($diff < 3600) return floor($diff/60) . ' Min. ago';
if ($diff < 86400) return floor($diff/3600) . ' Std. ago';
if ($diff < 604800) return floor($diff/86400). ' Tage ago';
return date_i18n('d.m.Y', strtotime($datetime));
}
public static function avatar( $url, $name, $size = 40, $online = false ) {
$src = esc_url($url ?: 'https://www.gravatar.com/avatar/0?d=identicon&s='.$size);
$img = "<img src=\"$src\" alt=\"".esc_attr($name)."\" class=\"wbf-avatar\" width=\"$size\" height=\"$size\">";
if (!$online) return $img;
return "<span class=\"wbf-avatar-wrap\">$img<span class=\"wbf-online-dot\"></span></span>";
}
public static function role_badge( $role ) {
// Dynamisch aus WBF_Roles — deckt alle Rollen inkl. superadmin ab
return WBF_Roles::badge( $role );
}
public static function render_prefix( $thread ) {
if ( empty($thread->prefix_label) ) return '';
$label = esc_html($thread->prefix_label);
$color = esc_attr($thread->prefix_color ?? '#fff');
$bg = esc_attr($thread->prefix_bg ?? '#475569');
return "<span class=\"wbf-prefix-badge\" style=\"color:{$color};background:{$bg}\">{$label}</span>";
}
public static function render_tags( $tags, $small = false ) {
if ( empty($tags) ) return '';
$cls = $small ? 'wbf-tag wbf-tag--sm' : 'wbf-tag';
$out = '<div class="wbf-tag-list">';
foreach ( $tags as $tag ) {
$out .= '<a href="?forum_tag=' . esc_attr($tag->slug) . '" class="' . $cls . '">'
. '<i class="fas fa-hashtag"></i>' . esc_html($tag->name) . '</a>';
}
return $out . '</div>';
}
private static function like_btn( $object_id, $type, $count, $liked = false ) {
// Keep for thread likes (header area)
$cls = $liked ? ' wbf-liked' : '';
return sprintf(
'<button class="wbf-like-btn%s" data-id="%d" data-type="%s">
<i class="fas fa-thumbs-up"></i>
<span class="wbf-like-count">%d</span>
</button>',
$cls, $object_id, $type, $count
);
}
private static function reaction_bar( $object_id, $object_type, $current_user ) {
$emojis = WBF_DB::get_allowed_reactions();
$user_id = $current_user ? (int)$current_user->id : 0;
$data = WBF_DB::get_reactions($object_id, $object_type, $user_id);
$counts = $data['counts'];
$mine = $data['mine'];
$total = array_sum($counts);
$out = '<div class="wbf-reaction-bar" data-id="' . $object_id . '" data-type="' . esc_attr($object_type) . '">';
// Summary of existing reactions (clickable)
if ($total > 0) {
$out .= '<div class="wbf-reaction-summary">';
foreach ($emojis as $e) {
if (!empty($counts[$e])) {
$active = ($mine === $e) ? ' wbf-reaction-active' : '';
$out .= '<button class="wbf-reaction-pill' . $active . '" data-reaction="' . esc_attr($e) . '">'
. $e . '<span>' . (int)$counts[$e] . '</span></button>';
}
}
$out .= '</div>';
}
// Picker trigger (only for logged-in users with 'like' perm)
if ($current_user && WBF_DB::can($current_user, 'like')) {
$out .= '<div class="wbf-reaction-wrap">'
. '<button class="wbf-reaction-trigger" title="Reaktion hinzufügen">😊 <i class="fas fa-plus" style="font-size:.55rem"></i></button>'
. '<div class="wbf-reaction-picker">';
foreach ($emojis as $e) {
$active = ($mine === $e) ? ' wbf-reaction-active' : '';
$out .= '<button class="wbf-reaction-btn' . $active . '" data-reaction="' . esc_attr($e) . '" title="' . esc_attr($e) . '">' . $e . '</button>';
}
$out .= '</div></div>';
}
$out .= '</div>';
return $out;
}
private static function mod_tools_thread( $thread, $current ) {
if (!$current) return '';
// Only show mod bar to users who have at least one mod-level permission
$can_pin = WBF_DB::can($current, 'pin_thread');
$can_close = WBF_DB::can($current, 'close_thread');
$can_delete = WBF_DB::can($current, 'delete_thread');
$can_move = WBF_DB::can($current, 'manage_cats');
if ( !$can_pin && !$can_close && !$can_delete && !$can_move ) return '';
$is_archived = $thread->status === 'archived';
$pin_label = $thread->pinned ? '<i class="fas fa-thumbtack-slash"></i> Entpinnen' : '<i class="fas fa-thumbtack"></i> Pinnen';
$pin_action = $thread->pinned ? 'unpin_thread' : 'pin_thread';
$arch_action = $is_archived ? 'unarchive_thread' : 'archive_thread';
$arch_label = $is_archived
? '<i class="fas fa-box-open"></i> Wiederherstellen'
: '<i class="fas fa-box-archive"></i> Archivieren';
// Close/Open only makes sense on non-archived threads
$cls_btn = '';
if ( !$is_archived && $can_close ) {
$cls_action = $thread->status==='closed' ? 'open_thread' : 'close_thread';
$cls_label = $thread->status==='closed' ? '<i class="fas fa-lock-open"></i> Öffnen' : '<i class="fas fa-lock"></i> Schließen';
$cls_btn = '<button class="wbf-mod-btn" data-action="'.$cls_action.'" data-id="'.esc_attr($thread->id).'">'.$cls_label.'</button>';
}
$move_btn = $can_move
? '<button class="wbf-mod-btn wbf-mod-btn--move" data-action="open_move" data-id="'.esc_attr($thread->id).'"><i class="fas fa-right-left"></i> Verschieben</button>'
: '';
$del_btn = $can_delete
? '<button class="wbf-mod-btn wbf-mod-btn--danger" data-action="delete_thread" data-id="'.esc_attr($thread->id).'"><i class="fas fa-trash"></i> Löschen</button>'
: '';
return '<div class="wbf-mod-bar">
<span class="wbf-mod-bar__label"><i class="fas fa-shield-halved"></i> Mod</span>
'.( $can_pin ? '<button class="wbf-mod-btn" data-action="'.$pin_action.'" data-id="'.esc_attr($thread->id).'">'.$pin_label.'</button>' : '').'
'.$cls_btn.'
'.( $can_close ? '<button class="wbf-mod-btn wbf-mod-btn--archive" data-action="'.$arch_action.'" data-id="'.esc_attr($thread->id).'">'.$arch_label.'</button>' : '').'
'.$move_btn.'
'.$del_btn.'
</div>';
}
private static function mod_tools_post( $post_id, $current ) {
if (!$current || !WBF_DB::can($current,'delete_post')) return '';
return '<button class="wbf-mod-btn wbf-mod-btn--danger wbf-mod-btn--sm" data-action="delete_post" data-id="'.esc_attr($post_id).'"><i class="fas fa-trash"></i></button>';
}
// ── Router ────────────────────────────────────────────────────────────────
public static function forum_main( $atts ) {
// Server-seitiger Logout-Fallback — Nonce-Schutz gegen CSRF
if (isset($_GET['wbf_do_logout'])) {
if ( ! isset($_GET['_wpnonce']) || ! wp_verify_nonce( sanitize_text_field($_GET['_wpnonce']), 'wbf_logout' ) ) {
wp_redirect( wbf_get_forum_url() );
exit;
}
WBF_Auth::logout();
wp_redirect( wbf_get_forum_url() );
exit;
}
// ── Wartungsmodus — zentraler Check vor allem anderen ────────────────
$wbf_current_user = WBF_Auth::get_current_user();
$wbf_maint = wbf_get_settings()['maintenance_mode'] ?? '0';
if ( $wbf_maint === '1' ) {
$is_staff = $wbf_current_user && WBF_Roles::level($wbf_current_user->role) >= 50;
if ( ! $is_staff ) {
return self::view_maintenance();
}
}
if (isset($_GET['forum_members'])) return self::view_members();
// Einladungscode aus URL vorausfüllen
if (isset($_GET['wbf_invite'])) {
$inv_code = strtoupper(sanitize_text_field($_GET['wbf_invite']));
if (!WBF_DB::verify_invite($inv_code)) {
// Ungültiger Code — zeige Meldung
ob_start(); ?>
<div class="wbf-wrap"><?php self::render_topbar(null); ?>
<div class="wbf-container wbf-mt">
<div class="wbf-notice wbf-notice--warning">
<i class="fas fa-exclamation-triangle"></i>
Dieser Einladungslink ist ungültig oder bereits abgelaufen.
</div>
<div style="margin-top:1rem">
<a href="<?php echo esc_url(wbf_get_forum_url()); ?>" class="wbf-btn wbf-btn--sm">← Zurück zum Forum</a>
</div>
</div></div>
<?php return ob_get_clean();
}
}
if (isset($_GET['wbf_reset_token'])) return self::view_reset_password();
if (isset($_GET['forum_rules'])) return self::view_rules();
if (isset($_GET['forum_thread'])) return self::view_thread();
if (isset($_GET['forum_cat'])) return self::view_category();
if (isset($_GET['forum_profile'])) return self::view_profile();
if (isset($_GET['forum_search'])) return self::view_search();
if (isset($_GET['forum_tag'])) return self::view_tag();
if (isset($_GET['forum_dm'])) return self::view_dm();
return self::view_home();
}
/** Darf der Nutzer diese Kategorie sehen? */
private static function can_see_category( $user, $cat ) {
// Gäste: guest_visible prüfen
if ( ! $user && (int)($cat->guest_visible ?? 1) === 0 ) return false;
// Min-Rolle: Nutzer muss mindestens diese Rolle haben um die Kategorie zu sehen
if ( $cat->min_role && $cat->min_role !== 'member' ) {
if ( ! $user ) return false; // nicht eingeloggt → versteckt wenn min_role > member
if ( WBF_Roles::level($user->role) < WBF_Roles::level($cat->min_role) ) return false;
}
return true;
}
// ── HOME ──────────────────────────────────────────────────────────────────
private static function view_home() {
$current = WBF_Auth::get_current_user();
$tree = WBF_DB::get_categories_tree();
$stats = WBF_DB::get_stats();
$recent = WBF_DB::get_recent_threads(5);
ob_start(); ?>
<div class="wbf-wrap">
<?php self::render_topbar($current); ?>
<div class="wbf-hero">
<div class="wbf-hero__inner">
<h1 class="wbf-hero__title"><i class="fas fa-comments"></i> <?php echo esc_html(wbf_get_settings()['hero_title']); ?></h1>
<p class="wbf-hero__sub"><?php echo esc_html(wbf_get_settings()['hero_subtitle']); ?></p>
</div>
<div class="wbf-stats">
<div class="wbf-stat"><span class="wbf-stat__num"><?php echo number_format((int)$stats['threads']); ?></span><span><?php echo esc_html(wbf_get_settings()['stat_threads']); ?></span></div>
<div class="wbf-stat"><span class="wbf-stat__num"><?php echo number_format((int)$stats['posts']); ?></span><span><?php echo esc_html(wbf_get_settings()['stat_posts']); ?></span></div>
<div class="wbf-stat">
<?php if ($current): ?>
<a href="?forum_members=1" style="text-decoration:none;color:inherit">
<span class="wbf-stat__num wbf-stat__num--link"><?php echo number_format((int)$stats['members']); ?></span>
<span><?php echo esc_html(wbf_get_settings()['stat_members']); ?></span>
</a>
<?php else: ?>
<span class="wbf-stat__num"><?php echo number_format((int)$stats['members']); ?></span>
<span><?php echo esc_html(wbf_get_settings()['stat_members']); ?></span>
<?php endif; ?>
</div>
</div>
</div>
<div class="wbf-container">
<div class="wbf-grid">
<main class="wbf-main">
<div class="wbf-section-header">
<h2><?php echo esc_html(wbf_get_settings()['section_cats']); ?></h2>
<?php if (WBF_DB::can($current,'create_thread')): ?>
<div style="display:flex;gap:.5rem;flex-wrap:wrap">
<button class="wbf-btn wbf-btn--outline-poll" onclick="wbfShowNewPoll()"><i class="fas fa-chart-bar"></i> Neue Umfrage</button>
<button class="wbf-btn wbf-btn--primary" onclick="wbfShowNewThread()"><i class="fas fa-plus"></i> <?php echo esc_html(wbf_get_settings()['btn_new_thread']); ?></button>
</div>
<?php endif; ?>
</div>
<div class="wbf-cat-group-list">
<?php foreach ($tree as $parent):
if (!self::can_see_category($current, $parent)) continue; ?>
<div class="wbf-cat-group">
<div class="wbf-cat-parent">
<div class="wbf-cat-parent__icon"><i class="<?php echo esc_attr($parent->icon); ?>"></i></div>
<div class="wbf-cat-parent__body">
<a href="?forum_cat=<?php echo esc_attr($parent->slug); ?>" class="wbf-cat-parent__name"><?php echo esc_html($parent->name); ?></a>
<p class="wbf-cat-parent__desc"><?php echo esc_html($parent->description); ?></p>
</div>
<div class="wbf-cat-parent__stats">
<span><i class="fas fa-layer-group"></i> <?php echo (int)$parent->thread_count; ?></span>
<span><i class="fas fa-comment"></i> <?php echo (int)$parent->post_count; ?></span>
<?php if (($parent->min_role??'member') !== 'member'): ?>
<span class="wbf-cat-lock"><i class="fas fa-lock"></i> <?php echo esc_html(ucfirst($parent->min_role)); ?>+</span>
<?php endif; ?>
</div>
</div>
<?php if (!empty($parent->children)): ?>
<div class="wbf-cat-children">
<?php foreach ($parent->children as $child):
if (!self::can_see_category($current, $child)) continue; ?>
<div class="wbf-cat-child">
<div class="wbf-cat-child__icon"><i class="<?php echo esc_attr($child->icon); ?>"></i></div>
<div class="wbf-cat-child__body">
<a href="?forum_cat=<?php echo esc_attr($child->slug); ?>" class="wbf-cat-child__name"><?php echo esc_html($child->name); ?></a>
<span class="wbf-cat-child__desc"><?php echo esc_html($child->description); ?></span>
</div>
<div class="wbf-cat-child__meta">
<span><i class="fas fa-layer-group"></i> <?php echo (int)$child->thread_count; ?></span>
<span><i class="fas fa-comment"></i> <?php echo (int)$child->post_count; ?></span>
<?php if ($child->last_thread_title): ?>
<a href="?forum_thread=<?php echo (int)$child->last_thread_id; ?>" class="wbf-cat-child__last"><i class="fas fa-clock"></i> <?php echo esc_html(substr($child->last_thread_title,0,30)); ?>…</a>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</main>
<aside class="wbf-sidebar">
<div class="wbf-section-header"><h2><?php echo $current ? '<i class="fas fa-user-circle"></i> '.esc_html(wbf_get_settings()['sidebar_profile']) : '<i class="fas fa-sign-in-alt"></i> '.esc_html(wbf_get_settings()['sidebar_login']); ?></h2></div>
<?php if (!$current): ?>
<div class="wbf-widget"><?php self::render_auth_forms(); ?></div>
<?php else: ?>
<div class="wbf-widget wbf-profile-widget">
<div class="wbf-profile-widget__avatar"><?php echo self::avatar($current->avatar_url, $current->display_name, 60); ?></div>
<div class="wbf-profile-widget__info">
<strong><?php echo esc_html($current->display_name); ?></strong>
<?php echo self::role_badge($current->role); ?>
<span class="wbf-profile-widget__posts"><?php echo (int)$current->post_count; ?> Beiträge</span>
</div>
<div class="wbf-profile-widget__actions">
<a href="?forum_profile=<?php echo (int)$current->id; ?>" class="wbf-btn wbf-btn--sm">Profil</a>
<a href="<?php echo esc_url(wp_nonce_url(wbf_get_forum_url() . '?wbf_do_logout=1', 'wbf_logout')); ?>" class="wbf-btn wbf-btn--sm wbf-btn--outline"><?php echo esc_html(wbf_get_settings()['btn_logout']); ?></a>
</div>
</div>
<?php endif; ?>
<div class="wbf-section-header" style="margin-top:1.25rem"><h2><i class="fas fa-clock"></i> <?php echo esc_html(wbf_get_settings()['section_recent']); ?></h2></div>
<div class="wbf-widget">
<ul class="wbf-recent-list">
<?php foreach ($recent as $r): ?>
<li><a href="?forum_thread=<?php echo (int)$r->id; ?>"><?php echo esc_html(substr($r->title,0,45)); ?></a>
<span><?php echo esc_html($r->cat_name); ?> · <?php echo self::time_ago($r->created_at); ?></span></li>
<?php endforeach; ?>
<?php if (empty($recent)): ?><li style="color:var(--c-muted);font-size:.8rem">Noch keine Threads.</li><?php endif; ?>
</ul>
</div>
<?php $pop_tags = WBF_DB::get_popular_tags(20); if (!empty($pop_tags)): ?>
<div class="wbf-section-header" style="margin-top:1.25rem">
<h2><i class="fas fa-tags"></i> Tags</h2>
</div>
<div class="wbf-widget">
<div class="wbf-tag-cloud">
<?php foreach ($pop_tags as $pt): ?>
<a href="?forum_tag=<?php echo esc_attr($pt->slug); ?>" class="wbf-tag wbf-tag--sm">
<i class="fas fa-hashtag"></i><?php echo esc_html($pt->name); ?>
<span class="wbf-tag__count"><?php echo (int)$pt->use_count; ?></span>
</a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</aside>
</div>
<?php self::render_forum_footer(); ?>
</div>
<?php self::render_new_thread_modal(WBF_DB::get_categories_flat(), $current); ?>
<?php self::render_auth_modal(); ?>
</div>
<?php return ob_get_clean();
}
// ── CATEGORY ──────────────────────────────────────────────────────────────
private static function view_category() {
$slug = sanitize_key($_GET['forum_cat'] ?? '');
$cat = WBF_DB::get_category($slug);
if (!$cat) return '<p class="wbf-notice">Kategorie nicht gefunden.</p>';
$current = WBF_Auth::get_current_user();
// Zugang prüfen — Gäste + Min-Rolle
if (!self::can_see_category($current, $cat)) {
ob_start(); ?>
<div class="wbf-wrap"><?php self::render_topbar($current); ?>
<div class="wbf-container wbf-mt">
<div class="wbf-notice wbf-notice--warning">
<i class="fas fa-lock"></i>
<?php if (!$current): ?>
Diese Kategorie ist nur für eingeloggte Mitglieder sichtbar.
<a href="#" class="wbf-login-link" style="margin-left:.5rem;font-weight:700">Jetzt einloggen</a>
<?php else: ?>
Du hast keine Berechtigung um diese Kategorie zu sehen.
<?php endif; ?>
</div>
</div></div>
<?php return ob_get_clean();
}
$page = max(1,(int)($_GET['fp']??1));
$threads = WBF_DB::get_threads($cat->id, $page);
$total = WBF_DB::count_threads($cat->id);
$pages = ceil($total / 20) ?: 1;
$children = WBF_DB::get_child_categories($cat->id);
$crumbs = WBF_DB::get_category_breadcrumb($cat);
ob_start(); ?>
<div class="wbf-wrap">
<?php self::render_topbar($current); ?>
<div class="wbf-container wbf-mt">
<nav class="wbf-breadcrumb">
<a href="<?php echo esc_url(remove_query_arg(['forum_cat','fp'])); ?>"><i class="fas fa-home"></i> Forum</a>
<?php foreach ($crumbs as $c): ?>
<span>/</span>
<?php if ($c->id===$cat->id): ?><span><?php echo esc_html($c->name); ?></span>
<?php else: ?><a href="?forum_cat=<?php echo esc_attr($c->slug); ?>"><?php echo esc_html($c->name); ?></a><?php endif; ?>
<?php endforeach; ?>
</nav>
<div class="wbf-section-header">
<div><h2><i class="<?php echo esc_attr($cat->icon); ?>"></i> <?php echo esc_html($cat->name); ?></h2>
<p class="wbf-muted"><?php echo esc_html($cat->description); ?></p></div>
<?php if (WBF_DB::can($current,'create_thread') && WBF_DB::can_post_in($current,$cat)): ?>
<div style="display:flex;gap:.5rem;flex-wrap:wrap">
<button class="wbf-btn wbf-btn--outline-poll" onclick="wbfShowNewPoll(<?php echo $cat->id; ?>)"><i class="fas fa-chart-bar"></i> Neue Umfrage</button>
<button class="wbf-btn wbf-btn--primary" onclick="wbfShowNewThread(<?php echo $cat->id; ?>)"><i class="fas fa-plus"></i> <?php echo esc_html(wbf_get_settings()['btn_new_thread']); ?></button>
</div>
<?php endif; ?>
</div>
<?php if (!empty($children)): ?>
<div class="wbf-subcat-list">
<?php foreach ($children as $child):
if (!self::can_see_category($current, $child)) continue; ?>
<a href="?forum_cat=<?php echo esc_attr($child->slug); ?>" class="wbf-subcat-card">
<div class="wbf-subcat-card__icon"><i class="<?php echo esc_attr($child->icon); ?>"></i></div>
<div class="wbf-subcat-card__body">
<span class="wbf-subcat-card__name"><?php echo esc_html($child->name); ?></span>
<span class="wbf-subcat-card__desc"><?php echo esc_html($child->description); ?></span>
</div>
<div class="wbf-subcat-card__stats"><span><?php echo (int)$child->thread_count; ?> Threads</span></div>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if (empty($threads)): ?>
<div class="wbf-empty"><i class="fas fa-inbox"></i><p>Noch keine Threads. Starte die Diskussion!</p></div>
<?php else: ?>
<div class="wbf-thread-list">
<?php foreach ($threads as $t):
$liked = $current ? WBF_DB::has_liked($current->id,$t->id,'thread') : false; ?>
<div class="wbf-thread-row<?php echo $t->pinned?' wbf-thread-row--pinned':''; ?>"
data-thread-id="<?php echo (int)$t->id; ?>"
data-last-reply="<?php echo esc_attr($t->last_reply_at); ?>"
data-preview="<?php echo esc_attr(mb_substr(strip_tags(WBF_BBCode::render($t->content)),0,160)); ?>">
<div class="wbf-thread-row__avatar"><?php echo self::avatar($t->avatar_url,$t->display_name); ?></div>
<div class="wbf-thread-row__body">
<div class="wbf-thread-row__top">
<?php if ($t->pinned): ?><span class="wbf-pin"><i class="fas fa-thumbtack"></i></span><?php endif; ?>
<?php if ($t->status==='closed'): ?><span class="wbf-badge wbf-badge--closed"><i class="fas fa-lock"></i> Geschlossen</span><?php endif; ?>
<?php echo self::render_prefix($t); ?>
<a href="?forum_thread=<?php echo (int)$t->id; ?>" class="wbf-thread-row__title"><?php echo esc_html($t->title); ?></a>
<span class="wbf-new-badge" style="display:none"><i class="fas fa-circle-dot"></i> Neu</span>
</div>
<div class="wbf-thread-row__meta">
<span>von <strong><?php echo esc_html($t->display_name); ?></strong></span>
<?php echo self::role_badge($t->author_role); ?>
<span><?php echo self::time_ago($t->created_at); ?></span>
</div>
<?php $row_tags = WBF_DB::get_thread_tags($t->id); echo self::render_tags($row_tags, true); ?>
</div>
<div class="wbf-thread-row__stats">
<span><i class="fas fa-comment-dots"></i> <?php echo (int)$t->reply_count; ?></span>
<span><i class="fas fa-eye"></i> <?php echo (int)$t->views; ?></span>
<?php echo self::like_btn($t->id,'thread',$t->like_count,$liked); ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php if ($pages>1): ?>
<div class="wbf-pagination">
<?php for ($i=1;$i<=$pages;$i++): ?>
<a href="?forum_cat=<?php echo esc_attr($slug); ?>&fp=<?php echo $i; ?>" class="wbf-page-btn<?php echo ($i==$page)?' active':''; ?>"><?php echo $i; ?></a>
<?php endfor; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
<?php
// Archived threads section (only for mods)
if (WBF_DB::can($current,'close_thread')):
$archived = WBF_DB::get_archived_threads($cat->id, 1, 50);
$arch_count = WBF_DB::count_archived_threads($cat->id);
if ($arch_count > 0):
?>
<div class="wbf-archive-section" id="wbfArchiveSection" style="margin-top:2rem">
<div class="wbf-archive-section__toggle" onclick="document.getElementById('wbfArchiveList').classList.toggle('open');this.classList.toggle('open')">
<i class="fas fa-box-archive"></i>
Archivierte Threads
<span class="wbf-archive-section__count"><?php echo (int)$arch_count; ?></span>
<i class="fas fa-chevron-down" style="margin-left:auto"></i>
</div>
<div class="wbf-archive-list" id="wbfArchiveList">
<?php foreach ($archived as $at): ?>
<div class="wbf-thread-row wbf-thread-row--archived">
<div class="wbf-thread-row__avatar"><?php echo self::avatar($at->avatar_url,$at->display_name); ?></div>
<div class="wbf-thread-row__body">
<div class="wbf-thread-row__top">
<span class="wbf-badge wbf-badge--archived"><i class="fas fa-box-archive"></i> Archiviert</span>
<a href="?forum_thread=<?php echo (int)$at->id; ?>" class="wbf-thread-row__title"><?php echo esc_html($at->title); ?></a>
</div>
<div class="wbf-thread-row__meta">
<span>von <strong><?php echo esc_html($at->display_name); ?></strong></span>
<?php echo self::role_badge($at->author_role); ?>
<span><?php echo self::time_ago($at->created_at); ?></span>
</div>
</div>
<div class="wbf-thread-row__stats">
<span><i class="fas fa-comment-dots"></i> <?php echo (int)$at->reply_count; ?></span>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; endif; ?>
<?php self::render_forum_footer(); ?>
<?php self::render_new_thread_modal(WBF_DB::get_categories_flat(),$current,$cat->id); ?>
<?php self::render_auth_modal(); ?>
</div>
<?php return ob_get_clean();
}
// ── THREAD ────────────────────────────────────────────────────────────────
private static function view_thread() {
// Wartungsmodus
$maint6 = wbf_get_settings()['maintenance_mode'] ?? '0';
$cur6 = WBF_Auth::get_current_user();
if ( $maint6 === '1' && ( !$cur6 || WBF_Roles::level($cur6->role) < 50 ) ) {
return self::view_maintenance();
}
$id = (int)($_GET['forum_thread'] ?? 0);
$thread = WBF_DB::get_thread($id);
if (!$thread) return '<p class="wbf-notice">Thread nicht gefunden.</p>';
// Kategorie-Zugang prüfen (Gäste + Min-Rolle)
$cat6 = WBF_DB::get_category($thread->category_id);
if ($cat6 && !self::can_see_category($cur6, $cat6)) {
ob_start(); ?>
<div class="wbf-wrap"><?php self::render_topbar($cur6); ?>
<div class="wbf-container wbf-mt">
<div class="wbf-notice wbf-notice--warning">
<i class="fas fa-lock"></i>
<?php if (!$cur6): ?>
Dieser Thread ist nur für eingeloggte Mitglieder sichtbar.
<a href="#" class="wbf-login-link" style="margin-left:.5rem;font-weight:700">Jetzt einloggen</a>
<?php else: ?>
Du hast keine Berechtigung um diesen Thread zu sehen.
<?php endif; ?>
</div>
</div></div>
<?php return ob_get_clean();
}
global $wpdb;
$wpdb->query($wpdb->prepare("UPDATE {$wpdb->prefix}forum_threads SET views=views+1 WHERE id=%d",$id));
$page = max(1,(int)($_GET['tp']??1));
$posts = WBF_DB::get_posts($id,$page,15);
$total = WBF_DB::count_posts($id);
$pages = ceil($total/15) ?: 1;
$current = WBF_Auth::get_current_user();
$cat = WBF_DB::get_category($thread->category_id);
$crumbs = $cat ? WBF_DB::get_category_breadcrumb($cat) : [];
$t_liked = $current ? WBF_DB::has_liked($current->id,$id,'thread') : false;
ob_start(); ?>
<div class="wbf-wrap">
<?php self::render_topbar($current); ?>
<div class="wbf-container wbf-mt" data-thread-view="<?php echo (int)$id; ?>">
<nav class="wbf-breadcrumb">
<a href="<?php echo esc_url(remove_query_arg(['forum_thread','tp'])); ?>"><i class="fas fa-home"></i> Forum</a>
<?php foreach ($crumbs as $c): ?>
<span>/</span><a href="?forum_cat=<?php echo esc_attr($c->slug); ?>"><?php echo esc_html($c->name); ?></a>
<?php endforeach; ?>
<span>/</span><span><?php echo esc_html(substr($thread->title,0,40)); ?>…</span>
</nav>
<?php echo self::mod_tools_thread($thread,$current); ?>
<div class="wbf-thread-header">
<h1 class="wbf-thread-title">
<?php if ($thread->pinned): ?><i class="fas fa-thumbtack wbf-pin-icon"></i><?php endif; ?>
<?php if ($thread->status==='closed'): ?><i class="fas fa-lock" style="color:var(--c-muted);font-size:1rem;margin-right:.35rem"></i><?php endif; ?>
<?php if ($thread->status==='archived'): ?><span class="wbf-badge wbf-badge--archived"><i class="fas fa-box-archive"></i> Archiviert</span><?php endif; ?>
<?php echo self::render_prefix($thread); ?>
<?php echo esc_html($thread->title); ?>
</h1>
<div class="wbf-thread-header-meta">
<?php echo self::like_btn($id,'thread',$thread->like_count,$t_liked); ?>
<?php if ($current): $is_bm = WBF_DB::is_bookmarked($current->id,$id); ?>
<button class="wbf-bookmark-btn<?php echo $is_bm?' wbf-bookmarked':''; ?>" data-thread="<?php echo $id; ?>" title="<?php echo $is_bm?'Lesezeichen entfernen':'Lesezeichen hinzufügen'; ?>">
<i class="fa<?php echo $is_bm?'s':'r'; ?> fa-bookmark"></i>
</button>
<?php endif; ?>
<span><i class="fas fa-comment-dots"></i> <?php echo (int)$thread->reply_count; ?> Antworten</span>
<span><i class="fas fa-eye"></i> <?php echo (int)$thread->views; ?> Views</span>
</div>
<?php $thread_tags = WBF_DB::get_thread_tags($id); echo self::render_tags($thread_tags); ?>
</div>
</div>
<div class="wbf-posts" id="wbfPosts">
<!-- Poll (optional) -->
<?php
$poll = WBF_DB::get_poll($id);
$can_add_poll = $current && (int)$current->id === (int)$thread->user_id && !$poll && $thread->status === 'open';
if ($can_add_poll): ?>
<div style="margin-bottom:1rem">
<button class="wbf-btn wbf-btn--sm wbf-btn--outline" id="wbfOpenPollModal">
<i class="fas fa-chart-bar"></i> Umfrage hinzufügen
</button>
</div>
<?php endif;
if ($poll):
$results = WBF_DB::get_poll_results($poll->id);
$my_votes = $current ? WBF_DB::get_user_votes($poll->id, $current->id) : [];
$total = array_sum($results);
$voted = !empty($my_votes);
$expired = $poll->ends_at && strtotime($poll->ends_at) < time();
$show_results = $voted || $expired || !$current;
?>
<div class="wbf-poll" id="wbfPoll-<?php echo (int)$poll->id; ?>" data-poll-id="<?php echo (int)$poll->id; ?>" data-multi="<?php echo (int)$poll->multi; ?>">
<div class="wbf-poll__header">
<i class="fas fa-chart-bar"></i>
<span class="wbf-poll__title"><?php echo esc_html($poll->question); ?></span>
<?php if ($poll->multi): ?><span class="wbf-poll__badge">Mehrfachauswahl</span><?php endif; ?>
<?php if ($expired): ?><span class="wbf-poll__badge wbf-poll__badge--ended">Beendet</span><?php endif; ?>
</div>
<div class="wbf-poll__body">
<?php if ($show_results): ?>
<!-- Ergebnisse -->
<?php foreach ($poll->options as $i => $opt):
$votes = $results[$i] ?? 0;
$pct = $total > 0 ? round($votes / $total * 100) : 0;
$mine = in_array($i, $my_votes);
?>
<div class="wbf-poll__result<?php echo $mine?' wbf-poll__result--mine':''; ?>">
<div class="wbf-poll__result-bar" style="width:<?php echo $pct; ?>%"></div>
<div class="wbf-poll__result-content">
<span class="wbf-poll__result-label"><?php if($mine): ?><i class="fas fa-check-circle" style="color:var(--c-primary)"></i> <?php endif; ?><?php echo esc_html($opt); ?></span>
<span class="wbf-poll__result-pct"><?php echo $pct; ?>% <span style="color:var(--c-muted);font-size:.75em">(<?php echo $votes; ?>)</span></span>
</div>
</div>
<?php endforeach; ?>
<div class="wbf-poll__footer">
<i class="fas fa-users"></i> <?php echo $total; ?> Stimme<?php echo $total!=1?'n':''; ?>
<?php if ($poll->ends_at && !$expired): ?>
· <i class="fas fa-clock"></i> Endet <?php echo esc_html(date_i18n('d.m.Y \u\m H:i \U\h\r', strtotime($poll->ends_at))); ?>
<?php elseif ($expired): ?>
· <i class="fas fa-flag-checkered"></i> Abgestimmt
<?php endif; ?>
</div>
<?php else: ?>
<!-- Abstimmung -->
<form class="wbf-poll__form" data-poll-id="<?php echo (int)$poll->id; ?>">
<?php foreach ($poll->options as $i => $opt): ?>
<label class="wbf-poll__option">
<input type="<?php echo $poll->multi?'checkbox':'radio'; ?>"
name="wbf_poll_option" value="<?php echo $i; ?>"
style="accent-color:var(--c-primary)">
<?php echo esc_html($opt); ?>
</label>
<?php endforeach; ?>
<div style="display:flex;align-items:center;gap:.75rem;margin-top:.75rem">
<button type="submit" class="wbf-btn wbf-btn--sm wbf-btn--primary">
<i class="fas fa-vote-yea"></i> Abstimmen
</button>
<span class="wbf-poll__msg wbf-msg"></span>
</div>
</form>
<?php if ($poll->ends_at): ?>
<div class="wbf-poll__footer">
<i class="fas fa-clock"></i> Endet <?php echo esc_html(date_i18n('d.m.Y \u\m H:i \U\h\r', strtotime($poll->ends_at))); ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php
$op_reported = $current ? WBF_DB::has_reported($current->id, $id, 'thread') : false;
$op_can_edit = $current && ((int)$current->id === (int)$thread->user_id || WBF_DB::can($current,'delete_post'));
?>
<div class="wbf-post wbf-post--op">
<div class="wbf-post__sidebar">
<?php $op_online = WBF_DB::is_online((int)$thread->user_id); echo self::avatar($thread->avatar_url,$thread->display_name,52,$op_online); ?>
<strong><?php echo esc_html($thread->display_name); ?></strong>
<?php echo self::role_badge($thread->author_role); ?>
<?php echo WBF_Levels::badge((int)$thread->author_posts); ?>
<span class="wbf-post__meta-small"><?php echo (int)$thread->author_posts; ?> Beiträge</span>
<span class="wbf-post__meta-small"><?php echo date_i18n('d.m.Y', strtotime($thread->author_registered)); ?></span>
</div>
<div class="wbf-post__body">
<div class="wbf-post__content" id="wbf-thread-content-<?php echo (int)$id; ?>"><?php echo WBF_BBCode::render($thread->content); ?></div>
<div id="wbf-thread-edit-<?php echo (int)$id; ?>" style="display:none;margin-top:.75rem">
<input type="text" class="wbf-edit-title-input" value="<?php echo esc_attr($thread->title); ?>"
style="width:100%;padding:.6rem;border:1px solid var(--c-border,rgba(255,255,255,.14));border-radius:6px;font-size:.95rem;font-weight:700;margin-bottom:.5rem;box-sizing:border-box;background:var(--c-bg2,#161a22);color:var(--c-text,#e8eaf0)">
<textarea class="wbf-edit-textarea" rows="7"
style="width:100%;padding:.6rem;border:1px solid var(--c-border,#e2e8f0);border-radius:6px;font-size:.9rem;resize:vertical;box-sizing:border-box"><?php echo esc_textarea($thread->content); ?></textarea>
<div style="display:flex;gap:.6rem;margin-top:.5rem;align-items:center">
<button class="wbf-btn wbf-btn--primary wbf-btn--sm wbf-save-thread-btn" data-id="<?php echo (int)$id; ?>">
<i class="fas fa-save"></i> Speichern
</button>
<button class="wbf-btn wbf-btn--sm wbf-cancel-thread-btn" data-id="<?php echo (int)$id; ?>">Abbrechen</button>
<span class="wbf-edit-msg" style="font-size:.85rem"></span>
</div>
</div>
<?php if (!empty($thread->signature)): ?>
<div class="wbf-signature"><div class="wbf-signature__divider"></div><?php echo WBF_BBCode::render($thread->signature); ?></div>
<?php endif; ?>
<div class="wbf-post__footer">
<span class="wbf-post__date"><?php echo self::time_ago($thread->created_at); ?></span>
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap">
<?php if ($current): ?>
<?php $is_subbed = WBF_DB::is_subscribed($current->id, $id); ?>
<button class="wbf-subscribe-btn wbf-btn wbf-btn--sm<?php echo $is_subbed?' wbf-btn--primary':''; ?>"
data-thread="<?php echo (int)$id; ?>"
title="<?php echo $is_subbed?'Abonnement entfernen':'Thread abonnieren'; ?>">
<i class="fas fa-bell<?php echo $is_subbed?'':'-slash'; ?>"></i>
<?php echo $is_subbed?'Abonniert':'Abonnieren'; ?>
</button>
<?php endif; ?>
<?php if ($current && WBF_DB::can($current,'post') && $thread->status !== 'closed'): ?>
<button class="wbf-quote-btn"
data-source="wbf-thread-content-<?php echo (int)$id; ?>"
data-author="<?php echo esc_attr($thread->display_name); ?>"
title="Zitieren">
<i class="fas fa-quote-left"></i> Zitieren
</button>
<?php endif; ?>
<?php if ($current && !$op_reported): ?>
<button class="wbf-report-btn" data-id="<?php echo (int)$id; ?>" data-type="thread" title="Thread melden">
<i class="fas fa-flag"></i>
</button>
<?php elseif ($op_reported): ?>
<span class="wbf-reported-label" title="Bereits gemeldet"><i class="fas fa-flag"></i></span>
<?php endif; ?>
<?php if ($op_can_edit): ?>
<button class="wbf-edit-thread-btn"
data-id="<?php echo (int)$id; ?>"
style="display:inline-flex;align-items:center;gap:.3rem;background:none;border:1.5px solid var(--c-border,#e2e8f0);cursor:pointer;color:var(--c-muted,#94a3b8);padding:3px 8px;border-radius:4px;font-size:.82rem"
title="Thread bearbeiten">
<i class="fas fa-pen"></i> Bearbeiten
</button>
<?php endif; ?>
<?php
// Ignore-Button: nur wenn der Thread-Autor nicht der eingeloggte User ist
// und die Rolle blockiert werden darf (konfigurierbar in Einstellungen)
$op_author = WBF_DB::get_user((int)$thread->user_id);
if ($current && (int)$current->id !== (int)$thread->user_id && wbf_can_be_ignored($op_author)):
$op_is_ignored = WBF_DB::is_ignored($current->id, (int)$thread->user_id);
?>
<button class="wbf-ignore-btn"
data-id="<?php echo (int)$thread->user_id; ?>"
data-name="<?php echo esc_attr($thread->display_name); ?>"
data-ignored="<?php echo $op_is_ignored ? '1' : '0'; ?>"
title="<?php echo $op_is_ignored ? 'Ignorierung aufheben' : 'Nutzer ignorieren'; ?>"
style="background:none;border:none;cursor:pointer;color:var(--c-muted,#94a3b8);padding:2px 6px;border-radius:4px;font-size:.82rem">
<i class="fas fa-<?php echo $op_is_ignored ? 'eye' : 'eye-slash'; ?>"></i>
<?php echo $op_is_ignored ? 'Entblocken' : 'Ignorieren'; ?>
</button>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php foreach ($posts as $post):
$p_liked = $current ? WBF_DB::has_liked($current->id,$post->id,'post') : false;
self::render_single_post($post,$current,$p_liked);
endforeach; ?>
</div>
<?php if ($pages>1): ?>
<div class="wbf-pagination">
<?php for ($i=1;$i<=$pages;$i++): ?>
<a href="?forum_thread=<?php echo $id; ?>&tp=<?php echo $i; ?>" class="wbf-page-btn<?php echo ($i==$page)?' active':''; ?>"><?php echo $i; ?></a>
<?php endfor; ?>
</div>
<?php endif; ?>
<?php if ($thread->status==='open'): ?>
<div class="wbf-reply-box" id="wbfReplyBox">
<h3 class="wbf-reply-box__title"><i class="fas fa-reply"></i> Antworten</h3>
<?php if (WBF_DB::can($current,'post')): ?>
<div class="wbf-reply-form">
<div class="wbf-reply-form__avatar"><?php echo self::avatar($current->avatar_url,$current->display_name,44); ?></div>
<div class="wbf-reply-form__input">
<?php self::render_editor_toolbar('wbfReplyContent'); ?>
<textarea id="wbfReplyContent" placeholder="Deine Antwort…" rows="5"></textarea>
<div class="wbf-reply-form__actions">
<span class="wbf-reply-counter" id="wbfReplyCounter">0 Zeichen</span>
<button class="wbf-btn wbf-btn--primary" id="wbfSubmitReply" data-thread="<?php echo $id; ?>"><i class="fas fa-paper-plane"></i> Senden</button>
</div>
</div>
</div>
<?php else: ?>
<div class="wbf-notice wbf-notice--info"><i class="fas fa-info-circle"></i>
<a href="#" class="wbf-login-link">Einloggen</a> oder <a href="#" class="wbf-register-link">Registrieren</a> um zu antworten.
</div>
<?php endif; ?>
</div>
<?php else: ?>
<div class="wbf-notice wbf-notice--warning"><i class="fas fa-lock"></i> Dieser Thread ist geschlossen.</div>
<?php endif; ?>
<?php self::render_forum_footer(); ?>
</div>
<?php self::render_auth_modal(); ?>
<?php self::render_report_modal(); ?>
<?php if (WBF_DB::can($current,'manage_cats')): self::render_move_modal(WBF_DB::get_categories_flat(), $id); endif; ?>
<?php if ($can_add_poll): self::render_poll_modal($id); endif; ?>
</div>
<?php return ob_get_clean();
}
public static function render_single_post( $post, $current = null, $liked = false ) {
$already_reported = $current ? WBF_DB::has_reported($current->id, $post->id, 'post') : false;
?>
<div class="wbf-post" id="post-<?php echo (int)$post->id; ?>"
data-created="<?php echo esc_attr($post->created_at); ?>">
<div class="wbf-post__sidebar">
<?php $p_online = WBF_DB::is_online((int)$post->user_id); echo self::avatar($post->avatar_url,$post->display_name,52,$p_online); ?>
<strong><?php echo esc_html($post->display_name); ?></strong>
<?php echo self::role_badge($post->author_role); ?>
<?php echo WBF_Levels::badge((int)$post->author_posts); ?>
<span class="wbf-post__meta-small"><?php echo (int)$post->author_posts; ?> Beiträge</span>
<span class="wbf-post__meta-small"><?php echo date_i18n('d.m.Y', strtotime($post->author_registered)); ?></span>
</div>
<div class="wbf-post__body">
<div class="wbf-post__new-label"><i class="fas fa-circle-dot"></i> Neu</div>
<div class="wbf-post__content" id="wbf-post-content-<?php echo (int)$post->id; ?>"><?php echo WBF_BBCode::render($post->content); ?></div>
<div class="wbf-post__edit-wrap" id="wbf-post-edit-<?php echo (int)$post->id; ?>" style="display:none;margin-top:.75rem">
<textarea class="wbf-edit-textarea" rows="6"
style="width:100%;padding:.6rem;border:1px solid var(--c-border,#e2e8f0);border-radius:6px;font-size:.9rem;resize:vertical;box-sizing:border-box"
><?php echo esc_textarea($post->content); ?></textarea>
<div style="display:flex;gap:.6rem;margin-top:.5rem;align-items:center">
<button class="wbf-btn wbf-btn--primary wbf-btn--sm wbf-save-edit-btn"
data-id="<?php echo (int)$post->id; ?>">
<i class="fas fa-save"></i> Speichern
</button>
<button class="wbf-btn wbf-btn--sm wbf-cancel-edit-btn" data-id="<?php echo (int)$post->id; ?>">Abbrechen</button>
<span class="wbf-edit-msg" style="font-size:.85rem"></span>
</div>
</div>
<?php if (!empty($post->signature)): ?>
<div class="wbf-signature"><div class="wbf-signature__divider"></div><?php echo WBF_BBCode::render($post->signature); ?></div>
<?php endif; ?>
<div class="wbf-post__footer">
<span class="wbf-post__date"><?php echo self::time_ago($post->created_at); ?></span>
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap">
<?php echo self::reaction_bar($post->id, 'post', $current); ?>
<?php if ($current && WBF_DB::can($current,'post')): ?>
<button class="wbf-quote-btn"
data-source="wbf-post-content-<?php echo (int)$post->id; ?>"
data-author="<?php echo esc_attr($post->display_name); ?>"
title="Zitieren">
<i class="fas fa-quote-left"></i> Zitieren
</button>
<?php endif; ?>
<?php if ($current && !$already_reported): ?>
<button class="wbf-report-btn" data-id="<?php echo (int)$post->id; ?>" data-type="post" title="Beitrag melden">
<i class="fas fa-flag"></i>
</button>
<?php elseif ($already_reported): ?>
<span class="wbf-reported-label" title="Bereits gemeldet"><i class="fas fa-flag"></i></span>
<?php endif; ?>
<?php if ($current && ((int)$current->id === (int)$post->user_id || WBF_DB::can($current,'delete_post'))): ?>
<button class="wbf-edit-post-btn"
data-id="<?php echo (int)$post->id; ?>"
style="background:none;border:none;cursor:pointer;color:var(--c-muted,#94a3b8);padding:2px 6px;border-radius:4px;font-size:.82rem"
title="Beitrag bearbeiten">
<i class="fas fa-pen"></i> Bearbeiten
</button>
<?php endif; ?>
<?php
// Ignore-Button im Post-Footer
$post_author = WBF_DB::get_user((int)$post->user_id);
if ($current && (int)$current->id !== (int)$post->user_id && wbf_can_be_ignored($post_author)):
$post_is_ignored = WBF_DB::is_ignored($current->id, (int)$post->user_id);
?>
<button class="wbf-ignore-btn"
data-id="<?php echo (int)$post->user_id; ?>"
data-name="<?php echo esc_attr($post->display_name); ?>"
data-ignored="<?php echo $post_is_ignored ? '1' : '0'; ?>"
title="<?php echo $post_is_ignored ? 'Ignorierung aufheben' : 'Nutzer ignorieren'; ?>"
style="background:none;border:none;cursor:pointer;color:var(--c-muted,#94a3b8);padding:2px 6px;border-radius:4px;font-size:.82rem">
<i class="fas fa-<?php echo $post_is_ignored ? 'eye' : 'eye-slash'; ?>"></i>
<?php echo $post_is_ignored ? 'Entblocken' : 'Ignorieren'; ?>
</button>
<?php endif; ?>
<?php echo self::mod_tools_post($post->id,$current); ?>
</div>
</div>
</div>
</div>
<?php }
// ── PROFILE ───────────────────────────────────────────────────────────────
private static function view_profile() {
$profile_id = (int)($_GET['forum_profile'] ?? 0);
$current = WBF_Auth::get_current_user();
$profile = $profile_id ? WBF_DB::get_user($profile_id) : $current;
if (!$profile) return '<p class="wbf-notice">Profil nicht gefunden.</p>';
$is_own = $current && $current->id == $profile->id;
$is_staff = $current && WBF_Roles::level($current->role) >= 50;
// Profil-Sichtbarkeit prüfen
// profile_public NULL = Spalte fehlt noch = als öffentlich (1) behandeln
$profile_public = isset($profile->profile_public) ? (int)$profile->profile_public : 1;
if (!$is_own && !$is_staff && $profile_public === 0) {
ob_start(); ?>
<div class="wbf-wrap"><?php self::render_topbar($current); ?>
<div class="wbf-container wbf-mt">
<div class="wbf-notice wbf-notice--warning">
<i class="fas fa-user-lock"></i> Dieses Profil ist nicht öffentlich.
</div>
</div></div>
<?php return ob_get_clean();
}
$user_posts = WBF_DB::get_user_posts( $profile->id, 50 );
$bookmarks = $is_own ? WBF_DB::get_user_bookmarks($current->id, 50) : [];
$ignore_list = $is_own ? WBF_DB::get_ignore_list($current->id) : [];
$cf_defs = WBF_DB::get_profile_field_defs();
$cf_cats = WBF_DB::get_profile_field_categories();
$cf_cat_map = array_column( $cf_cats, null, 'id' );
$cf_vals = WBF_DB::get_user_meta( $profile->id );
// 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);
$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]) ) {
$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;
ob_start(); ?>
<div class="wbf-wrap">
<?php self::render_topbar($current); ?>
<div class="wbf-container wbf-mt">
<nav class="wbf-breadcrumb">
<a href="<?php echo esc_url(remove_query_arg(['forum_profile', 'ptab'])); ?>"><i class="fas fa-home"></i> Forum</a>
<span>/</span><span>Profil</span>
</nav>
<div class="wbf-profile-layout">
<!-- ── SIDEBAR ─────────────────────────────────────────── -->
<aside class="wbf-profile-sidebar">
<div class="wbf-profile-sidebar__avatar-wrap">
<img src="<?php echo esc_url($profile->avatar_url); ?>"
alt="<?php echo esc_attr($profile->display_name); ?>"
id="wbfProfileAvatar"
class="wbf-profile-sidebar__avatar">
<?php if ($is_own): ?>
<label class="wbf-avatar-upload-btn" title="Avatar ändern">
<i class="fas fa-camera"></i>
<input type="file" id="wbfAvatarFile" accept="image/*" style="display:none">
</label>
<?php endif; ?>
</div>
<div class="wbf-profile-sidebar__identity">
<h2><?php echo esc_html($profile->display_name); ?></h2>
<?php echo self::role_badge($profile->role); ?>
<span class="wbf-profile-sidebar__username">@<?php echo esc_html($profile->username); ?></span>
<?php
$profile_online = WBF_DB::is_online($profile->id, 15);
if ($profile_online): ?>
<span class="wbf-profile-online-badge">
<span class="wbf-profile-online-dot"></span> Online
</span>
<?php else:
$last = $profile->last_active ?? null;
if ($last && $last !== '0000-00-00 00:00:00'): ?>
<span class="wbf-profile-lastseen">
<i class="fas fa-clock"></i> Zuletzt aktiv: <?php echo self::time_ago($last); ?>
</span>
<?php endif; endif; ?>
</div>
<div class="wbf-profile-sidebar__stats">
<div class="wbf-profile-sidebar__stat">
<span><?php echo (int)$profile->post_count; ?></span>
<em>Beiträge</em>
</div>
<div class="wbf-profile-sidebar__stat">
<span><?php echo date_i18n('m/Y', strtotime($profile->registered)); ?></span>
<em>Dabei seit</em>
</div>
</div>
<?php $level_bar = WBF_Levels::progress_bar((int)$profile->post_count); if ($level_bar): ?>
<div class="wbf-profile-sidebar__section"><?php echo $level_bar; ?></div>
<?php endif; ?>
<?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>
<?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>
<?php endif; ?>
<!-- Öffentliche Custom Fields — nach Kategorie gruppiert -->
<?php
$cf_by_cat_sb = [];
foreach ( $cf_defs as $def_sb ) {
if (!$is_own && empty($def_sb['public'])) continue;
$val_sb = trim($cf_vals[$def_sb['key']] ?? '');
if ($val_sb === '') continue;
$cid_sb = $def_sb['category_id'] ?? '';
if (!$cid_sb || !isset($cf_cat_map[$cid_sb])) $cid_sb = '__none__';
$cf_by_cat_sb[$cid_sb][] = ['def'=>$def_sb,'val'=>$val_sb];
}
$sb_sections = $cf_cats;
if (isset($cf_by_cat_sb['__none__'])) {
$sb_sections[] = ['id'=>'__none__','name'=>'Weitere Infos','icon'=>''];
}
foreach ($sb_sections as $scat_sb):
$scid_sb = $scat_sb['id'];
if (empty($cf_by_cat_sb[$scid_sb])) continue;
?>
<div class="wbf-profile-sidebar__section">
<span class="wbf-profile-sidebar__section-label" style="display:flex;align-items:center;gap:5px;margin-bottom:4px">
<?php if(!empty($scat_sb['icon'])): ?>
<span style="font-size:.9rem"><?php echo esc_html($scat_sb['icon']); ?></span>
<?php endif; ?>
<?php echo esc_html($scat_sb['name']); ?>
</span>
<?php foreach ($cf_by_cat_sb[$scid_sb] as $cf_entry_sb):
$def_sb = $cf_entry_sb['def'];
$val_sb = $cf_entry_sb['val'];
// Auto-Link für Telegram und Discord anhand des Feld-Keys erkennen
$key_sb = strtolower($def_sb['key']);
$is_telegram = strpos($key_sb, 'telegram') !== false;
$is_discord = strpos($key_sb, 'discord') !== false;
?>
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:6px;margin-bottom:4px;font-size:.85rem">
<span style="color:var(--c-muted,#94a3b8);flex-shrink:0"><?php echo esc_html($def_sb['label']); ?></span>
<?php if ($def_sb['type'] === 'url'): ?>
<a href="<?php echo esc_url($val_sb); ?>" target="_blank" rel="noopener noreferrer"
style="color:var(--c-primary);word-break:break-all;text-align:right">
<?php echo esc_html(mb_strtolower(preg_replace('#^https?://#i','',$val_sb))); ?>
</a>
<?php elseif ($is_telegram):
// Username bereinigen: @ und Leerzeichen entfernen
$tg_user = ltrim(trim($val_sb), '@');
$tg_url = 'https://t.me/' . rawurlencode($tg_user);
?>
<a href="<?php echo esc_url($tg_url); ?>" target="_blank" rel="noopener noreferrer"
style="color:#29b6f6;text-align:right;font-size:inherit">
@<?php echo esc_html($tg_user); ?>
</a>
<?php elseif ($is_discord): ?>
<span style="color:#7289da;text-align:right;font-size:inherit">
<?php echo esc_html($val_sb); ?>
</span>
<?php elseif ($def_sb['type'] === 'textarea'): ?>
<span style="text-align:right"><?php echo nl2br(esc_html($val_sb)); ?></span>
<?php elseif ($def_sb['type'] === 'date'):
$age_sb = self::calc_age($val_sb); ?>
<span><?php echo $age_sb !== null ? esc_html((string)$age_sb) . ' Jahre' : '—'; ?></span>
<?php else: ?>
<span style="text-align:right"><?php echo esc_html($val_sb); ?></span>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</aside>
<!-- ── MAIN ────────────────────────────────────────────── -->
<div class="wbf-profile-main">
<!-- DM-Button + Ignorieren-Button (nur auf fremden Profilen) -->
<?php if ($current && !$is_own && WBF_Roles::level($profile->role) >= 0): ?>
<div style="display:flex;justify-content:flex-end;gap:.5rem;margin-bottom:.75rem;flex-wrap:wrap">
<a href="?forum_dm=inbox&with=<?php echo (int)$profile->id; ?>"
class="wbf-btn wbf-btn--sm wbf-btn--primary">
<i class="fas fa-envelope"></i> Nachricht senden
</a>
<?php if ( wbf_can_be_ignored($profile) ):
$viewer_ignores = WBF_DB::is_ignored($current->id, $profile->id); ?>
<button class="wbf-ignore-btn wbf-btn wbf-btn--sm<?php echo $viewer_ignores?' wbf-btn--primary':''; ?>"
data-id="<?php echo (int)$profile->id; ?>"
data-name="<?php echo esc_attr($profile->display_name); ?>"
data-ignored="<?php echo $viewer_ignores?'1':'0'; ?>">
<i class="fas fa-<?php echo $viewer_ignores?'eye':'eye-slash'; ?>"></i>
<?php echo $viewer_ignores?'Ignorierung aufheben':'Nutzer ignorieren'; ?>
</button>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- ── TAB-NAVIGATION ─────────────────────────────── -->
<?php if ($is_own): ?>
<div class="wbf-profile-tabs">
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=1"
class="wbf-profile-tab<?php echo $active_tab===1?' active':''; ?>">
<i class="fas fa-sliders"></i> Profil
</a>
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=2"
class="wbf-profile-tab<?php echo $active_tab===2?' active':''; ?>">
<i class="fas fa-comments"></i> Aktivität
</a>
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=3"
class="wbf-profile-tab<?php echo $active_tab===3?' active':''; ?>">
<i class="fas fa-shield-halved"></i> Privatsphäre
</a>
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=4"
class="wbf-profile-tab<?php echo $active_tab===4?' active':''; ?>">
<i class="fas fa-lock"></i> Sicherheit
</a>
<?php
// „Verbindungen" Tab — immer sichtbar (Discord eingebaut, MC optional)
$wbf_has_connections = true;
if ( $wbf_has_connections ) : ?>
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=mc"
class="wbf-profile-tab<?php echo $active_tab==='mc'?' active':''; ?>">
<i class="fas fa-plug"></i> Verbindungen
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- ══════════════════════════════════════════════════
TAB 1 — Profil bearbeiten + Weitere Profilangaben
══════════════════════════════════════════════════ -->
<?php if ($is_own && $active_tab === 1): ?>
<!-- Profil bearbeiten -->
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-sliders"></i> Profil bearbeiten
</div>
<div class="wbf-profile-card__body">
<div class="wbf-form-row">
<label>Anzeigename</label>
<input type="text" id="wbfEditName" value="<?php echo esc_attr($profile->display_name); ?>">
</div>
<div class="wbf-form-row">
<label>Bio</label>
<?php self::render_editor_toolbar('wbfEditBio'); ?>
<textarea id="wbfEditBio" rows="2"><?php echo esc_textarea($profile->bio); ?></textarea>
</div>
<div class="wbf-form-row">
<label>Signatur <small>(max. 300 Zeichen)</small></label>
<?php self::render_editor_toolbar('wbfEditSignature'); ?>
<textarea id="wbfEditSignature" rows="2" maxlength="300" placeholder="Deine Signatur…"><?php echo esc_textarea($profile->signature ?? ''); ?></textarea>
<div class="wbf-sig-counter"><span id="wbfSigCount"><?php echo mb_strlen($profile->signature??''); ?></span>/300</div>
</div>
</div>
</div>
<!-- Weitere Profilangaben — nach Kategorie gruppiert (ohne eigene Speichern-Buttons) -->
<?php
$cf_edit_by_cat = [];
foreach ( $cf_defs as $def_e ) {
$cid_e = $def_e['category_id'] ?? '';
if (!$cid_e || !isset($cf_cat_map[$cid_e])) $cid_e = '__none__';
$cf_edit_by_cat[$cid_e][] = $def_e;
}
$edit_sections = $cf_cats;
if (isset($cf_edit_by_cat['__none__'])) {
$edit_sections[] = ['id'=>'__none__','name'=>'Weitere Angaben','icon'=>'📋'];
}
if (!empty($cf_defs)):
foreach ($edit_sections as $ecat):
$ecid = $ecat['id'];
if (empty($cf_edit_by_cat[$ecid])) continue;
?>
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<?php if(!empty($ecat['icon'])): ?>
<span style="margin-right:5px"><?php echo esc_html($ecat['icon']); ?></span>
<?php else: ?><i class="fas fa-sliders"></i><?php endif; ?>
<?php echo esc_html($ecat['name']); ?>
</div>
<div class="wbf-profile-card__body">
<div class="wbf-profile-edit-grid">
<?php foreach ($cf_edit_by_cat[$ecid] as $def):
$k = esc_attr($def['key']);
$lbl = esc_html($def['label']);
$ph = esc_attr($def['placeholder'] ?? '');
$val = esc_attr($cf_vals[$def['key']] ?? '');
$req = !empty($def['required']) ? 'required' : '';
?>
<div class="wbf-form-row">
<label><?php echo $lbl; ?><?php if($req): ?> <span style="color:var(--c-danger)">*</span><?php endif; ?></label>
<?php if ($def['type'] === 'textarea'): ?>
<textarea class="wbf-cf-input" data-field="cf_<?php echo $k; ?>"
rows="2" placeholder="<?php echo $ph; ?>"
<?php echo $req; ?>><?php echo esc_textarea($cf_vals[$def['key']] ?? ''); ?></textarea>
<?php elseif ($def['type'] === 'select'):
$opts = array_filter(array_map('trim', explode("\n", $def['options'] ?? '')));
?>
<select class="wbf-cf-input" data-field="cf_<?php echo $k; ?>">
<option value="">— Bitte wählen —</option>
<?php foreach ($opts as $opt): ?>
<option value="<?php echo esc_attr($opt); ?>"
<?php selected($cf_vals[$def['key']] ?? '', $opt); ?>><?php echo esc_html($opt); ?></option>
<?php endforeach; ?>
</select>
<?php elseif ($def['type'] === 'date'): ?>
<input type="date"
class="wbf-cf-input"
data-field="cf_<?php echo $k; ?>"
value="<?php echo esc_attr($cf_vals[$def['key']] ?? ''); ?>"
max="<?php echo date('Y-m-d'); ?>"
<?php echo $req; ?>>
<?php else: ?>
<input type="<?php echo $def['type']==='url'?'url':($def['type']==='number'?'number':'text'); ?>"
class="wbf-cf-input"
data-field="cf_<?php echo $k; ?>"
value="<?php echo $val; ?>"
placeholder="<?php echo $ph; ?>"
<?php echo $req; ?>>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endforeach; endif; ?>
<!-- Globaler Speichern-Button für Tab 1 -->
<div style="display:flex;align-items:center;gap:1rem;padding:.25rem 0 .5rem">
<button class="wbf-btn wbf-btn--primary" id="wbfSaveProfile" style="min-width:160px">
<i class="fas fa-save"></i> Alles speichern
</button>
<span class="wbf-msg" id="wbfProfileMsg"></span>
</div>
<?php endif; /* end Tab 1 */ ?>
<!-- ══════════════════════════════════════════════════
TAB 2 — Lesezeichen + Beiträge
══════════════════════════════════════════════════ -->
<?php if ($active_tab === 2): ?>
<!-- Lesezeichen (nur eigenes Profil) -->
<?php if ($is_own): ?>
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-bookmark"></i> Lesezeichen
<span class="wbf-profile-card__count"><?php echo count($bookmarks); ?></span>
</div>
<div class="wbf-profile-card__body wbf-profile-card__body--posts">
<?php if (empty($bookmarks)): ?>
<p class="wbf-profile-empty">Noch keine Lesezeichen.</p>
<?php else: foreach ($bookmarks as $bm): ?>
<div class="wbf-profile-post-item">
<div class="wbf-profile-post-item__top">
<?php echo self::render_prefix($bm); ?>
<a href="?forum_thread=<?php echo (int)$bm->id; ?>" class="wbf-profile-post-item__title">
<?php echo esc_html(mb_substr($bm->title,0,60)); ?>
</a>
<span class="wbf-profile-post-item__cat"><i class="fas fa-folder"></i> <?php echo esc_html($bm->cat_name); ?></span>
<span class="wbf-profile-post-item__time"><i class="fas fa-bookmark" style="font-size:.65rem"></i> <?php echo self::time_ago($bm->bookmarked_at); ?></span>
</div>
</div>
<?php endforeach; endif; ?>
</div>
</div>
<?php endif; ?>
<!-- Beiträge -->
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-comments"></i> Beiträge
<span class="wbf-profile-card__count"><?php echo count($user_posts); ?></span>
</div>
<div class="wbf-profile-card__body wbf-profile-card__body--posts">
<?php if (empty($user_posts)): ?>
<p class="wbf-profile-empty">Noch keine Beiträge.</p>
<?php else:
foreach ($user_posts as $up):
$preview = esc_html(mb_substr(strip_tags($up->content), 0, 130));
$more = mb_strlen(strip_tags($up->content)) > 130 ? '…' : '';
$is_thread = isset($up->entry_type) && $up->entry_type === 'thread';
$anchor = $is_thread
? '?forum_thread=' . (int)$up->thread_id
: '?forum_thread=' . (int)$up->thread_id . '#post-' . (int)$up->id;
?>
<div class="wbf-profile-post-item">
<div class="wbf-profile-post-item__top">
<?php if ($is_thread): ?>
<span class="wbf-profile-post-item__type wbf-profile-post-item__type--thread">
<i class="fas fa-layer-group"></i> Thread
</span>
<?php else: ?>
<span class="wbf-profile-post-item__type wbf-profile-post-item__type--reply">
<i class="fas fa-reply"></i> Antwort
</span>
<?php endif; ?>
<a href="<?php echo esc_url($anchor); ?>" class="wbf-profile-post-item__title">
<?php echo esc_html(mb_substr($up->thread_title, 0, 60)); ?>
</a>
<span class="wbf-profile-post-item__cat">
<i class="fas fa-folder"></i> <?php echo esc_html($up->cat_name); ?>
</span>
<span class="wbf-profile-post-item__time"><?php echo self::time_ago($up->created_at); ?></span>
</div>
<?php if ($preview): ?>
<p class="wbf-profile-post-item__preview"><?php echo $preview . $more; ?></p>
<?php endif; ?>
</div>
<?php endforeach; endif; ?>
</div>
</div>
<?php endif; /* end Tab 2 */ ?>
<!-- ══════════════════════════════════════════════════
TAB 3 — Ignorierte Nutzer + Datenschutz
══════════════════════════════════════════════════ -->
<?php if ($is_own && $active_tab === 3): ?>
<!-- Profil-Sichtbarkeit -->
<?php $pub = (int)($profile->profile_public ?? 1); ?>
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-eye"></i> Profil-Sichtbarkeit
</div>
<div class="wbf-profile-card__body">
<div class="wbf-form-row" style="display:flex;align-items:center;gap:1rem">
<div>
<div style="font-size:.9rem;font-weight:600;margin-bottom:3px">Profil öffentlich sichtbar</div>
<div style="font-size:.8rem;color:var(--c-muted)">Wenn deaktiviert, können nur du selbst und Moderatoren dein Profil sehen.</div>
</div>
<button type="button" id="wbfToggleProfileVis"
class="wbf-btn wbf-btn--sm<?php echo $pub?' wbf-btn--primary':''; ?>"
data-state="<?php echo $pub; ?>"
style="margin-left:auto;white-space:nowrap">
<i class="fas fa-<?php echo $pub?'eye':'eye-slash'; ?>"></i>
<?php echo $pub?'Öffentlich':'Privat'; ?>
</button>
</div>
</div>
</div>
<!-- Ignorierte Nutzer -->
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-bell"></i> E-Mail-Benachrichtigungen
</div>
<div class="wbf-profile-card__body">
<?php
$notif_meta = WBF_DB::get_user_meta($current->id);
$n_reply = ($notif_meta['notify_reply'] ?? '1') !== '0';
$n_mention = ($notif_meta['notify_mention'] ?? '1') !== '0';
$n_message = ($notif_meta['notify_message'] ?? '1') !== '0';
?>
<p style="font-size:.82rem;color:var(--c-muted);margin-bottom:1rem">
Lege fest bei welchen Ereignissen du eine E-Mail erhältst.
</p>
<div class="wbf-notif-pref-list">
<label class="wbf-notif-pref">
<div class="wbf-notif-pref__info">
<span><i class="fas fa-reply"></i> Antworten auf meine Threads</span>
<small>Wenn jemand in einem deiner Threads antwortet</small>
</div>
<div class="wbf-toggle<?php echo $n_reply?' wbf-toggle--on':''; ?>"
id="wbfNotifReply" data-key="notify_reply" data-state="<?php echo $n_reply?'1':'0'; ?>">
<div class="wbf-toggle__knob"></div>
</div>
</label>
<label class="wbf-notif-pref">
<div class="wbf-notif-pref__info">
<span><i class="fas fa-at"></i> @Erwähnungen</span>
<small>Wenn dich jemand in einem Beitrag erwähnt</small>
</div>
<div class="wbf-toggle<?php echo $n_mention?' wbf-toggle--on':''; ?>"
id="wbfNotifMention" data-key="notify_mention" data-state="<?php echo $n_mention?'1':'0'; ?>">
<div class="wbf-toggle__knob"></div>
</div>
</label>
<label class="wbf-notif-pref">
<div class="wbf-notif-pref__info">
<span><i class="fas fa-envelope"></i> Privatnachrichten</span>
<small>Wenn du eine neue Direktnachricht erhältst</small>
</div>
<div class="wbf-toggle<?php echo $n_message?' wbf-toggle--on':''; ?>"
id="wbfNotifMessage" data-key="notify_message" data-state="<?php echo $n_message?'1':'0'; ?>">
<div class="wbf-toggle__knob"></div>
</div>
</label>
</div>
<div class="wbf-profile-card__footer" style="margin-top:1rem">
<button class="wbf-btn wbf-btn--primary" id="wbfSaveNotifPrefs">
<i class="fas fa-save"></i> Einstellungen speichern
</button>
<span class="wbf-msg" id="wbfNotifPrefsMsg"></span>
</div>
</div>
</div>
<!-- Ignorierte Nutzer -->
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-eye-slash"></i> Ignorierte Nutzer
<span class="wbf-profile-card__count" id="wbfIgnoreCount"><?php echo count($ignore_list); ?></span>
</div>
<div class="wbf-profile-card__body" id="wbfIgnoreListWrap">
<?php if (empty($ignore_list)): ?>
<p class="wbf-profile-empty" id="wbfIgnoreEmpty">Du ignorierst niemanden.</p>
<?php else: ?>
<div class="wbf-ignore-list" id="wbfIgnoreList">
<?php foreach ($ignore_list as $ign): ?>
<div class="wbf-ignore-item" id="wbf-ignore-item-<?php echo (int)$ign->id; ?>">
<a href="?forum_profile=<?php echo (int)$ign->id; ?>" class="wbf-ignore-item__avatar">
<?php echo self::avatar($ign->avatar_url, $ign->display_name, 36); ?>
</a>
<div class="wbf-ignore-item__info">
<a href="?forum_profile=<?php echo (int)$ign->id; ?>" class="wbf-ignore-item__name">
<?php echo esc_html($ign->display_name); ?>
</a>
<span class="wbf-ignore-item__since">Ignoriert seit <?php echo self::time_ago($ign->ignored_since); ?></span>
</div>
<button class="wbf-ignore-btn wbf-btn wbf-btn--sm"
data-id="<?php echo (int)$ign->id; ?>"
data-name="<?php echo esc_attr($ign->display_name); ?>"
data-ignored="1"
style="margin-left:auto">
<i class="fas fa-eye"></i> Entblocken
</button>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<!-- Datenschutz & Konto löschen -->
<div class="wbf-profile-card" style="border-color:rgba(240,82,82,.25)">
<div class="wbf-profile-card__header" style="color:var(--c-danger);background:rgba(240,82,82,.06);border-bottom-color:rgba(240,82,82,.15)">
<i class="fas fa-shield-halved"></i> Datenschutz & Konto löschen
</div>
<div class="wbf-profile-card__body">
<p style="font-size:.85rem;color:var(--c-text-dim);margin-bottom:1rem;line-height:1.6">
Gemäß <strong>DSGVO Art. 17</strong> (Recht auf Vergessenwerden) kannst du die vollständige Löschung deines Kontos und aller personenbezogenen Daten beantragen.<br>
<span style="color:var(--c-muted);font-size:.8rem">Deine Beiträge bleiben anonymisiert sichtbar. Direktnachrichten, Likes, Profilinformationen und alle persönlichen Daten werden dauerhaft gelöscht.</span>
</p>
<div id="wbfGdprBox" style="background:rgba(240,82,82,.06);border:1px solid rgba(240,82,82,.2);border-radius:var(--radius-sm);padding:1.1rem;display:none">
<p style="font-size:.82rem;font-weight:700;color:var(--c-danger);margin-bottom:.9rem"><i class="fas fa-triangle-exclamation"></i> Diese Aktion ist unwiderruflich.</p>
<div class="wbf-form-row">
<label style="font-size:.72rem">Passwort zur Bestätigung</label>
<input type="password" id="wbfGdprPassword" placeholder="Dein aktuelles Passwort" autocomplete="current-password">
</div>
<label style="display:flex;align-items:center;gap:.6rem;font-size:.82rem;color:var(--c-text-dim);cursor:pointer;margin-bottom:1rem">
<input type="checkbox" id="wbfGdprConfirm" style="width:15px;height:15px;accent-color:var(--c-danger);cursor:pointer">
Ich verstehe, dass mein Konto und alle persönlichen Daten unwiderruflich gelöscht werden.
</label>
<div style="display:flex;gap:.75rem;align-items:center;flex-wrap:wrap">
<button class="wbf-btn wbf-btn--sm" id="wbfGdprCancel"
onclick="document.getElementById('wbfGdprBox').style.display='none';document.getElementById('wbfGdprToggle').style.display=''">
<i class="fas fa-xmark"></i> Abbrechen
</button>
<button class="wbf-btn wbf-btn--sm" id="wbfGdprSubmit"
style="background:rgba(240,82,82,.15);color:var(--c-danger);border-color:rgba(240,82,82,.4)">
<i class="fas fa-trash-can"></i> Konto endgültig löschen
</button>
<span class="wbf-msg" id="wbfGdprMsg"></span>
</div>
</div>
<button class="wbf-btn wbf-btn--sm" id="wbfGdprToggle"
style="background:rgba(240,82,82,.08);color:var(--c-danger);border-color:rgba(240,82,82,.3)"
onclick="document.getElementById('wbfGdprBox').style.display='';document.getElementById('wbfGdprToggle').style.display='none'">
<i class="fas fa-trash-can"></i> Konto löschen (DSGVO Art. 17)
</button>
</div>
</div>
<?php endif; /* end Tab 3 */ ?>
<!-- ══════════════════════════════════════════════════
TAB 4 — Sicherheit (Passwort & E-Mail)
══════════════════════════════════════════════════ -->
<?php if ($is_own && $active_tab === 4): ?>
<!-- Passwort ändern -->
<div class="wbf-profile-card" style="border-color:rgba(0,180,216,.25)">
<div class="wbf-profile-card__header" style="background:rgba(0,180,216,.07);border-bottom-color:rgba(0,180,216,.18)">
<i class="fas fa-lock" style="color:var(--c-primary)"></i> Passwort ändern
</div>
<div class="wbf-profile-card__body">
<div class="wbf-form-row">
<label>Aktuelles Passwort</label>
<input type="password" id="wbfCurrentPassword" placeholder="Dein aktuelles Passwort" autocomplete="current-password">
</div>
<div class="wbf-profile-edit-grid">
<div class="wbf-form-row">
<label>Neues Passwort <small>(min. 6 Zeichen)</small></label>
<input type="password" id="wbfNewPassword" placeholder="Neues Passwort" autocomplete="new-password">
</div>
<div class="wbf-form-row">
<label>Neues Passwort wiederholen</label>
<input type="password" id="wbfNewPassword2" placeholder="Passwort bestätigen" autocomplete="new-password">
</div>
</div>
<div class="wbf-profile-card__footer">
<button class="wbf-btn wbf-btn--primary" id="wbfSavePassword">
<i class="fas fa-key"></i> Passwort ändern
</button>
<span class="wbf-msg" id="wbfPasswordMsg"></span>
</div>
</div>
</div>
<!-- E-Mail-Adresse ändern -->
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-envelope"></i> E-Mail-Adresse
</div>
<div class="wbf-profile-card__body">
<p style="font-size:.82rem;color:var(--c-muted);margin-bottom:1rem">
Aktuelle Adresse: <strong style="color:var(--c-text)"><?php echo esc_html($profile->email); ?></strong>
</p>
<div class="wbf-profile-edit-grid">
<div class="wbf-form-row">
<label>Neue E-Mail-Adresse</label>
<input type="email" id="wbfNewEmail" placeholder="neue@email.de" autocomplete="off">
</div>
<div class="wbf-form-row">
<label>Aktuelles Passwort <small>(zur Bestätigung)</small></label>
<input type="password" id="wbfEmailPassword" placeholder="••••••" autocomplete="current-password">
</div>
</div>
<div class="wbf-profile-card__footer">
<button class="wbf-btn wbf-btn--primary" id="wbfSaveEmail">
<i class="fas fa-envelope"></i> E-Mail ändern
</button>
<span class="wbf-msg" id="wbfEmailMsg"></span>
</div>
</div>
</div>
<?php endif; /* end Tab 4 */ ?>
<!-- ══════════════════════════════════════════════════
TAB MC — Verbindungen (Externe Dienste verknüpfen)
Wird nur gerendert wenn mind. eine Integration aktiv ist.
Neue Integrationen: einfach weiteres .wbf-connection-card-Block
via apply_filters('wbf_profile_connections', ...) hinzufügen.
══════════════════════════════════════════════════ -->
<?php if ( $is_own && $active_tab === 'mc' ) : ?>
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-plug"></i> Verbundene Dienste
</div>
<div class="wbf-profile-card__body" style="padding:0">
<?php if ( class_exists('MC_Gallery_Forum_Bridge') ) :
$mc_content = apply_filters('wbf_profile_tab_content', '', 'minecraft', $profile);
?>
<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</span>
</div>
<div class="wbf-connection-card__content">
<?php echo $mc_content; ?>
</div>
</div>
<?php endif; ?>
<?php
// ── Discord-Card (eingebaut, kein extra Plugin nötig) ──────────────
$discord_meta = WBF_DB::get_user_meta( $profile->id );
$discord_current = trim( $discord_meta['discord_username'] ?? '' );
$discord_connected = $discord_current !== '';
?>
<?php
$s = wbf_get_settings();
$discord_bot_configured = ! empty( trim( $s['discord_bot_token'] ?? '' ) );
?>
<div class="wbf-connection-card wbf-connection-card--discord">
<div class="wbf-connection-card__icon">
<i class="fab fa-discord"></i>
</div>
<div class="wbf-connection-card__head">
<span class="wbf-connection-card__title">Discord</span>
<?php if ( $discord_connected ) : ?>
<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 ( $discord_connected ) : ?>
<!-- ── Bereits verbunden ── -->
<div class="wbf-discord-connected-info">
<span class="wbf-discord-linked-name">
<i class="fab fa-discord" style="color:#5865f2"></i>
<?php echo esc_html( $discord_current ); ?>
</span>
</div>
<div class="wbf-connect-row" style="margin-top:.75rem">
<button type="button" class="wbf-btn wbf-btn--primary" id="wbf-discord-relink">
<i class="fas fa-rotate"></i> Neu verknüpfen
</button>
<button type="button" class="wbf-btn wbf-btn--ghost" id="wbf-discord-disconnect">
<i class="fas fa-unlink"></i> Trennen
</button>
</div>
<div id="wbf-discord-msg" style="margin-top:.5rem;font-size:.82rem"></div>
<!-- Formular (standardmäßig ausgeblendet, bei "Neu verknüpfen" sichtbar) -->
<div id="wbf-discord-form" style="display:none;margin-top:1rem">
<?php self::render_discord_form( $discord_bot_configured ); ?>
</div>
<?php else : ?>
<!-- ── Noch nicht verbunden ── -->
<p class="wbf-connection-card__desc">
Verknüpfe deinen Discord-Account mit deinem Profil.
<?php if ( $discord_bot_configured ) : ?>
Ein Bestätigungs-Code wird dir per Discord-DM zugeschickt.
<?php else : ?>
<em style="color:var(--c-muted)">(Bot noch nicht konfiguriert wende dich an einen Admin.)</em>
<?php endif; ?>
</p>
<div id="wbf-discord-msg" style="margin-top:.3rem;font-size:.82rem"></div>
<div id="wbf-discord-form">
<?php self::render_discord_form( $discord_bot_configured ); ?>
</div>
<?php endif; ?>
</div>
</div>
<?php
// Hook für weitere Verbindungen (z.B. Steam, Twitch, …)
// Nutzung: add_filter('wbf_profile_connections', function($html, $profile) {
// return $html . '<div class="wbf-connection-card">…</div>';
// }, 10, 2);
echo apply_filters('wbf_profile_connections', '', $profile);
?>
</div>
</div>
<?php endif; /* end Tab MC */ ?>
</div><!-- /.wbf-profile-main -->
</div><!-- /.wbf-profile-layout -->
</div>
</div>
<?php return ob_get_clean();
}
// ── TAG PAGE ─────────────────────────────────────────────────────────────
// ── Discord Verifikations-Formular (3-Schritt) ────────────────────────────
private static function render_discord_form( $bot_configured ) { ?>
<?php if ( ! $bot_configured ) : ?>
<p style="color:var(--c-muted);font-size:.83rem;margin:0">
<i class="fas fa-triangle-exclamation"></i>
Discord-Bot noch nicht eingerichtet. Bitte Admin kontaktieren.
</p>
<?php else : ?>
<!-- Schritt 1: Benutzername eingeben -->
<div id="wbf-dc-step1">
<label>DISCORD-BENUTZERNAME</label>
<div class="wbf-connect-row">
<input type="text"
id="wbf-discord-input"
placeholder="z. B. MvViper"
maxlength="40"
autocomplete="off">
<button type="button" class="wbf-btn wbf-btn--primary" id="wbf-discord-send-code">
<i class="fab fa-discord"></i> Code senden
</button>
</div>
<p style="font-size:.78rem;color:var(--c-muted);margin:.45rem 0 0">
<i class="fas fa-info-circle"></i>
Du musst Mitglied unseres Discord-Servers sein und DMs erlauben.
</p>
</div>
<!-- Schritt 2: Code eingeben (zunächst ausgeblendet) -->
<div id="wbf-dc-step2" style="display:none;margin-top:.9rem">
<label>BESTÄTIGUNGS-CODE (aus Discord-DM)</label>
<div class="wbf-connect-row">
<input type="text"
id="wbf-discord-code-input"
placeholder="A1B2C3"
maxlength="6"
autocomplete="off"
style="font-family:monospace;letter-spacing:.15em;text-transform:uppercase;max-width:140px">
<button type="button" class="wbf-btn wbf-btn--primary" id="wbf-discord-verify">
<i class="fas fa-check"></i> Bestätigen
</button>
<button type="button" class="wbf-btn wbf-btn--ghost" id="wbf-discord-code-back">
<i class="fas fa-arrow-left"></i> Zurück
</button>
</div>
<p style="font-size:.78rem;color:var(--c-muted);margin:.45rem 0 0">
<i class="fas fa-clock"></i> Code ist 10 Minuten gültig.
</p>
</div>
<?php endif; ?>
<?php }
private static function view_tag() {
$slug = sanitize_title( $_GET['forum_tag'] ?? '' );
$tag = WBF_DB::get_tag($slug);
if (!$tag) return '<p class="wbf-notice">Tag nicht gefunden.</p>';
$current = WBF_Auth::get_current_user();
$page = max(1,(int)($_GET['fp']??1));
$threads = WBF_DB::get_threads_by_tag($slug, $page, 20);
$total = WBF_DB::count_threads_by_tag($slug);
$pages = ceil($total/20) ?: 1;
ob_start(); ?>
<div class="wbf-wrap">
<?php self::render_topbar($current); ?>
<div class="wbf-container wbf-mt">
<nav class="wbf-breadcrumb">
<a href="<?php echo esc_url(remove_query_arg(['forum_tag','fp'])); ?>"><i class="fas fa-home"></i> Forum</a>
<span>/</span><span><i class="fas fa-hashtag"></i> <?php echo esc_html($tag->name); ?></span>
</nav>
<div class="wbf-section-header">
<h2><i class="fas fa-hashtag"></i> <?php echo esc_html($tag->name); ?>
<span style="font-size:.85rem;font-weight:400;color:var(--c-muted)"><?php echo (int)$total; ?> Thread<?php echo $total!=1?'s':''; ?></span>
</h2>
</div>
<?php if (empty($threads)): ?>
<div class="wbf-empty"><i class="fas fa-hashtag"></i><p>Keine Threads mit diesem Tag.</p></div>
<?php else: ?>
<div class="wbf-thread-list">
<?php foreach ($threads as $t):
$liked = $current ? WBF_DB::has_liked($current->id,$t->id,'thread') : false;
$t_tags = WBF_DB::get_thread_tags($t->id); ?>
<div class="wbf-thread-row" data-thread-id="<?php echo (int)$t->id; ?>" data-last-reply="<?php echo esc_attr($t->last_reply_at); ?>">
<div class="wbf-thread-row__avatar"><?php echo self::avatar($t->avatar_url,$t->display_name); ?></div>
<div class="wbf-thread-row__body">
<div class="wbf-thread-row__top">
<a href="?forum_thread=<?php echo (int)$t->id; ?>" class="wbf-thread-row__title"><?php echo esc_html($t->title); ?></a>
<span class="wbf-muted" style="font-size:.75rem">in <a href="?forum_cat=<?php echo esc_attr($t->cat_slug); ?>" style="color:var(--c-primary)"><?php echo esc_html($t->cat_name); ?></a></span>
</div>
<div class="wbf-thread-row__meta">
<span>von <strong><?php echo esc_html($t->display_name); ?></strong></span>
<?php echo self::role_badge($t->author_role); ?>
<span><?php echo self::time_ago($t->created_at); ?></span>
</div>
<?php echo self::render_tags($t_tags, true); ?>
</div>
<div class="wbf-thread-row__stats">
<span><i class="fas fa-comment-dots"></i> <?php echo (int)$t->reply_count; ?></span>
<span><i class="fas fa-eye"></i> <?php echo (int)$t->views; ?></span>
<?php echo self::like_btn($t->id,'thread',$t->like_count,$liked); ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php if ($pages>1): ?>
<div class="wbf-pagination">
<?php for ($i=1;$i<=$pages;$i++): ?>
<a href="?forum_tag=<?php echo esc_attr($slug); ?>&fp=<?php echo $i; ?>" class="wbf-page-btn<?php echo ($i==$page)?' active':''; ?>"><?php echo $i; ?></a>
<?php endfor; ?>
</div>
<?php endif; ?>
<?php endif; ?>
<!-- Popular Tags Cloud -->
<?php $pop_tags = WBF_DB::get_popular_tags(40); if (!empty($pop_tags)): ?>
<div style="margin-top:2rem">
<h3 style="font-size:.82rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--c-text-dim);margin-bottom:.75rem">
<i class="fas fa-tags"></i> Alle Tags
</h3>
<div class="wbf-tag-cloud">
<?php foreach ($pop_tags as $pt): ?>
<a href="?forum_tag=<?php echo esc_attr($pt->slug); ?>"
class="wbf-tag<?php echo $pt->slug===$slug?' wbf-tag--active':''; ?>">
<i class="fas fa-hashtag"></i><?php echo esc_html($pt->name); ?>
<span class="wbf-tag__count"><?php echo (int)$pt->use_count; ?></span>
</a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php self::render_forum_footer(); ?>
</div>
<?php self::render_auth_modal(); ?>
</div>
<?php return ob_get_clean();
}
// ── PRIVATE NACHRICHTEN ──────────────────────────────────────────────────
private static function view_dm() {
$current = WBF_Auth::get_current_user();
if (!$current) {
ob_start(); ?>
<div class="wbf-wrap">
<?php self::render_topbar(null); ?>
<div class="wbf-container wbf-mt" style="display:flex;justify-content:center;padding:3rem 1rem">
<div style="width:100%;max-width:460px;background:var(--c-surface);border:1px solid rgba(0,180,216,.25);border-radius:var(--radius);padding:2rem;position:relative;overflow:hidden;box-shadow:0 24px 60px rgba(0,0,0,.6)">
<div style="position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,#00b4d8,transparent)"></div>
<div style="text-align:center;margin-bottom:1.5rem">
<div style="font-size:2.2rem;margin-bottom:.6rem">🔒</div>
<h2 style="font-size:1.1rem;font-weight:700;color:var(--c-text);margin-bottom:.3rem">Bitte einloggen</h2>
<p style="font-size:.85rem;color:var(--c-muted)">Um Nachrichten lesen zu können, musst du eingeloggt sein.</p>
</div>
<?php self::render_auth_forms(); ?>
</div>
</div>
</div>
<?php return ob_get_clean();
}
$partner_id = (int)($_GET['with'] ?? 0);
ob_start(); ?>
<div class="wbf-wrap">
<?php self::render_topbar($current); ?>
<div class="wbf-container wbf-mt">
<nav class="wbf-breadcrumb">
<a href="<?php echo esc_url(remove_query_arg(['forum_dm','with'])); ?>"><i class="fas fa-home"></i> Forum</a>
<span>/</span><span><i class="fas fa-envelope"></i> Nachrichten</span>
</nav>
<div class="wbf-dm-layout">
<aside class="wbf-dm-sidebar">
<div class="wbf-dm-sidebar__header">
<span><i class="fas fa-inbox"></i> Posteingang</span>
<div style="display:flex;align-items:center;gap:.4rem">
<button class="wbf-btn wbf-btn--primary wbf-btn--sm" id="wbfNewDmBtn"><i class="fas fa-pen-to-square"></i> Neu</button>
<a href="<?php echo esc_url(remove_query_arg(['forum_dm','with'])); ?>" class="wbf-dm-close-btn" title="Nachrichten schließen"><i class="fas fa-xmark"></i></a>
</div>
</div>
<div class="wbf-dm-inbox" id="wbfDmInbox">
<div style="padding:1.5rem;text-align:center;color:var(--c-muted)"><i class="fas fa-spinner fa-spin"></i></div>
</div>
</aside>
<div class="wbf-dm-main">
<?php if ($partner_id): ?>
<div class="wbf-dm-conversation" id="wbfDmConversation" data-partner="<?php echo $partner_id; ?>">
<div class="wbf-dm-header" id="wbfDmHeader">
<div style="text-align:center;padding:.5rem;color:var(--c-muted)"><i class="fas fa-spinner fa-spin"></i></div>
</div>
<div class="wbf-dm-load-more-wrap" id="wbfDmLoadMoreWrap" style="display:none;text-align:center;padding:.5rem">
<button class="wbf-btn wbf-btn--sm" id="wbfDmLoadMore" data-offset="0" data-partner="<?php echo $partner_id; ?>">
<i class="fas fa-clock-rotate-left"></i> Ältere Nachrichten laden
</button>
</div>
<div class="wbf-dm-messages" id="wbfDmMessages">
<div style="text-align:center;padding:2rem;color:var(--c-muted)"><i class="fas fa-spinner fa-spin"></i></div>
</div>
<div class="wbf-dm-compose">
<textarea id="wbfDmReplyText" placeholder="Antwort schreiben…" rows="2"></textarea>
<button class="wbf-btn wbf-btn--primary" id="wbfDmSendReply" data-to="<?php echo $partner_id; ?>">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</div>
<?php else: ?>
<div class="wbf-dm-empty">
<i class="fas fa-envelope-open-text"></i>
<p>Wähle eine Konversation aus oder starte eine neue Nachricht.</p>
<button class="wbf-btn wbf-btn--primary" id="wbfNewDmBtn2"><i class="fas fa-pen-to-square"></i> Neue Nachricht</button>
</div>
<?php endif; ?>
</div>
</div>
<?php self::render_forum_footer(); ?>
</div>
<?php self::render_auth_modal(); ?>
<?php self::render_dm_compose_modal(); ?>
</div>
<?php return ob_get_clean();
}
private static function render_dm_compose_modal() { ?>
<div class="wbf-modal" id="wbfDmComposeModal">
<div class="wbf-modal__box">
<button class="wbf-modal__close" onclick="document.getElementById('wbfDmComposeModal').classList.remove('active')">&times;</button>
<h3 style="margin-bottom:1.2rem"><i class="fas fa-pen-to-square" style="color:var(--c-primary)"></i> Neue Nachricht</h3>
<div class="wbf-form-row">
<label>Empfänger</label>
<div class="wbf-tag-input-wrap" id="wbfDmRecipientWrap">
<input type="text" id="wbfDmRecipientInput" placeholder="@Benutzername suchen…" autocomplete="off">
<input type="hidden" id="wbfDmToId" value="">
<div class="wbf-tag-suggest" id="wbfDmSuggest" style="display:none"></div>
</div>
</div>
<div class="wbf-form-row">
<label>Nachricht</label>
<textarea id="wbfDmComposeText" rows="5" placeholder="Deine Nachricht…" maxlength="2000"></textarea>
</div>
<div style="display:flex;gap:1rem;align-items:center;margin-top:.5rem">
<button class="wbf-btn wbf-btn--primary" id="wbfDmComposeSend"><i class="fas fa-paper-plane"></i> Senden</button>
<span class="wbf-msg" id="wbfDmComposeMsg"></span>
</div>
</div>
</div>
<?php }
// ── PARTIALS ──────────────────────────────────────────────────────────────
// ── SEARCH ────────────────────────────────────────────────────────────────
private static function view_search() {
$cur_s = WBF_Auth::get_current_user();
$maint_s = wbf_get_settings()['maintenance_mode'] ?? '0';
if ($maint_s === '1' && (!$cur_s || WBF_Roles::level($cur_s->role) < 50)) return self::view_maintenance();
$query = sanitize_text_field($_GET['q'] ?? '');
$current = WBF_Auth::get_current_user();
$results = mb_strlen($query) >= 2 ? WBF_DB::search($query, 40, $current) : [];
ob_start(); ?>
<div class="wbf-wrap">
<?php self::render_topbar($current); ?>
<div class="wbf-container wbf-mt">
<nav class="wbf-breadcrumb">
<a href="<?php echo esc_url(remove_query_arg(['forum_search'])); ?>"><i class="fas fa-home"></i> Forum</a>
<span>/</span><span>Suche</span>
</nav>
<div class="wbf-section-header">
<h2><i class="fas fa-search"></i> Suchergebnisse
<?php if ($query): ?>
<span style="font-size:.9rem;font-weight:400;color:var(--c-muted)"> für &bdquo;<?php echo esc_html($query); ?>&ldquo;</span>
<?php endif; ?>
</h2>
</div>
<?php if (empty($results)): ?>
<div class="wbf-empty"><i class="fas fa-search"></i><p>Keine Ergebnisse<?php echo $query ? ' für &bdquo;'.esc_html($query).'&ldquo;' : ''; ?>.</p></div>
<?php else: ?>
<div class="wbf-search-results">
<?php foreach ($results as $r):
$is_thread = $r->result_type === 'thread';
$url = $is_thread ? '?forum_thread='.(int)$r->id : '?forum_thread='.(int)$r->id;
$preview = esc_html(mb_substr(strip_tags($r->content), 0, 200));
$more = mb_strlen(strip_tags($r->content)) > 200 ? '…' : '';
?>
<div class="wbf-search-result-item">
<div class="wbf-search-result__meta">
<?php if ($is_thread): ?>
<span class="wbf-search-result__type wbf-search-result__type--thread"><i class="fas fa-layer-group"></i> Thread</span>
<?php else: ?>
<span class="wbf-search-result__type wbf-search-result__type--post"><i class="fas fa-comment"></i> Antwort</span>
<?php endif; ?>
<span class="wbf-search-result__cat"><i class="fas fa-folder"></i> <?php echo esc_html($r->cat_name); ?></span>
<span class="wbf-search-result__author"><?php echo self::avatar($r->avatar_url, $r->display_name, 18); ?> <?php echo esc_html($r->display_name); ?></span>
<span class="wbf-search-result__date"><?php echo self::time_ago($r->created_at); ?></span>
</div>
<a href="<?php echo esc_url($url); ?>" class="wbf-search-result__title"><?php echo esc_html(mb_substr($r->title, 0, 80)); ?></a>
<p class="wbf-search-result__preview"><?php echo $preview.$more; ?></p>
</div>
<?php endforeach; ?>
</div>
<p style="color:var(--c-muted);font-size:.82rem;margin-top:1rem"><?php echo count($results); ?> Ergebnis(se) gefunden.</p>
<?php endif; ?>
<?php self::render_forum_footer(); ?>
</div>
<?php self::render_auth_modal(); ?>
</div>
<?php return ob_get_clean();
}
private static function render_topbar( $current ) {
$unread = $current ? WBF_DB::count_unread_notifications($current->id) : 0;
?>
<div class="wbf-topbar">
<div class="wbf-topbar__inner">
<a href="<?php echo esc_url(wbf_get_forum_url()); ?>" class="wbf-topbar__brand">
<i class="fas fa-comments"></i> <?php echo esc_html(wbf_get_settings()['topbar_brand']); ?>
</a>
<!-- Suchfeld -->
<form class="wbf-search-form" id="wbfSearchForm" onsubmit="return false">
<div class="wbf-search-input-wrap">
<i class="fas fa-search wbf-search-icon"></i>
<input type="text" id="wbfSearchInput" placeholder="Forum durchsuchen" autocomplete="off">
</div>
<div class="wbf-search-dropdown" id="wbfSearchDropdown" style="display:none"></div>
</form>
<div class="wbf-topbar__right">
<?php if ($current): ?>
<!-- DM-Button -->
<?php $unread_dm = WBF_DB::count_unread_messages($current->id); ?>
<a href="?forum_dm=inbox" class="wbf-notif-btn wbf-dm-btn" title="Nachrichten" style="position:relative;text-decoration:none">
<i class="fas fa-envelope"></i>
<?php if ($unread_dm > 0): ?>
<span class="wbf-notif-badge" style="background:#22c55e"><?php echo min($unread_dm,99); ?></span>
<?php endif; ?>
</a>
<!-- Benachrichtigungs-Glocke -->
<div class="wbf-notif-wrap" id="wbfNotifWrap">
<button class="wbf-notif-btn" id="wbfNotifBtn" title="Benachrichtigungen">
<i class="fas fa-bell"></i>
<?php if ($unread > 0): ?>
<span class="wbf-notif-badge" id="wbfNotifBadge"><?php echo min($unread,99); ?></span>
<?php else: ?>
<span class="wbf-notif-badge" id="wbfNotifBadge" style="display:none">0</span>
<?php endif; ?>
</button>
<div class="wbf-notif-dropdown" id="wbfNotifDropdown" style="display:none">
<div class="wbf-notif-header">
<span><i class="fas fa-bell"></i> Benachrichtigungen</span>
<button class="wbf-notif-mark-read" id="wbfMarkAllRead">Alle gelesen</button>
</div>
<div class="wbf-notif-list" id="wbfNotifList">
<div class="wbf-notif-loading"><i class="fas fa-spinner fa-spin"></i></div>
</div>
</div>
</div>
<a href="?forum_profile=<?php echo (int)$current->id; ?>" class="wbf-topbar__user">
<?php echo self::avatar($current->avatar_url,$current->display_name,28); ?>
<?php echo esc_html($current->display_name); ?>
<?php echo self::role_badge($current->role); ?>
</a>
<a href="<?php echo esc_url(wp_nonce_url(wbf_get_forum_url() . '?wbf_do_logout=1', 'wbf_logout')); ?>" class="wbf-btn wbf-btn--sm wbf-btn--outline"><?php echo esc_html(wbf_get_settings()['btn_logout']); ?></a>
<?php else: ?>
<button class="wbf-btn wbf-btn--sm" id="wbfOpenLogin"><?php echo esc_html(wbf_get_settings()['btn_login']); ?></button>
<button class="wbf-btn wbf-btn--sm wbf-btn--primary" id="wbfOpenRegister"><?php echo esc_html(wbf_get_settings()['btn_register']); ?></button>
<?php endif; ?>
</div>
</div>
</div>
<?php }
private static function render_auth_forms() {
$reg_mode = wbf_get_settings()['registration_mode'] ?? 'open';
$invite_msg = wbf_get_settings()['invite_message'] ?? 'Registrierung ist aktuell nur auf Einladung möglich.';
?>
<div class="wbf-auth-box">
<div class="wbf-auth-tabs">
<button class="wbf-auth-tab active" data-tab="login">Login</button>
<?php if ($reg_mode === 'open'): ?>
<button class="wbf-auth-tab" data-tab="register">Registrieren</button>
<?php elseif ($reg_mode === 'invite'): ?>
<button class="wbf-auth-tab wbf-auth-tab--muted" disabled title="<?php echo esc_attr($invite_msg); ?>">Registrieren <i class="fas fa-lock" style="font-size:.7em"></i></button>
<?php endif; ?>
</div>
<div class="wbf-auth-panel active" data-panel="login">
<div class="wbf-form-row"><input type="text" class="wbf-field-username" placeholder="Benutzername oder E-Mail"></div>
<div class="wbf-form-row"><input type="password" class="wbf-field-password" placeholder="Passwort"></div>
<div class="wbf-form-row" style="display:flex;align-items:center;gap:.5rem;margin-bottom:.6rem">
<input type="checkbox" class="wbf-field-remember" style="width:16px;height:16px;cursor:pointer;accent-color:var(--c-primary,#00b4d8)">
<label style="font-size:.82rem;color:var(--c-muted);cursor:pointer;margin:0">30 Tage eingeloggt bleiben</label>
</div>
<button class="wbf-btn wbf-btn--primary wbf-btn--full wbf-login-submit-btn"><i class="fas fa-sign-in-alt"></i> Einloggen</button>
<div style="text-align:right;margin-top:.4rem"><a href="#" class="wbf-forgot-link" style="font-size:.78rem;color:var(--c-muted)">Passwort vergessen?</a></div>
<span class="wbf-login-msg wbf-msg"></span>
</div>
<!-- Registrierung gesperrt/invite -->
<?php if ($reg_mode === 'invite'): ?>
<div class="wbf-auth-panel" data-panel="register">
<div class="wbf-notice" style="margin:.5rem 0;font-size:.85rem;text-align:center">
<i class="fas fa-lock"></i> <?php echo esc_html($invite_msg); ?>
</div>
</div>
<?php elseif ($reg_mode === 'disabled'): ?>
<div class="wbf-auth-panel" data-panel="register">
<div class="wbf-notice wbf-notice--warning" style="margin:.5rem 0;font-size:.85rem;text-align:center">
<i class="fas fa-ban"></i> Registrierung ist deaktiviert.
</div>
</div>
<?php endif; ?>
<!-- Passwort vergessen -->
<div class="wbf-auth-panel" data-panel="forgot">
<p style="font-size:.82rem;color:var(--c-text-dim);margin-bottom:.75rem">Gib deine E-Mail ein — wir schicken dir einen Reset-Link.</p>
<div class="wbf-form-row"><input type="email" class="wbf-field-forgot-email" placeholder="deine@email.de"></div>
<button class="wbf-btn wbf-btn--primary wbf-btn--full wbf-forgot-submit-btn"><i class="fas fa-paper-plane"></i> Reset-Link senden</button>
<div style="text-align:center;margin-top:.6rem"><a href="#" class="wbf-show-login" style="font-size:.78rem;color:var(--c-muted)">← Zurück zum Login</a></div>
<span class="wbf-forgot-msg wbf-msg"></span>
</div>
<div class="wbf-auth-panel" data-panel="register">
<?php
$inv_code_prefill = '';
if (isset($_GET['wbf_invite'])) $inv_code_prefill = strtoupper(sanitize_text_field($_GET['wbf_invite']));
$reg_mode_now = wbf_get_settings()['registration_mode'] ?? 'open';
if ($reg_mode_now === 'invite'): ?>
<div class="wbf-form-row">
<input type="text" class="wbf-field-invite-code"
placeholder="Einladungscode"
value="<?php echo esc_attr($inv_code_prefill); ?>"
style="text-transform:uppercase;letter-spacing:.1em;font-weight:700">
</div>
<?php endif; ?>
<!-- Spam: Honeypot (versteckt) + Zeitstempel -->
<input type="text" name="wbf_website" class="wbf-hp-field" tabindex="-1" autocomplete="off" style="display:none!important;visibility:hidden;position:absolute;left:-9999px">
<input type="hidden" class="wbf-field-form-time" value="<?php echo time(); ?>">
<div class="wbf-form-row"><input type="text" class="wbf-field-reg-user" placeholder="Benutzername"></div>
<div class="wbf-form-row"><input type="text" class="wbf-field-reg-name" placeholder="Anzeigename"></div>
<div class="wbf-form-row"><input type="email" class="wbf-field-reg-email" placeholder="E-Mail"></div>
<div class="wbf-form-row"><input type="password" class="wbf-field-reg-pass" placeholder="Passwort (min. 6 Zeichen)"></div>
<?php
$rules_required = ( wbf_get_settings()['rules_accept_required'] ?? '1' ) === '1';
$rules_enabled = ( wbf_get_settings()['rules_enabled'] ?? '1' ) === '1';
if ( $rules_enabled ):
?>
<label style="display:flex;align-items:center;gap:.5rem;font-size:.82rem;color:var(--c-text-dim);cursor:pointer;margin-bottom:.75rem;line-height:1.4;flex-wrap:wrap">
<input type="checkbox" class="wbf-field-rules-accept"
style="width:15px;height:15px;accent-color:var(--c-primary);cursor:pointer;flex-shrink:0"
<?php echo $rules_required ? 'required' : ''; ?>>
<span>Ich akzeptiere die <a href="<?php echo esc_url(wbf_get_forum_url().'?forum_rules=1'); ?>" target="_blank" style="color:var(--c-primary);font-weight:600;white-space:nowrap">Forum-Regeln</a><?php echo $rules_required ? ' <span style="color:var(--c-danger)">*</span>' : ''; ?></span>
</label>
<?php endif; ?>
<button class="wbf-btn wbf-btn--primary wbf-btn--full wbf-reg-submit-btn"><i class="fas fa-user-plus"></i> Konto erstellen</button>
<span class="wbf-reg-msg wbf-msg"></span>
</div>
</div>
<?php }
private static function render_auth_modal() { ?>
<div class="wbf-modal" id="wbfAuthModal">
<div class="wbf-modal__box">
<button class="wbf-modal__close" onclick="document.getElementById('wbfAuthModal').classList.remove('active')">&times;</button>
<h3 style="margin-bottom:1rem"><i class="fas fa-user-circle"></i> Forum Zugang</h3>
<?php self::render_auth_forms(); ?>
</div>
</div>
<?php }
private static function render_move_modal( $categories, $thread_id ) {
$parents = array_filter($categories, fn($c) => (int)$c->parent_id === 0);
$children_map = [];
foreach ($categories as $c) { if ((int)$c->parent_id > 0) $children_map[$c->parent_id][] = $c; }
?>
<div class="wbf-modal" id="wbfMoveModal">
<div class="wbf-modal__box">
<button class="wbf-modal__close" onclick="document.getElementById('wbfMoveModal').classList.remove('active')">&times;</button>
<h3 style="margin-bottom:1.2rem"><i class="fas fa-right-left" style="color:var(--c-primary)"></i> Thread verschieben</h3>
<input type="hidden" id="wbfMoveThreadId" value="<?php echo (int)$thread_id; ?>">
<div class="wbf-form-row">
<label>Ziel-Kategorie</label>
<select id="wbfMoveCatSelect">
<?php foreach ($parents as $p): ?>
<optgroup label="<?php echo esc_attr($p->name); ?>">
<option value="<?php echo (int)$p->id; ?>"><?php echo esc_html($p->name); ?> (Allgemein)</option>
<?php foreach (($children_map[$p->id]??[]) as $child): ?>
<option value="<?php echo (int)$child->id; ?>">&nbsp;&nbsp;↳ <?php echo esc_html($child->name); ?></option>
<?php endforeach; ?>
</optgroup>
<?php endforeach; ?>
</select>
</div>
<div style="display:flex;gap:1rem;align-items:center;margin-top:1rem">
<button class="wbf-btn wbf-btn--primary" id="wbfSubmitMove">
<i class="fas fa-right-left"></i> Verschieben
</button>
<span class="wbf-msg" id="wbfMoveMsg"></span>
</div>
</div>
</div>
<?php }
private static function render_report_modal() { ?>
<div class="wbf-modal" id="wbfReportModal">
<div class="wbf-modal__box">
<button class="wbf-modal__close" onclick="document.getElementById('wbfReportModal').classList.remove('active')">&times;</button>
<h3 style="margin-bottom:1rem"><i class="fas fa-flag" style="color:var(--c-danger)"></i> Beitrag melden</h3>
<input type="hidden" id="wbfReportId" value="">
<input type="hidden" id="wbfReportType" value="post">
<div class="wbf-form-row">
<label>Grund <span style="color:var(--c-danger)">*</span></label>
<select id="wbfReportReason">
<option value="">— Grund auswählen —</option>
<option value="spam">Spam / Werbung</option>
<option value="harassment">Belästigung / Beleidigung</option>
<option value="inappropriate">Unangemessener Inhalt</option>
<option value="misinformation">Fehlinformation</option>
<option value="off-topic">Offtopic / Irrelevant</option>
<option value="other">Sonstiges</option>
</select>
</div>
<div class="wbf-form-row">
<label>Zusätzliche Notiz <small>(optional)</small></label>
<textarea id="wbfReportNote" rows="3" placeholder="Beschreibe das Problem genauer…" maxlength="500"></textarea>
</div>
<div style="display:flex;gap:1rem;align-items:center;margin-top:.5rem">
<button class="wbf-btn wbf-btn--danger" id="wbfSubmitReport"><i class="fas fa-paper-plane"></i> Melden</button>
<span class="wbf-msg" id="wbfReportMsg"></span>
</div>
</div>
</div>
<?php }
private static function render_editor_toolbar( $target_id ) {
$emojis = [
'😊','😂','😍','🥰','😎','😅','😆','🙃','😇','🤩',
'😏','😒','😔','😢','😭','😡','😱','🤯','🥳','😴',
'🤔','🤗','😘','😜','🤪','😬','🙄','😤','🥺','🤭',
'👍','👎','❤️','🔥','💯','🎉','🙏','👏','💪','✨',
'✅','❌','⭐','💡','❓','❗','💬','📌','🏆','🎯',
];
?>
<div class="wbf-editor-toolbar" data-target="<?php echo esc_attr($target_id); ?>">
<!-- Formatierung -->
<button type="button" class="wbf-tb-btn" data-action="bb" data-bb="b" title="Fett [b]"><b>B</b></button>
<button type="button" class="wbf-tb-btn" data-action="bb" data-bb="i" title="Kursiv [i]"><i>I</i></button>
<button type="button" class="wbf-tb-btn" data-action="bb" data-bb="u" title="Unterstrichen [u]" style="text-decoration:underline">U</button>
<button type="button" class="wbf-tb-btn" data-action="bb" data-bb="s" title="Durchgestrichen [s]" style="text-decoration:line-through">S</button>
<span class="wbf-tb-divider"></span>
<!-- Überschriften -->
<button type="button" class="wbf-tb-btn" data-action="bb" data-bb="h2" title="Überschrift [h2]" style="font-weight:700">H2</button>
<button type="button" class="wbf-tb-btn" data-action="bb" data-bb="h3" title="Überschrift [h3]" style="font-weight:700;font-size:.72rem">H3</button>
<span class="wbf-tb-divider"></span>
<!-- Farbe + Größe (Dropdowns) -->
<div class="wbf-tb-dropdown" style="position:relative">
<button type="button" class="wbf-tb-btn" data-action="dropdown" data-target="color" title="Textfarbe">
<i class="fas fa-palette"></i> <i class="fas fa-caret-down" style="font-size:.55rem"></i>
</button>
<div class="wbf-tb-dropdown__panel" id="wbf-color-panel-<?php echo esc_attr($target_id); ?>" style="display:none">
<div class="wbf-tb-color-grid">
<?php foreach ([
'red'=>'#ef4444','orange'=>'#f97316','gold'=>'#fbbf24',
'lime'=>'#84cc16','green'=>'#22c55e','teal'=>'#14b8a6',
'cyan'=>'#00b4d8','blue'=>'#3b82f6','purple'=>'#a78bfa',
'pink'=>'#ec4899','gray'=>'#94a3b8','white'=>'#f1f5f9',
] as $name => $hex): ?>
<button type="button" class="wbf-tb-color-swatch"
data-action="bb-color" data-color="<?php echo esc_attr($name); ?>"
title="<?php echo esc_attr($name); ?>"
style="background:<?php echo esc_attr($hex); ?>"></button>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="wbf-tb-dropdown" style="position:relative">
<button type="button" class="wbf-tb-btn" data-action="dropdown" data-target="size" title="Textgröße">
<i class="fas fa-text-height"></i> <i class="fas fa-caret-down" style="font-size:.55rem"></i>
</button>
<div class="wbf-tb-dropdown__panel" id="wbf-size-panel-<?php echo esc_attr($target_id); ?>" style="display:none">
<button type="button" class="wbf-tb-dropdown__item" data-action="bb-size" data-size="small">Klein</button>
<button type="button" class="wbf-tb-dropdown__item" data-action="bb-size" data-size="large">Groß</button>
<button type="button" class="wbf-tb-dropdown__item" data-action="bb-size" data-size="xlarge">Sehr groß</button>
</div>
</div>
<span class="wbf-tb-divider"></span>
<!-- Listen -->
<button type="button" class="wbf-tb-btn" data-action="bb-list" data-ordered="0" title="Liste [list]"><i class="fas fa-list-ul"></i></button>
<button type="button" class="wbf-tb-btn" data-action="bb-list" data-ordered="1" title="Geordnete Liste [list=1]"><i class="fas fa-list-ol"></i></button>
<span class="wbf-tb-divider"></span>
<!-- Sonderblöcke -->
<button type="button" class="wbf-tb-btn" data-action="bb" data-bb="quote" title="Zitat [quote]"><i class="fas fa-quote-left"></i></button>
<button type="button" class="wbf-tb-btn" data-action="bb" data-bb="code" title="Code-Block [code]"><i class="fas fa-code"></i></button>
<button type="button" class="wbf-tb-btn" data-action="bb" data-bb="icode" title="Inline-Code [icode]"><i class="fas fa-terminal" style="font-size:.72rem"></i></button>
<button type="button" class="wbf-tb-btn" data-action="bb-spoiler" title="Spoiler [spoiler]"><i class="fas fa-eye-slash"></i></button>
<button type="button" class="wbf-tb-btn" data-action="bb-url" title="Link [url]"><i class="fas fa-link"></i></button>
<span class="wbf-tb-divider"></span>
<!-- Medien -->
<button type="button" class="wbf-tb-btn" data-action="img-upload" title="Bild hochladen">
<i class="fas fa-image"></i>
<input type="file" class="wbf-img-file-input" accept="image/jpeg,image/png,image/gif,image/webp" style="display:none">
</button>
<button type="button" class="wbf-tb-btn" data-action="bb-img" title="Bild-URL [img]"><i class="fas fa-photo-film"></i></button>
<button type="button" class="wbf-tb-btn" data-action="bb" data-bb="hr" title="Trennlinie [hr]" data-wrap="0">—</button>
<span class="wbf-tb-divider"></span>
<!-- Emoji -->
<button type="button" class="wbf-tb-btn wbf-tb-btn--emoji" data-action="emoji" title="Emojis">😊 <i class="fas fa-caret-down" style="font-size:.6rem;opacity:.6"></i></button>
<div class="wbf-emoji-picker" style="display:none">
<div class="wbf-emoji-picker__grid">
<?php foreach ($emojis as $e): ?>
<button type="button" class="wbf-emoji-item" data-emoji="<?php echo esc_attr($e); ?>"><?php echo $e; ?></button>
<?php endforeach; ?>
</div>
</div>
</div>
<?php
}
// ── UMFRAGE-MODAL ─────────────────────────────────────────────────────────
private static function render_poll_modal( $thread_id ) { ?>
<div class="wbf-modal" id="wbfPollModal">
<div class="wbf-modal__box">
<button class="wbf-modal__close" onclick="document.getElementById('wbfPollModal').classList.remove('active')">&times;</button>
<h3 style="margin-bottom:1.25rem"><i class="fas fa-chart-bar" style="color:var(--c-primary)"></i> Umfrage erstellen</h3>
<input type="hidden" id="wbfPollThreadId" value="<?php echo (int)$thread_id; ?>">
<div class="wbf-form-row">
<label>Frage <span style="color:var(--c-danger)">*</span></label>
<input type="text" id="wbfPollQuestion" placeholder="Was ist deine Meinung zu…?" maxlength="200">
</div>
<div class="wbf-form-row">
<label>Antwortmöglichkeiten <small>(min. 2, max. 10)</small></label>
<div id="wbfPollOptions">
<div class="wbf-poll-opt-row">
<input type="text" class="wbf-poll-opt" placeholder="Option 1" maxlength="100">
<button type="button" class="wbf-btn wbf-btn--sm" style="background:rgba(240,82,82,.1);color:var(--c-danger);border-color:rgba(240,82,82,.3);min-width:32px;flex-shrink:0" onclick="wbfRemovePollOpt(this)">✕</button>
</div>
<div class="wbf-poll-opt-row">
<input type="text" class="wbf-poll-opt" placeholder="Option 2" maxlength="100">
<button type="button" class="wbf-btn wbf-btn--sm" style="background:rgba(240,82,82,.1);color:var(--c-danger);border-color:rgba(240,82,82,.3);min-width:32px;flex-shrink:0" onclick="wbfRemovePollOpt(this)">✕</button>
</div>
</div>
<button type="button" id="wbfPollAddOpt" class="wbf-btn wbf-btn--sm" style="margin-top:.5rem">
<i class="fas fa-plus"></i> Option hinzufügen
</button>
</div>
<div style="display:flex;gap:1.25rem;flex-wrap:wrap;align-items:flex-end;margin-bottom:1rem">
<label style="display:flex;align-items:center;gap:.4rem;font-size:.82rem;color:var(--c-text-dim);cursor:pointer">
<input type="checkbox" id="wbfPollMulti" style="accent-color:var(--c-primary);width:15px;height:15px">
Mehrfachauswahl erlauben
</label>
<div class="wbf-form-row" style="margin:0;flex:1;min-width:180px">
<label style="font-size:.72rem">Endet am <small style="color:var(--c-muted)">(optional)</small></label>
<input type="datetime-local" id="wbfPollEndsAt" min="<?php echo date('Y-m-d\TH:i'); ?>">
</div>
</div>
<div style="display:flex;gap:.75rem;align-items:center">
<button class="wbf-btn wbf-btn--primary" id="wbfSubmitPoll">
<i class="fas fa-chart-bar"></i> Umfrage erstellen
</button>
<span class="wbf-msg" id="wbfPollMsg"></span>
</div>
</div>
</div>
<?php }
private static function render_new_thread_modal( $categories, $current, $preselect = 0 ) {
if (!$current) return;
$parents = array_filter($categories, fn($c) => (int)$c->parent_id === 0);
$children_map = [];
foreach ($categories as $c) { if ((int)$c->parent_id > 0) $children_map[$c->parent_id][] = $c; }
?>
<div class="wbf-modal" id="wbfNewThreadModal">
<div class="wbf-modal__box wbf-modal__box--lg">
<button class="wbf-modal__close" onclick="document.getElementById('wbfNewThreadModal').classList.remove('active')">&times;</button>
<h3 id="wbfModalTitle" style="margin-bottom:1.2rem"><i class="fas fa-plus-circle"></i> Neuen Thread erstellen</h3>
<div class="wbf-form-row"><label>Kategorie</label>
<select id="wbfThreadCat">
<?php foreach ($parents as $p): ?>
<optgroup label="<?php echo esc_attr($p->name); ?>">
<option value="<?php echo (int)$p->id; ?>" <?php selected($p->id,$preselect); ?>><?php echo esc_html($p->name); ?> (Allgemein)</option>
<?php foreach (($children_map[$p->id]??[]) as $child): ?>
<option value="<?php echo (int)$child->id; ?>" <?php selected($child->id,$preselect); ?>>&nbsp;&nbsp;↳ <?php echo esc_html($child->name); ?></option>
<?php endforeach; ?>
</optgroup>
<?php endforeach; ?>
</select>
</div>
<div class="wbf-form-row"><label id="wbfTitleLabel">Titel</label><input type="text" id="wbfThreadTitle" placeholder="Titel deines Threads"></div>
<?php $prefixes = WBF_DB::get_prefixes(); if (!empty($prefixes)): ?>
<div class="wbf-form-row" id="wbfPrefixRow">
<label>Präfix <small>(optional)</small></label>
<select id="wbfThreadPrefix">
<option value="">— Kein Präfix —</option>
<?php foreach ($prefixes as $px): ?>
<option value="<?php echo (int)$px->id; ?>"
data-color="<?php echo esc_attr($px->color); ?>"
data-bg="<?php echo esc_attr($px->bg_color); ?>">
<?php echo esc_html($px->label); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div class="wbf-form-row" id="wbfContentRow">
<label>Inhalt</label>
<?php self::render_editor_toolbar('wbfThreadContent'); ?>
<textarea id="wbfThreadContent" rows="7" placeholder="Was möchtest du besprechen?"></textarea>
</div>
<div class="wbf-form-row" id="wbfTagsRow">
<label>Tags <small>(kommagetrennt, max. 10 — z.B. php, wordpress, tipps)</small></label>
<div class="wbf-tag-input-wrap" id="wbfTagInputWrap">
<div class="wbf-tag-pills" id="wbfTagPills"></div>
<input type="text" id="wbfTagInput" placeholder="#tag hinzufügen…" autocomplete="off">
<input type="hidden" id="wbfThreadTags" value="">
<div class="wbf-tag-suggest" id="wbfTagSuggest" style="display:none"></div>
</div>
</div>
<!-- Thread Submit Row -->
<div id="wbfThreadSubmitRow" style="display:flex;gap:1rem;align-items:center;margin-top:.25rem">
<button class="wbf-btn wbf-btn--primary" id="wbfSubmitThread"><i class="fas fa-paper-plane"></i> Thread erstellen</button>
<button type="button" id="wbfShowPollSection" class="wbf-btn wbf-btn--sm wbf-btn--outline-poll">
<i class="fas fa-chart-bar"></i> Umfrage hinzufügen
</button>
<span class="wbf-msg" id="wbfThreadMsg"></span>
</div>
<!-- Poll Section -->
<div id="wbfPollSection" style="margin-top:1.25rem;border-top:1px solid var(--c-border);padding-top:1.1rem;display:none">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:.9rem">
<span style="font-size:.85rem;font-weight:700;color:#fbbf24"><i class="fas fa-chart-bar"></i> Umfrage</span>
<button type="button" id="wbfRemovePollSection" class="wbf-btn wbf-btn--sm" style="background:rgba(240,82,82,.1);color:var(--c-danger);border-color:rgba(240,82,82,.3);padding:.25rem .6rem;font-size:.72rem"><i class="fas fa-xmark"></i> Entfernen</button>
</div>
<div class="wbf-form-row">
<label>Frage <span style="color:var(--c-danger)">*</span></label>
<input type="text" id="wbfNewThreadPollQuestion" placeholder="Was ist deine Meinung zu…?" maxlength="200">
</div>
<div class="wbf-form-row">
<label>Antwortmöglichkeiten <small>(min. 2, max. 10)</small></label>
<div id="wbfNewThreadPollOptions">
<div class="wbf-poll-opt-row" style="display:flex;gap:.5rem;margin-bottom:.4rem">
<input type="text" class="wbf-nt-poll-opt" placeholder="Option 1" maxlength="100">
<button type="button" class="wbf-btn wbf-btn--sm" style="background:rgba(240,82,82,.1);color:var(--c-danger);border-color:rgba(240,82,82,.3);min-width:32px;flex-shrink:0" onclick="wbfRemoveNTPollOpt(this)">✕</button>
</div>
<div class="wbf-poll-opt-row" style="display:flex;gap:.5rem;margin-bottom:.4rem">
<input type="text" class="wbf-nt-poll-opt" placeholder="Option 2" maxlength="100">
<button type="button" class="wbf-btn wbf-btn--sm" style="background:rgba(240,82,82,.1);color:var(--c-danger);border-color:rgba(240,82,82,.3);min-width:32px;flex-shrink:0" onclick="wbfRemoveNTPollOpt(this)">✕</button>
</div>
</div>
<button type="button" id="wbfNTPollAddOpt" class="wbf-btn wbf-btn--sm" style="margin-top:.5rem">
<i class="fas fa-plus"></i> Option hinzufügen
</button>
</div>
<label style="display:flex;align-items:center;gap:.4rem;font-size:.82rem;color:var(--c-text-dim);cursor:pointer;margin-bottom:.75rem">
<input type="checkbox" id="wbfNTPollMulti" style="accent-color:var(--c-primary);width:15px;height:15px">
Mehrfachauswahl erlauben
</label>
<div class="wbf-form-row" style="margin-bottom:1rem">
<label style="font-size:.78rem;font-weight:600;color:var(--c-text-dim)">Endet am <small style="font-weight:400;color:var(--c-muted)">(optional)</small></label>
<input type="datetime-local" id="wbfNTPollEndsAt" min="<?php echo date('Y-m-d\TH:i'); ?>" style="width:100%;box-sizing:border-box">
</div>
<div style="display:flex;gap:1rem;align-items:center">
<button class="wbf-btn wbf-btn--outline-poll" id="wbfSubmitPollThread"><i class="fas fa-chart-bar"></i> Umfrage erstellen</button>
<span class="wbf-msg" id="wbfPollThreadMsg"></span>
</div>
</div>
</div>
</div>
<?php }
// ── Passwort zurücksetzen (via Token-Link) ────────────────────────────────
private static function view_reset_password() {
$token = sanitize_text_field($_GET['wbf_reset_token'] ?? '');
$valid = $token ? WBF_DB::verify_reset_token($token) : false;
ob_start(); ?>
<div class="wbf-wrap">
<?php self::render_topbar(null); ?>
<div class="wbf-container wbf-mt" style="max-width:480px;margin-left:auto;margin-right:auto">
<div class="wbf-widget" style="padding:2rem">
<h2 style="font-size:1.1rem;font-weight:700;color:var(--c-text);margin-bottom:1.25rem">
<i class="fas fa-lock-open" style="color:var(--c-primary)"></i> Passwort zurücksetzen
</h2>
<?php if (!$valid): ?>
<div class="wbf-notice wbf-notice--warning">
<i class="fas fa-exclamation-triangle"></i>
Dieser Link ist ungültig oder abgelaufen. Bitte fordere einen neuen an.
</div>
<a href="<?php echo esc_url(wbf_get_forum_url()); ?>" class="wbf-btn wbf-btn--primary" style="margin-top:1rem;display:inline-block">
<i class="fas fa-home"></i> Zurück zum Forum
</a>
<?php else: ?>
<div class="wbf-form-row">
<label>Neues Passwort</label>
<input type="password" id="wbfResetPass1" placeholder="Mindestens 6 Zeichen">
</div>
<div class="wbf-form-row">
<label>Passwort wiederholen</label>
<input type="password" id="wbfResetPass2" placeholder="Passwort bestätigen">
</div>
<input type="hidden" id="wbfResetToken" value="<?php echo esc_attr($token); ?>">
<button class="wbf-btn wbf-btn--primary wbf-btn--full" id="wbfResetSubmit">
<i class="fas fa-check"></i> Passwort ändern
</button>
<span class="wbf-msg" id="wbfResetMsg" style="display:block;margin-top:.75rem"></span>
<?php endif; ?>
</div>
</div>
</div>
<?php return ob_get_clean();
}
// ── Mitglieder-Liste ──────────────────────────────────────────────────────
private static function view_members() {
$current = WBF_Auth::get_current_user();
$maint_m = wbf_get_settings()['maintenance_mode'] ?? '0';
if ($maint_m === '1' && (!$current || WBF_Roles::level($current->role) < 50)) return self::view_maintenance();
if ( ! $current ) {
ob_start(); ?>
<div class="wbf-wrap"><?php self::render_topbar(null); ?>
<div class="wbf-container wbf-mt">
<div class="wbf-notice wbf-notice--info"><i class="fas fa-lock"></i>
Bitte <a href="#" class="wbf-login-link">einloggen</a> um die Mitgliederliste zu sehen.
</div>
</div></div>
<?php return ob_get_clean();
}
$page = max(1, (int)($_GET['mp'] ?? 1));
$per_page = 24;
$offset = ($page - 1) * $per_page;
$search = sanitize_text_field($_GET['ms'] ?? '');
$sort = in_array($_GET['msort'] ?? '', ['registered','post_count','display_name']) ? $_GET['msort'] : 'registered';
global $wpdb;
$where = $search
? $wpdb->prepare("WHERE (display_name LIKE %s OR username LIKE %s)", '%'.$search.'%', '%'.$search.'%')
: '';
$order = $sort === 'display_name' ? 'ASC' : 'DESC';
$total = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_users $where");
$members = $wpdb->get_results($wpdb->prepare(
"SELECT id, username, display_name, avatar_url, role, post_count, registered, last_active
FROM {$wpdb->prefix}forum_users $where
ORDER BY $sort $order
LIMIT %d OFFSET %d",
$per_page, $offset
));
$pages = ceil($total / $per_page) ?: 1;
ob_start(); ?>
<div class="wbf-wrap">
<?php self::render_topbar($current); ?>
<div class="wbf-container wbf-mt">
<nav class="wbf-breadcrumb">
<a href="<?php echo esc_url(remove_query_arg(['forum_members','mp','ms','msort'])); ?>">
<i class="fas fa-home"></i> Forum
</a>
<span>/</span><span><i class="fas fa-users"></i> Mitglieder</span>
</nav>
<div class="wbf-section-header" style="margin-bottom:1.25rem">
<h2><i class="fas fa-users"></i> Mitglieder (<?php echo number_format($total); ?>)</h2>
</div>
<!-- Suche & Sortierung -->
<form method="get" class="wbf-members-filter">
<input type="hidden" name="forum_members" value="1">
<input type="text" name="ms" value="<?php echo esc_attr($search); ?>"
placeholder="Mitglied suchen…" class="wbf-members-search">
<select name="msort" class="wbf-members-sort" onchange="this.form.submit()">
<option value="registered" <?php selected($sort,'registered'); ?>>Neueste zuerst</option>
<option value="post_count" <?php selected($sort,'post_count'); ?>>Aktivste zuerst</option>
<option value="display_name"<?php selected($sort,'display_name'); ?>>Alphabetisch</option>
</select>
<button type="submit" class="wbf-btn wbf-btn--primary wbf-btn--sm">
<i class="fas fa-search"></i> Suchen
</button>
<?php if ($search): ?>
<a href="?forum_members=1" class="wbf-btn wbf-btn--sm">
<i class="fas fa-xmark"></i> Zurücksetzen
</a>
<?php endif; ?>
</form>
<!-- Mitglieder-Grid -->
<div class="wbf-members-grid">
<?php foreach ($members as $m):
$role = WBF_Roles::get($m->role);
$is_online = WBF_DB::is_online($m->id);
?>
<a href="?forum_profile=<?php echo (int)$m->id; ?>" class="wbf-member-card">
<div class="wbf-member-card__avatar-wrap">
<?php echo self::avatar($m->avatar_url, $m->display_name, 64); ?>
<?php if ($is_online): ?>
<span class="wbf-member-card__online-dot" title="Online"></span>
<?php endif; ?>
</div>
<div class="wbf-member-card__name"><?php echo esc_html($m->display_name); ?></div>
<div class="wbf-member-card__username">@<?php echo esc_html($m->username); ?></div>
<div class="wbf-member-card__badge"><?php echo self::role_badge($m->role); ?></div>
<div class="wbf-member-card__stats">
<span><i class="fas fa-comment-dots"></i> <?php echo number_format((int)$m->post_count); ?></span>
<span><i class="fas fa-calendar"></i> <?php echo date_i18n('d.m.Y', strtotime($m->registered)); ?></span>
</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>
<p>Keine Mitglieder gefunden.</p>
</div>
<?php endif; ?>
</div>
<!-- Pagination -->
<?php if ($pages > 1): ?>
<div class="wbf-pagination">
<?php for ($i = 1; $i <= $pages; $i++): ?>
<a href="?forum_members=1<?php echo $search ? '&ms='.urlencode($search) : ''; ?>&msort=<?php echo esc_attr($sort); ?>&mp=<?php echo $i; ?>"
class="wbf-page-btn<?php echo ($i === $page) ? ' active' : ''; ?>">
<?php echo $i; ?>
</a>
<?php endfor; ?>
</div>
<?php endif; ?>
<?php self::render_forum_footer(); ?>
</div>
<?php self::render_auth_modal(); ?>
</div>
<?php return ob_get_clean();
}
// ── Wartungsmodus ─────────────────────────────────────────────────────────
private static function view_maintenance() {
$s = wbf_get_settings();
$title = esc_html($s['maintenance_title'] ?? 'Wartungsarbeiten');
$msg = esc_html($s['maintenance_message'] ?? 'Das Forum wird gerade gewartet. Bitte versuche es später erneut.');
ob_start(); ?>
<div class="wbf-wrap">
<div style="min-height:60vh;display:flex;align-items:center;justify-content:center;text-align:center;padding:2rem">
<div style="max-width:480px">
<div style="font-size:4rem;margin-bottom:1.5rem">🔧</div>
<h1 style="font-size:1.6rem;font-weight:800;color:var(--c-text);margin-bottom:1rem"><?php echo $title; ?></h1>
<p style="color:var(--c-text-dim);font-size:1rem;line-height:1.7"><?php echo $msg; ?></p>
<p style="margin-top:2rem">
<a href="#" class="wbf-login-link wbf-btn wbf-btn--sm wbf-btn--outline" style="font-size:.85rem">
<i class="fas fa-lock"></i> Als Admin einloggen
</a>
</p>
</div>
</div>
<?php self::render_auth_modal(); ?>
</div>
<?php return ob_get_clean();
}
// ── FORUM-FOOTER ──────────────────────────────────────────────────────────
private static function render_forum_footer() {
if ( ( wbf_get_settings()['rules_enabled'] ?? '1' ) !== '1' ) return;
$rules_url = esc_url( wbf_get_forum_url() . '?forum_rules=1' );
?>
<div style="border:1px solid var(--c-border);border-radius:var(--radius);margin-top:2rem;padding:1rem 1.25rem;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.75rem;background:var(--c-surface)">
<span style="font-size:.78rem;color:var(--c-muted)">
<i class="fas fa-shield-halved" style="color:var(--c-primary);margin-right:.35rem"></i>
Durch die Nutzung des Forums stimmst du unseren Regeln zu.
</span>
<a href="<?php echo $rules_url; ?>"
style="display:inline-flex;align-items:center;gap:.4rem;font-size:.78rem;font-weight:700;color:#0d1117;background:var(--c-primary);padding:.35rem .9rem;border-radius:6px;text-decoration:none;transition:opacity .15s"
onmouseover="this.style.opacity='.85'" onmouseout="this.style.opacity='1'">
<i class="fas fa-scroll"></i> Forum-Regeln lesen
</a>
</div>
<?php
}
// ── FORUM-REGELN ──────────────────────────────────────────────────────────
private static function view_rules() {
$current = WBF_Auth::get_current_user();
$s = wbf_get_settings();
if ( ( $s['rules_enabled'] ?? '1' ) !== '1' ) {
return '<p class="wbf-notice">Diese Seite ist nicht verfügbar.</p>';
}
$title = esc_html( $s['rules_title'] ?? 'Forum-Regeln & Nutzungsbedingungen' );
$raw = $s['rules_content'] ?? '';
ob_start(); ?>
<div class="wbf-wrap">
<?php self::render_topbar($current); ?>
<div class="wbf-container wbf-mt" style="max-width:820px">
<nav class="wbf-breadcrumb">
<a href="<?php echo esc_url(wbf_get_forum_url()); ?>"><i class="fas fa-home"></i> Forum</a>
<span>/</span><span><i class="fas fa-shield-halved"></i> Regeln</span>
</nav>
<div style="background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--radius);overflow:hidden;margin-bottom:2rem">
<div style="background:linear-gradient(135deg,rgba(0,180,216,.12),rgba(99,102,241,.08));border-bottom:1px solid var(--c-border);padding:1.75rem 2rem;display:flex;align-items:center;gap:1rem">
<div style="width:48px;height:48px;border-radius:12px;background:rgba(0,180,216,.15);border:1px solid rgba(0,180,216,.25);display:flex;align-items:center;justify-content:center;font-size:1.3rem;flex-shrink:0">📜</div>
<div>
<h1 style="font-size:1.3rem;font-weight:800;color:var(--c-text);margin:0 0 .2rem"><?php echo $title; ?></h1>
<p style="font-size:.8rem;color:var(--c-muted);margin:0">
<i class="fas fa-clock"></i> Zuletzt aktualisiert: <?php echo date_i18n('d. F Y'); ?>
&nbsp;·&nbsp;<i class="fas fa-eye"></i> Bitte sorgfältig lesen
</p>
</div>
</div>
<div class="wbf-rules-body" style="padding:2rem">
<?php echo self::render_rules_content( $raw ); ?>
</div>
<div style="border-top:1px solid var(--c-border);padding:1.25rem 2rem;background:rgba(0,0,0,.12);display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.75rem">
<span style="font-size:.82rem;color:var(--c-muted)">
<i class="fas fa-info-circle"></i> Mit der Nutzung des Forums stimmst du diesen Regeln zu.
</span>
<a href="<?php echo esc_url(wbf_get_forum_url()); ?>" class="wbf-btn wbf-btn--sm wbf-btn--primary" style="color:#0d1117;font-weight:700">
<i class="fas fa-arrow-left"></i> Zurück zum Forum
</a>
</div>
</div>
</div>
</div>
<?php return ob_get_clean();
}
private static function render_rules_content( $raw ) {
if ( empty( $raw ) ) {
return '<p style="color:var(--c-muted)">Noch keine Regeln hinterlegt.</p>';
}
$paragraphs = preg_split( '/\n{2,}/', trim( $raw ) );
$out = '';
foreach ( $paragraphs as $para ) {
$para = trim( $para );
if ( $para === '' ) continue;
if ( preg_match( '/^\*\*(\d+\.\s*.+?)\*\*$/', $para, $m ) ) {
preg_match('/^(\d+)\.\s*(.+)$/', $m[1], $parts);
$num = esc_html($parts[1] ?? '');
$text = esc_html($parts[2] ?? $m[1]);
$out .= '<div class="wbf-rules-section">'
. '<span class="wbf-rules-num">' . $num . '</span>'
. '<h2 class="wbf-rules-heading">' . $text . '</h2>'
. '</div>';
} else {
$para = htmlspecialchars( $para, ENT_QUOTES, 'UTF-8' );
$para = preg_replace( '/\*\*(.+?)\*\*/s', '<strong>$1</strong>', $para );
$para = nl2br( $para );
$out .= '<p class="wbf-rules-para">' . $para . '</p>';
}
}
return $out;
}
}
WBF_Shortcodes::init();