Files
WP-Business-Forum/includes/class-forum-shortcodes.php
2026-03-21 18:47:28 +01:00

2213 lines
131 KiB
PHP

<?php
if ( ! defined( 'ABSPATH' ) ) exit;
class WBF_Shortcodes {
public static function init() {
add_shortcode('business_forum', [__CLASS__, 'forum_main']);
}
// ── Helpers ───────────────────────────────────────────────────────────────
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
if (isset($_GET['wbf_do_logout'])) {
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(wbf_get_forum_url() . '?wbf_do_logout=1'); ?>" 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>
</div>
<?php self::render_new_thread_modal(WBF_DB::get_categories_flat(), $current); ?>
<?php self::render_forum_footer(); ?>
<?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_new_thread_modal(WBF_DB::get_categories_flat(),$current,$cat->id); ?>
<?php self::render_forum_footer(); ?>
<?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 nl2br(esc_html($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; ?>
</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; ?>
</div>
<?php self::render_forum_footer(); ?>
<?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 nl2br(esc_html($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 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
if (!$is_own && !$is_staff && (int)($profile->profile_public ?? 1) === 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 );
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')); ?>"><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">
<!-- Avatar -->
<div class="wbf-profile-sidebar__avatar-wrap">
<img src="<?php echo esc_url($profile->avatar_url); ?>"
alt="<?php echo esc_attr($profile->display_name); ?>"
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>
<!-- Name + Badge + Username -->
<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>
</div>
<!-- Stats -->
<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>
<!-- Level-Fortschritt -->
<?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; ?>
<!-- Bio -->
<?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 nl2br(esc_html($profile->bio)); ?></p>
</div>
<?php endif; ?>
<!-- Signatur -->
<?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 nl2br(esc_html($profile->signature)); ?></p>
</div>
<?php endif; ?>
<!-- Benutzerdefinierte Profilfelder (öffentliche) -->
<?php
$cf_defs_pub = WBF_DB::get_profile_field_defs();
$cf_vals_pub = WBF_DB::get_user_meta( $profile->id );
foreach ( $cf_defs_pub as $def ):
if ( ! $is_own && empty($def['public']) ) continue;
$val = trim( $cf_vals_pub[ $def['key'] ] ?? '' );
if ( $val === '' ) continue;
?>
<div class="wbf-profile-sidebar__section">
<span class="wbf-profile-sidebar__section-label">
<i class="fas fa-<?php echo $def['type']==='url'?'link':($def['type']==='number'?'hashtag':'tag'); ?>"></i>
<?php echo esc_html($def['label']); ?>
</span>
<?php if ( $def['type'] === 'url' ): ?>
<a href="<?php echo esc_url($val); ?>" target="_blank" rel="noopener noreferrer"
style="color:var(--c-primary);font-size:.85rem;word-break:break-all">
<?php echo esc_html( mb_strtolower( preg_replace('#^https?://#i','',$val) ) ); ?>
</a>
<?php elseif ( $def['type'] === 'textarea' ): ?>
<p style="font-size:.85rem"><?php echo nl2br(esc_html($val)); ?></p>
<?php else: ?>
<p style="font-size:.85rem"><?php echo esc_html($val); ?></p>
<?php endif; ?>
</div>
<?php endforeach; ?>
</aside>
<!-- ── MAIN ────────────────────────────────────────────── -->
<div class="wbf-profile-main">
<!-- Profil bearbeiten (nur eigenes) -->
<?php if ($is_own): ?>
<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-profile-edit-grid">
<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>Neues Passwort <small>(leer = nicht ändern)</small></label>
<input type="password" id="wbfNewPassword" placeholder="••••••">
</div>
</div>
<div class="wbf-form-row">
<label>Bio</label>
<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>
<div class="wbf-form-row" style="display:flex;align-items:center;gap:.75rem;margin-bottom:.75rem">
<label style="font-size:.82rem;color:var(--c-muted)">Profil öffentlich sichtbar</label>
<?php $pub = (int)($profile->profile_public ?? 1); ?>
<button type="button" id="wbfToggleProfileVis"
class="wbf-btn wbf-btn--sm<?php echo $pub?' wbf-btn--primary':''; ?>"
data-state="<?php echo $pub; ?>">
<i class="fas fa-<?php echo $pub?'eye':'eye-slash'; ?>"></i>
<?php echo $pub?'Öffentlich':'Privat'; ?>
</button>
</div>
<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 class="wbf-profile-card__footer">
<button class="wbf-btn wbf-btn--primary" id="wbfSaveProfile">
<i class="fas fa-save"></i> Speichern
</button>
<span class="wbf-msg" id="wbfProfileMsg"></span>
</div>
</div>
</div>
<!-- ── Benutzerdefinierte Profilfelder ──────────────── -->
<?php
$cf_defs = WBF_DB::get_profile_field_defs();
$cf_vals = WBF_DB::get_user_meta( $profile->id );
if ( ! empty( $cf_defs ) ):
?>
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-sliders"></i> Weitere Profilangaben
</div>
<div class="wbf-profile-card__body">
<div class="wbf-profile-edit-grid">
<?php foreach ( $cf_defs 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 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 class="wbf-profile-card__footer">
<button class="wbf-btn wbf-btn--primary" id="wbfSaveProfileCf">
<i class="fas fa-save"></i> Speichern
</button>
<span class="wbf-msg" id="wbfProfileCfMsg"></span>
</div>
</div>
</div>
<?php endif; ?>
<!-- ── DSGVO: 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 $is_own */ ?>
<!-- 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>
<!-- Lesezeichen (nur eigenes Profil) -->
<?php if ($is_own):
$bookmarks = WBF_DB::get_user_bookmarks($current->id, 50); ?>
<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; ?>
</div><!-- /.wbf-profile-main -->
</div><!-- /.wbf-profile-layout -->
</div>
</div>
<?php return ob_get_clean();
}
// ── TAG PAGE ─────────────────────────────────────────────────────────────
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; ?>
</div>
<?php self::render_forum_footer(); ?>
<?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>
</div>
<?php self::render_forum_footer(); ?>
<?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) : [];
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; ?>
</div>
<?php self::render_forum_footer(); ?>
<?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(wbf_get_forum_url() . '?wbf_do_logout=1'); ?>" 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; ?>
<?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; ?>
</div>
<?php self::render_forum_footer(); ?>
<?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-top:1px solid var(--c-border);margin-top:3rem;padding:1.25rem 1.5rem;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.75rem;background:var(--c-bg2)">
<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();