' . (int) $count . ''; break; } } }, 999 ); // ── Wartungsmodus-Toggle via admin_init (läuft vor WordPress-Output/Headern) ── add_action( 'admin_init', function() { if ( ! isset( $_POST['wbf_dash_toggle_maint'] ) ) return; if ( ! check_admin_referer( 'wbf_dash_maint_nonce' ) ) return; if ( ! current_user_can( 'manage_options' ) ) return; $s = wbf_get_settings(); $s['maintenance_mode'] = ( ( $s['maintenance_mode'] ?? '0' ) === '1' ) ? '0' : '1'; update_option( 'wbf_settings', $s ); wp_safe_redirect( add_query_arg( 'page', 'wbf-admin', admin_url( 'admin.php' ) ) ); exit; } ); // ── Export via admin_init (läuft vor WordPress-Output/Headern) ──────────────── add_action( 'admin_init', function() { if ( ! isset( $_POST['wbf_do_export'] ) ) return; if ( ! check_admin_referer( 'wbf_export_nonce' ) ) return; if ( ! current_user_can( 'manage_options' ) ) return; $sections = array_keys( array_filter( $_POST['export_sections'] ?? [] ) ); $data = [ '_meta' => [ 'plugin' => 'WP Business Forum', 'version' => WBF_VERSION, 'exported' => date('c'), 'site' => get_bloginfo('url'), 'sections' => $sections, ], ]; global $wpdb; foreach ( $sections as $sec ) { switch ( $sec ) { case 'settings': $data['settings'] = get_option('wbf_settings', []); $data['profile_fields'] = get_option('wbf_profile_fields', []); $data['reactions_cfg'] = get_option('wbf_reactions', []); break; case 'roles': $data['roles'] = get_option('wbf_custom_roles', []); break; case 'levels': $data['levels'] = [ 'config' => get_option('wbf_level_config', []), 'enabled' => get_option('wbf_levels_enabled', true), ]; break; case 'categories': $data['categories'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_categories ORDER BY parent_id ASC, sort_order ASC", ARRAY_A ); break; case 'users': $data['users'] = $wpdb->get_results( "SELECT id, username, email, password, display_name, avatar_url, bio, signature, role, pre_ban_role, ban_reason, ban_until, post_count, registered, last_active, profile_public FROM {$wpdb->prefix}forum_users ORDER BY id ASC", ARRAY_A ); $data['user_meta'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_user_meta ORDER BY user_id ASC", ARRAY_A ); break; case 'threads': $data['threads'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_threads ORDER BY id ASC", ARRAY_A ); $data['posts'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_posts ORDER BY id ASC", ARRAY_A ); $data['thread_tags'] = $wpdb->get_results( "SELECT tt.*, t.name, t.slug, t.use_count FROM {$wpdb->prefix}forum_thread_tags tt JOIN {$wpdb->prefix}forum_tags t ON t.id = tt.tag_id ORDER BY tt.thread_id ASC", ARRAY_A ); $data['subscriptions'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_subscriptions ORDER BY id ASC", ARRAY_A ); break; case 'polls': $data['polls'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_polls ORDER BY id ASC", ARRAY_A ); $data['poll_votes'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_poll_votes ORDER BY id ASC", ARRAY_A ); break; case 'bookmarks': $data['bookmarks'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_bookmarks ORDER BY id ASC", ARRAY_A ); break; case 'prefixes': $data['prefixes'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_prefixes ORDER BY sort_order ASC", ARRAY_A ); break; case 'interactions': $data['likes'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_likes ORDER BY id ASC", ARRAY_A ); $data['reactions'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_reactions ORDER BY id ASC", ARRAY_A ); $data['notifications'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_notifications ORDER BY id ASC", ARRAY_A ); break; case 'messages': $data['messages'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_messages ORDER BY id ASC", ARRAY_A ); break; case 'reports': $data['reports'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_reports ORDER BY id ASC", ARRAY_A ); break; case 'invites': $data['invites'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_invites ORDER BY id ASC", ARRAY_A ); break; case 'ignore_list': $data['ignore_list'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_ignore_list ORDER BY id ASC", ARRAY_A ); break; } } $json = wp_json_encode( $data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE ); $filename = 'wbf-backup-' . date('Y-m-d-His') . '.json'; header( 'Content-Type: application/json; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); header( 'Content-Length: ' . strlen( $json ) ); header( 'Cache-Control: no-cache, no-store, must-revalidate' ); echo $json; exit; } ); // ── Inline Styles ───────────────────────────────────────────────────────────── add_action( 'admin_head', function() { if ( ! isset( $_GET['page'] ) || strpos( $_GET['page'], 'wbf-' ) === false ) return; echo ''; } ); // ── Dashboard ───────────────────────────────────────────────────────────────── function wbf_admin_page() { global $wpdb; $stats = WBF_DB::get_stats(); $roles = WBF_Roles::get_sorted(); $open_reports = WBF_DB::count_open_reports(); $recent = WBF_DB::get_recent_threads(8); $members = WBF_DB::get_all_users(1); $admin_url = admin_url('admin.php'); $maint_active = ( wbf_get_settings()['maintenance_mode'] ?? '0' ) === '1'; $furl = wbf_get_forum_url(); // ── Counts ─────────────────────────────────────────────────────────────── $tag_count = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_tags"); $deleted_count = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_threads WHERE deleted_at IS NOT NULL") + (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_posts WHERE deleted_at IS NOT NULL"); $sub_count = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_subscriptions"); $online_count = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_users WHERE last_active >= DATE_SUB(NOW(), INTERVAL 15 MINUTE)"); $invite_count = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_invites WHERE use_count < max_uses AND (expires_at IS NULL OR expires_at > NOW())"); $banned_count = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_users WHERE role='banned'"); $update_info = wbf_update_available(); // null oder array mit Versionsdaten // ── System ──────────────────────────────────────────────────────────────── $php_ver = PHP_VERSION; $php_rec = version_compare($php_ver,'8.0','>='); $php_ok = version_compare($php_ver,'7.4','>='); $mail_ok = function_exists('wp_mail'); $mysql_ver = $wpdb->get_var('SELECT VERSION()') ?: '?'; $exp_tables= ['forum_users','forum_categories','forum_threads','forum_posts','forum_likes','forum_reports','forum_tags','forum_thread_tags','forum_messages','forum_reactions','forum_remember_tokens','forum_subscriptions','forum_notifications']; $existing = $wpdb->get_col("SHOW TABLES LIKE '{$wpdb->prefix}forum_%'"); $missing = array_filter($exp_tables, fn($t) => !in_array($wpdb->prefix.$t, $existing)); // ── MC Bridge StatusAPI ─────────────────────────────────────────────────── $mc_s = wbf_get_settings(); $mc_enabled = ! empty( $mc_s['mc_bridge_enabled'] ); $mc_api_url = trim( $mc_s['mc_bridge_api_url'] ?? '' ); // ── Trends ──────────────────────────────────────────────────────────────── $pt = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_posts WHERE deleted_at IS NULL AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)"); $pl = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_posts WHERE deleted_at IS NULL AND created_at BETWEEN DATE_SUB(NOW(), INTERVAL 14 DAY) AND DATE_SUB(NOW(), INTERVAL 7 DAY)"); $tht = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_threads WHERE deleted_at IS NULL AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)"); $thl = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_threads WHERE deleted_at IS NULL AND created_at BETWEEN DATE_SUB(NOW(), INTERVAL 14 DAY) AND DATE_SUB(NOW(), INTERVAL 7 DAY)"); $ut = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_users WHERE registered >= DATE_SUB(NOW(), INTERVAL 7 DAY)"); $ul = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_users WHERE registered BETWEEN DATE_SUB(NOW(), INTERVAL 14 DAY) AND DATE_SUB(NOW(), INTERVAL 7 DAY)"); $tr = function($now,$prev) { if ($prev==0) return $now>0 ? ['+∞','wbf-tu','fa-arrow-trend-up'] : ['—','wbf-tf','fa-minus']; $p=round(($now-$prev)/$prev*100); if ($p>0) return ['+'.$p.'%','wbf-tu','fa-arrow-trend-up']; if ($p<0) return [$p.'%','wbf-td','fa-arrow-trend-down']; return ['±0%','wbf-tf','fa-minus']; }; [$pp,$pc,$pi] = $tr($pt,$pl); [$tp,$tc,$ti] = $tr($tht,$thl); [$up,$uc,$ui] = $tr($ut,$ul); // ── Top cats ────────────────────────────────────────────────────────────── $top_cats = $wpdb->get_results("SELECT c.name,c.icon,COUNT(t.id) AS cnt FROM {$wpdb->prefix}forum_categories c LEFT JOIN {$wpdb->prefix}forum_threads t ON t.category_id=c.id AND t.deleted_at IS NULL GROUP BY c.id ORDER BY cnt DESC LIMIT 5"); $max_cnt = $top_cats ? max(1,max(array_map(fn($c)=>(int)$c->cnt,$top_cats))) : 1; // ── Banned ──────────────────────────────────────────────────────────────── $banned_users = $wpdb->get_results("SELECT display_name,avatar_url,ban_reason,ban_until FROM {$wpdb->prefix}forum_users WHERE role='banned' ORDER BY id DESC LIMIT 5"); // ── Activity ────────────────────────────────────────────────────────────── $activity = $wpdb->get_results(" (SELECT 'post' AS type,p.id AS oid,u.display_name,SUBSTRING(p.content,1,70) AS sub,p.created_at,u.avatar_url FROM {$wpdb->prefix}forum_posts p JOIN {$wpdb->prefix}forum_users u ON u.id=p.user_id WHERE p.deleted_at IS NULL ORDER BY p.created_at DESC LIMIT 6) UNION ALL (SELECT 'thread',t.id,u.display_name,t.title,t.created_at,u.avatar_url FROM {$wpdb->prefix}forum_threads t JOIN {$wpdb->prefix}forum_users u ON u.id=t.user_id WHERE t.deleted_at IS NULL ORDER BY t.created_at DESC LIMIT 5) UNION ALL (SELECT 'register',fu.id,fu.display_name,fu.username,fu.registered,fu.avatar_url FROM {$wpdb->prefix}forum_users fu ORDER BY fu.registered DESC LIMIT 4) UNION ALL (SELECT 'report',r.id,u.display_name,r.reason,r.created_at,u.avatar_url FROM {$wpdb->prefix}forum_reports r JOIN {$wpdb->prefix}forum_users u ON u.id=r.reporter_id WHERE r.status='open' ORDER BY r.created_at DESC LIMIT 3) ORDER BY created_at DESC LIMIT 15 "); // ── Open reports ────────────────────────────────────────────────────────── $reports_preview = $wpdb->get_results(" SELECT r.*,u.display_name AS rname,p.content AS pc,t.title AS tt,t.id AS tid FROM {$wpdb->prefix}forum_reports r LEFT JOIN {$wpdb->prefix}forum_users u ON u.id=r.reporter_id LEFT JOIN {$wpdb->prefix}forum_posts p ON p.id=r.object_id AND r.object_type='post' LEFT JOIN {$wpdb->prefix}forum_threads t ON t.id=p.thread_id WHERE r.status='open' ORDER BY r.created_at DESC LIMIT 6 "); $rlabels = ['spam'=>'Spam','harassment'=>'Belästigung','inappropriate'=>'Unangemessen','misinformation'=>'Fehlinformation','off-topic'=>'Offtopic','other'=>'Sonstiges']; $acfg = ['post'=>['fas fa-comment-dots','post','antwortete'],'thread'=>['fas fa-comments','thread','erstellte Thread:'],'register'=>['fas fa-user-plus','register','registrierte sich'],'report'=>['fas fa-flag','report','meldete Beitrag:']]; $nonce_url = fn($action,$id) => esc_url(wp_nonce_url(add_query_arg(['page'=>'wbf-reports','report_id'=>$id,'report_action'=>$action,'filter'=>'open'],admin_url('admin.php')),'wbf_report_action')); $ago = function($t) { $d=time()-strtotime($t); if($d<60) return 'jetzt'; if($d<3600) return (int)($d/60).' Min.'; if($d<86400)return (int)($d/3600).' Std.'; return date_i18n('d.m.',$d<604800?time()-$d:strtotime($t)); }; ?>
WP Business Forum
Admin Dashboard · v ·
Wartungsmodus aktiv Forum online Einstellungen Forum öffnen
WP Business Forum v
M_Viper'], ['fas fa-calendar', 'Update', esc_html(file_exists($pf)?date_i18n('d.m.Y',filemtime($pf)):'—')], ['fas fa-code-branch','Repo', 'git.viper.ipv64.net'], ['fas fa-layer-group','Shortcode','[business_forum]'], ] as [$ico,$lbl,$val]):?>
:
System PHP MySQL WP
wp_mail
Galerie: Shop:
Tabellen OK Fehlende Tabellen:
MC Bridge: Aus StatusAPI: Nicht konfiguriert StatusAPI: Prüfe... gerade online
Navigation
Live-Aktivität letzte 15 Aktionen

Noch keine Aktivität.

    type] ?? $acfg['post']; $t = $ago($ev->created_at); ?>
  • '💬','thread'=>'📝','register'=>'✨','report'=>'🚩']; echo $dot_emoji[$ev->type] ?? '💬'; ?>
    display_name);?> sub),0,45));?>
Letzte Threads

Keine Threads.

$role): $rc=esc_attr($role['color']); $rb=esc_attr($role['bg_color']);?>
Wartungsmodus

Admins haben immer Zugriff. Einstellungen →

Neue Mitglieder Alle →

Keine Mitglieder.

    role); $mc=esc_attr($mr['color']); $mb=esc_attr($mr['bg_color']);?>
  • post_count;?> Beitr.
Wachstum 7 Tage
Vorwoche:
Top-Kategorien Alle →

Keine Kategorien.

    $cat): $bpct=$max_cnt>0?round($cat->cnt/$max_cnt*100):0; $rc=$rk[$i]??'#94a3b8';?>
  • name,0,20));?> cnt;?>
Gesperrt 0):?> Liste →
Keine gesperrten Nutzer
display_name);?>
ban_reason):?>
ban_reason);?>
ban_until)): $rem = strtotime($bu->ban_until) - time(); $days_r = floor($rem/86400); $hrs_r = floor(($rem%86400)/3600); $rem_str = $rem > 0 ? '⏱ ' . ($days_r>0?$days_r.'T ':''). $hrs_r.'h verbl.' : 'läuft ab'; ?>
· ban_until)));?>
Permanent
5):?>
… und weitere
Offene Meldungen 0):?> 6):?>Alle →
Keine offenen Meldungen
tid ? esc_url(wbf_get_forum_url().'?forum_thread='.(int)$rp->tid.'#post-'.(int)$rp->object_id) : ''; ?>
rname??'?');?> reason]??$rp->reason);?> created_at));?>
pc):?>
"pc),0,80));?>"
$label ) { if ( ! empty( $_POST['perm_' . $p] ) ) $perms[] = $p; } WBF_Roles::save( $key, [ 'label' => sanitize_text_field( $_POST['role_label'] ), 'level' => max( -1, min( 99, (int) $_POST['role_level'] ) ), 'color' => sanitize_hex_color( $_POST['role_color'] ) ?: '#94a3b8', 'bg_color' => sanitize_text_field( $_POST['role_bg'] ) ?: 'rgba(148,163,184,.1)', 'icon' => sanitize_text_field( $_POST['role_icon'] ) ?: 'fas fa-user', 'permissions' => $perms, 'locked' => false, 'description' => sanitize_textarea_field( $_POST['role_desc'] ), ] ); echo '

Rolle gespeichert!

'; } } if ( isset( $_POST['wbf_create_role'] ) && check_admin_referer( 'wbf_role_create_nonce' ) ) { $new_key = sanitize_key( $_POST['new_role_key'] ?? '' ); if ( $new_key && $new_key !== WBF_Roles::SUPERADMIN ) { $perms = []; foreach ( $all_perms as $p => $label ) { if ( ! empty( $_POST['new_perm_' . $p] ) ) $perms[] = $p; } WBF_Roles::save( $new_key, [ 'label' => sanitize_text_field( $_POST['new_role_label'] ?: ucfirst( $new_key ) ), 'level' => max( 1, min( 79, (int) ( $_POST['new_role_level'] ?? 10 ) ) ), 'color' => sanitize_hex_color( $_POST['new_role_color'] ) ?: '#94a3b8', 'bg_color' => 'rgba(148,163,184,.1)', 'icon' => sanitize_text_field( $_POST['new_role_icon'] ) ?: 'fas fa-user', 'permissions' => $perms, 'locked' => false, 'description' => sanitize_textarea_field( $_POST['new_role_desc'] ), ] ); echo '

Neue Rolle erstellt!

'; } } if ( isset( $_GET['delete_role'] ) && check_admin_referer( 'delete_role_' . $_GET['delete_role'] ) ) { $del = sanitize_key( $_GET['delete_role'] ); if ( WBF_Roles::delete( $del ) ) { echo '

Rolle gelöscht. Nutzer auf "member" gesetzt.

'; } else { echo '

Diese Rolle kann nicht gelöscht werden.

'; } } $roles = WBF_Roles::get_sorted(); $edit_key = isset( $_GET['edit_role'] ) ? sanitize_key( $_GET['edit_role'] ) : null; $edit_role = $edit_key ? WBF_Roles::get( $edit_key ) : null; echo '

Rollen-Verwaltung + Neue Rolle

'; echo ''; foreach ( $roles as $key => $role ) { $color = esc_attr( $role['color'] ); $bg = esc_attr( $role['bg_color'] ); $badge = " " . esc_html( $role['label'] ) . ""; $perms = implode( ', ', array_map( fn($p) => esc_html( $all_perms[$p] ?? $p ), $role['permissions'] ?? [] ) ); if ( in_array( 'all', $role['permissions'] ?? [] ) ) $perms = 'Alle Rechte'; $actions = ''; if ( $key !== WBF_Roles::SUPERADMIN ) { $actions .= "Bearbeiten"; if ( ! in_array( $key, ['member'] ) ) { $del_url = wp_nonce_url( "?page=wbf-roles&delete_role={$key}", "delete_role_{$key}" ); $actions .= " | Löschen"; } } else { $actions = 'Systemrolle — unveränderlich'; } echo ""; } echo '
RolleLevelPermissionsBeschreibungAktionen
$badge " . ( ( $role['locked'] ?? false ) ? '(🔒)' : '' ) . " " . esc_html( $role['level'] ) . " $perms " . esc_html( $role['description'] ?? '' ) . " $actions
'; if ( $edit_role && $edit_key !== WBF_Roles::SUPERADMIN ) { echo '

Bearbeiten: ' . esc_html( $edit_role['label'] ) . '

'; echo '
'; wp_nonce_field( 'wbf_role_edit_nonce' ); echo ''; wbf_role_form_fields( $edit_role, $all_perms, '' ); echo '
'; submit_button( 'Änderungen speichern', 'primary', 'wbf_save_role' ); echo '

'; } echo '

Neue Rolle erstellen

'; wp_nonce_field( 'wbf_role_create_nonce' ); echo ''; wbf_role_form_fields( null, $all_perms, 'new_' ); echo '
Rollen-Schlüssel *

Nur Kleinbuchstaben, Zahlen, Unterstriche. Unveränderlich.

'; submit_button( 'Rolle erstellen', 'primary', 'wbf_create_role' ); echo '
'; } function wbf_role_form_fields( $role, $all_perms, $prefix ) { $v = fn($k, $d = '') => esc_attr( $role[$k] ?? $d ); $perms = $role['permissions'] ?? []; echo " Anzeigename * Level

Superadmin=100, Admin≤80, Mod≤50, Member=10, Banned=-1

Farbe Icon

FontAwesome 6, z.B. fas fa-crown

Beschreibung Permissions
"; foreach ( $all_perms as $p => $label ) { $checked = in_array( $p, $perms ) ? 'checked' : ''; echo ""; } echo "
"; } // ── Kategorien ──────────────────────────────────────────────────────────────── function wbf_admin_categories() { global $wpdb; $roles = WBF_Roles::get_sorted(); // ── Reihenfolge verschieben ────────────────────────────────────────────── if ( isset( $_POST['wbf_reorder_cat'] ) && check_admin_referer( 'wbf_reorder_nonce' ) ) { $move_id = (int) $_POST['cat_id']; $dir = ( $_POST['direction'] ?? '' ) === 'up' ? 'up' : 'down'; $parent = (int) $_POST['parent_id']; $siblings = $wpdb->get_results( $wpdb->prepare( "SELECT id, sort_order FROM {$wpdb->prefix}forum_categories WHERE parent_id=%d ORDER BY sort_order ASC, id ASC", $parent ) ); $ids = array_column( (array) $siblings, 'id' ); $pos = array_search( $move_id, $ids ); if ( $dir === 'up' && $pos > 0 ) { $swap_id = $ids[ $pos - 1 ]; } elseif ( $dir === 'down' && $pos !== false && $pos < count($ids) - 1 ) { $swap_id = $ids[ $pos + 1 ]; } if ( isset( $swap_id ) ) { $so_a = $siblings[$pos]->sort_order; $so_b = $siblings[ $dir === 'up' ? $pos - 1 : $pos + 1 ]->sort_order; if ( $so_a === $so_b ) { $so_b = $dir === 'up' ? $so_a - 1 : $so_a + 1; } $wpdb->update( "{$wpdb->prefix}forum_categories", ['sort_order' => $so_b], ['id' => $move_id] ); $wpdb->update( "{$wpdb->prefix}forum_categories", ['sort_order' => $so_a], ['id' => $swap_id] ); } // Gleiche Seite neu laden (kein redirect_to nötig) $tree = WBF_DB::get_categories_tree(); } if ( isset( $_POST['wbf_save_cat'] ) && check_admin_referer( 'wbf_cat_nonce' ) ) { $data = [ 'parent_id' => (int) ( $_POST['parent_id'] ?? 0 ), 'name' => sanitize_text_field( $_POST['name'] ), 'slug' => sanitize_title( $_POST['name'] ), 'description' => sanitize_textarea_field( $_POST['description'] ), 'icon' => sanitize_text_field( $_POST['icon'] ), 'sort_order' => (int) ( $_POST['sort_order'] ?? 0 ), 'min_role' => sanitize_key( $_POST['min_role'] ?? 'member' ), 'guest_visible' => isset($_POST['guest_visible']) ? 1 : 0, ]; if ( ! empty( $_POST['cat_id'] ) ) { $wpdb->update( "{$wpdb->prefix}forum_categories", $data, ['id' => (int) $_POST['cat_id']] ); echo '

Gespeichert!

'; } else { $wpdb->insert( "{$wpdb->prefix}forum_categories", $data ); echo '

Erstellt!

'; } } if ( isset( $_GET['delete_cat'] ) && current_user_can( 'manage_options' ) ) { check_admin_referer( 'delete_cat_' . (int) $_GET['delete_cat'] ); $wpdb->delete( "{$wpdb->prefix}forum_categories", ['id' => (int) $_GET['delete_cat']] ); } $tree = WBF_DB::get_categories_tree(); $all = WBF_DB::get_categories_flat(); $edit = isset( $_GET['edit'] ) ? WBF_DB::get_category( (int) $_GET['edit'] ) : null; echo '

Kategorien + Neue Kategorie

'; echo ''; foreach ( $tree as $p ) { $d = wp_nonce_url( "?page=wbf-categories&delete_cat={$p->id}", "delete_cat_{$p->id}" ); $role = WBF_Roles::get( $p->min_role ?? 'member' ); $lockbadge = ( $p->min_role ?? 'member' ) !== 'member' ? " 🔒 " . esc_html( $role['label'] ) . "" : ''; $guestbadge = ( (int)($p->guest_visible ?? 1) === 0 ) ? " 👁 Nur eingeloggt" : ''; $reorder_p = '' . wp_nonce_field( 'wbf_reorder_nonce', '_wpnonce', true, false ) . '' . '' . '' . '' . '' . '' . wp_nonce_field( 'wbf_reorder_nonce', '_wpnonce', true, false ) . '' . '' . '' . '' . ''; echo ""; foreach ( $p->children as $c ) { $cd = wp_nonce_url( "?page=wbf-categories&delete_cat={$c->id}", "delete_cat_{$c->id}" ); $crole = WBF_Roles::get( $c->min_role ?? 'member' ); $clbadge = ( $c->min_role ?? 'member' ) !== 'member' ? " 🔒 " . esc_html( $crole['label'] ) . "" : ''; $cguestbadge = ( (int)($c->guest_visible ?? 1) === 0 ) ? " 👁 Nur eingeloggt" : ''; $reorder_c = '' . wp_nonce_field( 'wbf_reorder_nonce', '_wpnonce', true, false ) . '' . '' . '' . '' . '' . '' . wp_nonce_field( 'wbf_reorder_nonce', '_wpnonce', true, false ) . '' . '' . '' . '' . ''; echo ""; } } echo '
KategorieThreadsMin. RolleAktionen
" . esc_html( $p->name ) . "$lockbadge " . esc_html( $p->thread_count ) . " " . esc_html( $role['label'] ) . " {$reorder_p} Bearbeiten | Löschen
↳ " . esc_html( $c->name ) . " " . esc_html( $c->thread_count ) . " " . esc_html( $crole['label'] ) . "$clbadge$cguestbadge {$reorder_c} Bearbeiten | Löschen
'; $parent_opts = ''; foreach ( $all as $c ) { if ( $edit && $c->id == $edit->id ) continue; $sel = ( $edit && (int) $edit->parent_id === (int) $c->id ) ? ' selected' : ''; $indent = (int) $c->parent_id > 0 ? '  ↳ ' : ''; $parent_opts .= ""; } $role_opts = ''; foreach ( $roles as $k => $r ) { if ( $k === 'banned' || $k === WBF_Roles::SUPERADMIN ) continue; $sel = ( $edit && ( $edit->min_role ?? 'member' ) === $k ) ? ' selected' : ''; $role_opts .= ""; } echo '

' . ( $edit ? 'Bearbeiten: ' . esc_html( $edit->name ) : 'Neue Kategorie' ) . '

'; echo '
'; wp_nonce_field( 'wbf_cat_nonce' ); if ( $edit ) echo ''; echo ' '; echo '
Elternkategorie
Name *
Beschreibung
Icon

z.B. fas fa-home, fas fa-bug

Min. Rolle

Mindest-Rolle zum Posten in dieser Kategorie.

Für Gäste sichtbar

Deaktivieren = Kategorie und ihre Threads sind nur für eingeloggte Nutzer sichtbar.

Reihenfolge
'; submit_button( $edit ? 'Speichern' : 'Erstellen', 'primary', 'wbf_save_cat' ); echo '
'; } // ── Mitglieder ──────────────────────────────────────────────────────────────── function wbf_admin_members() { $roles = WBF_Roles::get_sorted(); // ── Neuen Nutzer anlegen ────────────────────────────────────────────────── if ( isset( $_POST['wbf_create_user'] ) && check_admin_referer( 'wbf_create_user_nonce' ) ) { $username = sanitize_user( $_POST['username'] ?? '' ); $email = sanitize_email( $_POST['email'] ?? '' ); $display_name = sanitize_text_field( $_POST['display_name'] ?? '' ); $password = $_POST['password'] ?? ''; $role = sanitize_key( $_POST['role'] ?? 'member' ); $errors = []; if ( strlen($username) < 3 ) $errors[] = 'Benutzername mindestens 3 Zeichen.'; if ( ! is_email($email) ) $errors[] = 'Ungültige E-Mail-Adresse.'; if ( strlen($password) < 6 ) $errors[] = 'Passwort mindestens 6 Zeichen.'; if ( empty($display_name) ) $errors[] = 'Anzeigename darf nicht leer sein.'; if ( WBF_DB::get_user_by('username', $username) ) $errors[] = 'Benutzername bereits vergeben.'; if ( WBF_DB::get_user_by('email', $email) ) $errors[] = 'E-Mail bereits registriert.'; if ( $role === WBF_Roles::SUPERADMIN ) $errors[] = 'Superadmin kann nicht manuell vergeben werden.'; if ( empty($errors) ) { $avatar = 'https://www.gravatar.com/avatar/' . md5(strtolower($email)) . '?d=identicon&s=80'; $new_id = WBF_DB::create_user([ 'username' => $username, 'email' => $email, 'password' => password_hash($password, PASSWORD_DEFAULT), 'display_name' => $display_name, 'avatar_url' => $avatar, 'role' => $role, ]); echo '

✅ Nutzer ' . esc_html($display_name) . ' wurde angelegt (ID: ' . (int)$new_id . ').

'; } else { echo '

❌ ' . implode('
', array_map('esc_html', $errors)) . '

'; } } // ── Rolle ändern ────────────────────────────────────────────────────────── if ( isset( $_POST['wbf_change_role'] ) && check_admin_referer( 'wbf_member_role_nonce' ) ) { $uid = (int) $_POST['user_id']; $role = sanitize_key( $_POST['new_role'] ); if ( $uid && $role !== WBF_Roles::SUPERADMIN && array_key_exists( $role, WBF_Roles::get_all() ) ) { $update = ['role' => $role]; if ( $role === 'banned' ) { $update['ban_reason'] = sanitize_textarea_field( $_POST['ban_reason'] ?? '' ); // Zeitlich begrenzte Sperre $ban_until_raw = sanitize_text_field( $_POST['ban_until'] ?? '' ); if ( $ban_until_raw ) { $ts = strtotime( $ban_until_raw ); if ( $ts && $ts > time() ) { // temp_ban speichert pre_ban_role automatisch WBF_DB::temp_ban( $uid, date('Y-m-d H:i:s', $ts), $update['ban_reason'] ); echo '

✅ Nutzer temporär gesperrt bis ' . esc_html(date_i18n('d.m.Y H:i', $ts)) . '.

'; goto after_role_save; } } // Permanente Sperre — pre_ban_role sichern $existing = WBF_DB::get_user( $uid ); if ( $existing && $existing->role !== 'banned' ) { $update['pre_ban_role'] = $existing->role; } $update['ban_until'] = null; } else { $update['ban_reason'] = ''; $update['ban_until'] = null; $update['pre_ban_role'] = ''; } WBF_DB::update_user( $uid, $update ); echo '

Rolle aktualisiert!

'; after_role_save:; } } // ── Nutzer löschen (DSGVO Art. 17 / Hard Delete) ───────────────────────── if ( isset( $_POST['wbf_delete_member'] ) && check_admin_referer( 'wbf_delete_member_nonce' ) ) { $uid = (int) ( $_POST['user_id'] ?? 0 ); $mode = sanitize_key( $_POST['delete_mode'] ?? 'anonymize' ); if ( $uid ) { $target = WBF_DB::get_user( $uid ); if ( ! $target ) { echo '

❌ Nutzer nicht gefunden.

'; } elseif ( $target->role === WBF_Roles::SUPERADMIN ) { echo '

❌ Der Superadmin kann nicht gelöscht werden.

'; } else { global $wpdb; $name = esc_html( $target->display_name ); if ( $mode === 'hard' ) { // Alle nutzerbezogenen Daten entfernen $dep_tables = [ 'forum_messages' => [ 'from_id', 'to_id' ], 'forum_notifications' => [ 'user_id', 'actor_id' ], 'forum_subscriptions' => [ 'user_id' ], 'forum_bookmarks' => [ 'user_id' ], 'forum_likes' => [ 'user_id' ], 'forum_reactions' => [ 'user_id' ], 'forum_reports' => [ 'reporter_id' ], 'forum_poll_votes' => [ 'user_id' ], 'forum_remember_tokens' => [ 'user_id' ], 'forum_user_meta' => [ 'user_id' ], 'forum_ignore_list' => [ 'user_id', 'ignored_id' ], ]; foreach ( $dep_tables as $tbl => $cols ) { $full = $wpdb->prefix . $tbl; if ( $wpdb->get_var( "SHOW TABLES LIKE '$full'" ) !== $full ) continue; foreach ( $cols as $col ) { $wpdb->delete( $full, [ $col => $uid ], [ '%d' ] ); } } // Threads/Posts auf user_id=0 setzen (Inhalt bleibt) $wpdb->update( "{$wpdb->prefix}forum_threads", [ 'user_id' => 0 ], [ 'user_id' => $uid ], [ '%d' ], [ '%d' ] ); $wpdb->update( "{$wpdb->prefix}forum_posts", [ 'user_id' => 0 ], [ 'user_id' => $uid ], [ '%d' ], [ '%d' ] ); // Nutzer-Datensatz hart loeschen $wpdb->delete( "{$wpdb->prefix}forum_users", [ 'id' => $uid ], [ '%d' ] ); echo "

🗑 Nutzer {$name} dauerhaft geloescht. Threads/Posts anonym gesetzt.

"; } else { // DSGVO Art. 17 Anonymisierung (Standard) $ok = WBF_DB::delete_user_gdpr( $uid ); if ( $ok ) { echo "

✅ Nutzer {$name} anonymisiert (DSGVO Art. 17). Threads und Beitraege bleiben erhalten.

"; } else { echo "

❌ Anonymisierung fehlgeschlagen.

"; } } delete_transient( 'wbf_flood_' . $uid ); delete_transient( 'wbf_flood_ts_' . $uid ); } } } // ── Profil bearbeiten ───────────────────────────────────────────────────── if ( isset( $_POST['wbf_edit_user'] ) && check_admin_referer( 'wbf_edit_user_nonce' ) ) { $uid = (int) $_POST['user_id']; $display_name = sanitize_text_field( $_POST['display_name'] ?? '' ); $email = sanitize_email( $_POST['email'] ?? '' ); $new_password = $_POST['new_password'] ?? ''; if ( $uid && $uid !== 0 ) { $user = WBF_DB::get_user( $uid ); if ( $user && $user->role !== WBF_Roles::SUPERADMIN ) { $update = []; if ( ! empty($display_name) ) $update['display_name'] = $display_name; if ( is_email($email) ) $update['email'] = $email; if ( strlen($new_password) >= 6 ) $update['password'] = password_hash( $new_password, PASSWORD_DEFAULT ); // Bio + Signatur if ( isset($_POST['bio']) ) $update['bio'] = sanitize_textarea_field( $_POST['bio'] ); if ( isset($_POST['signature']) ) $update['signature'] = mb_substr( sanitize_textarea_field( $_POST['signature'] ), 0, 300 ); if ( ! empty($update) ) WBF_DB::update_user( $uid, $update ); // Benutzerdefinierte Profilfelder speichern $cf_defs = WBF_DB::get_profile_field_defs(); foreach ( $cf_defs as $def ) { $key = sanitize_key( $def['key'] ); if ( ! isset($_POST[ 'cf_' . $key ]) ) continue; $raw = $_POST[ 'cf_' . $key ]; $value = $def['type'] === 'url' ? esc_url_raw(trim($raw)) : ($def['type'] === 'textarea' ? sanitize_textarea_field($raw) : ($def['type'] === 'number' ? (is_numeric($raw) ? (string)(float)$raw : '') : sanitize_text_field($raw))); WBF_DB::set_user_meta( $uid, $key, $value ); } echo '

Profil von ' . esc_html($user->display_name) . ' aktualisiert!

'; } } } // ── Admin: einzelnen Ignore-Eintrag entfernen ───────────────────────────── if ( isset( $_POST['wbf_admin_remove_ignore'] ) && check_admin_referer( 'wbf_admin_ignore_nonce' ) ) { if ( current_user_can('manage_options') ) { $uid = (int) ( $_POST['user_id'] ?? 0 ); $ignored_id = (int) ( $_POST['ignored_id'] ?? 0 ); if ( $uid && $ignored_id ) { global $wpdb; $wpdb->delete( "{$wpdb->prefix}forum_ignore_list", [ 'user_id' => $uid, 'ignored_id' => $ignored_id ], [ '%d', '%d' ] ); echo '

Ignore-Eintrag entfernt.

'; } } } // ── Admin: gesamte Ignore-Liste eines Users leeren ──────────────────────── if ( isset( $_POST['wbf_admin_clear_ignores'] ) && check_admin_referer( 'wbf_admin_ignore_nonce' ) ) { if ( current_user_can('manage_options') ) { $uid = (int) ( $_POST['user_id'] ?? 0 ); if ( $uid ) { global $wpdb; $wpdb->delete( "{$wpdb->prefix}forum_ignore_list", [ 'user_id' => $uid ], [ '%d' ] ); echo '

Ignore-Liste vollständig geleert.

'; } } } // ── Admin: 2FA eines Users zurücksetzen ────────────────────────────────── if ( isset( $_POST['wbf_admin_reset_2fa'] ) && check_admin_referer( 'wbf_admin_2fa_nonce' ) ) { if ( current_user_can('manage_options') && class_exists('WBF_TOTP') ) { $uid = (int) ( $_POST['user_id'] ?? 0 ); if ( $uid ) { $target = WBF_DB::get_user( $uid ); if ( $target && $target->role !== WBF_Roles::SUPERADMIN ) { WBF_TOTP::disable_for( $uid ); echo '

2FA für ' . esc_html($target->display_name) . ' zurückgesetzt.

'; } } } } $members = WBF_DB::get_all_users( 200 ); $s_discord = wbf_get_settings(); $dc_sync_on = ( $s_discord['discord_role_sync'] ?? '0' ) === '1' && trim( $s_discord['discord_bot_token'] ?? '' ); // Discord-Meta aller User vorladen (1 Query statt N) $dc_meta = []; if ( $dc_sync_on ) { global $wpdb; $rows = $wpdb->get_results( "SELECT user_id, MAX(CASE WHEN meta_key='discord_user_id' THEN meta_value END) AS discord_uid, MAX(CASE WHEN meta_key='discord_username' THEN meta_value END) AS discord_name FROM {$wpdb->prefix}forum_user_meta WHERE meta_key IN ('discord_user_id','discord_username') GROUP BY user_id" ); foreach ( $rows as $r ) { $dc_meta[ (int)$r->user_id ] = $r; } } ?>

Mitglieder

role ); $color = esc_attr( $role['color'] ); $bg = esc_attr( $role['bg_color'] ); $icon = esc_attr( $role['icon'] ?? 'fas fa-user' ); // Nur sperren wenn dieser Forum-User wirklich dem WP-Superadmin (ID 1) entspricht. // Reine Rollen-Prüfung reicht nicht — sonst kann man versehentlich // zugewiesene superadmin-Rollen nicht mehr korrigieren. $wp_sa_data = get_userdata( WBF_Roles::get_wp_superadmin_id() ); $is_sa = ( $m->role === WBF_Roles::SUPERADMIN ) && $wp_sa_data && ( strtolower($m->email) === strtolower($wp_sa_data->user_email) ); $ban_reason = esc_attr( $m->ban_reason ?? '' ); $opts = ''; $dc_user = $dc_meta[ (int)$m->id ] ?? null; $has_dc = $dc_sync_on && $dc_user && ! empty( $dc_user->discord_uid ); foreach ( $roles as $k => $r ) { if ( $k === WBF_Roles::SUPERADMIN ) continue; $sel = $m->role === $k ? ' selected' : ''; $opts .= ''; } ?>
#NutzerE-Mail Aktuelle RolleBeiträge RegistriertRolle ändern Discord
id ); ?> display_name ); ?>
@username ); ?>
email ); ?> (Haupt-Admin) post_count ); ?> registered ) ) ); ?> Gesperrt — Haupt-Superadmin (WP User ID )
Leer = permanente Sperre
role === 'banned' && ! empty($m->ban_until) ): ?>
Temporäre Sperre — läuft ab am ban_until))); ?> ban_until) - time(); if ($diff > 0) { $days = floor($diff / 86400); $hours = floor(($diff % 86400) / 3600); $mins = floor(($diff % 3600) / 60); $parts = []; if ($days) $parts[] = $days . ' Tag' . ($days > 1 ? 'e' : ''); if ($hours) $parts[] = $hours . ' Std.'; if ($mins && !$days) $parts[] = $mins . ' Min.'; echo ' (' . esc_html(implode(', ', $parts)) . ' verbleibend)'; } ?>
role === 'banned' && empty($m->ban_until) ): ?>
Permanente Sperre
2FA-Status: id) ) : ?> ✔ Aktiv
— Nicht aktiv
discord_name ?: $dc_user->discord_uid ); ?>
Nicht verknüpft

Meldung aktualisiert.

'; } } $filter = sanitize_key( $_GET['filter'] ?? 'open' ); $reports = WBF_DB::get_reports( $filter, 100 ); $count = WBF_DB::count_open_reports(); echo '

Meldungen ' . ( $count > 0 ? '' . (int) $count . '' : '' ) . '

'; $tabs = ['open' => 'Offen', 'resolved' => 'Erledigt', 'dismissed' => 'Verworfen', 'all' => 'Alle']; echo ''; if ( empty( $reports ) ) { echo '

Keine Meldungen vorhanden.

'; return; } $reason_labels = [ 'spam' => 'Spam / Werbung', 'harassment' => 'Belästigung', 'inappropriate' => 'Unangemessen', 'misinformation' => 'Fehlinformation', 'off-topic' => 'Offtopic', 'other' => 'Sonstiges', ]; $status_colors = ['open' => '#f59e0b', 'resolved' => '#56cf7e', 'dismissed' => '#6b7a99']; echo ''; foreach ( $reports as $r ) { $reason_label = esc_html( $reason_labels[ $r->reason ] ?? $r->reason ); $preview = esc_html( mb_substr( strip_tags( $r->post_content ?? '' ), 0, 80 ) ); $sc = $status_colors[ $r->status ] ?? '#999'; $status_badge = "" . esc_html( ucfirst( $r->status ) ) . ""; $note_cell = $r->note ? '
' . esc_html( $r->note ) . '' : ''; $base = ['page' => 'wbf-reports', 'report_id' => $r->id, 'filter' => $filter]; if ( $r->status === 'open' ) { $res_url = esc_url( wp_nonce_url( add_query_arg( array_merge( $base, ['report_action' => 'resolved'] ), admin_url('admin.php') ), 'wbf_report_action' ) ); $dis_url = esc_url( wp_nonce_url( add_query_arg( array_merge( $base, ['report_action' => 'dismissed'] ), admin_url('admin.php') ), 'wbf_report_action' ) ); $actions = "✔ Erledigt "; $actions .= "✖ Verwerfen"; } else { $reopen_url = esc_url( wp_nonce_url( add_query_arg( array_merge( $base, ['report_action' => 'open'] ), admin_url('admin.php') ), 'wbf_report_action' ) ); $actions = "↩ Wieder öffnen"; } echo ""; } echo '
#Gemeldet vonGrundVorschauThreadDatumStatusAktionen
" . esc_html( $r->id ) . " " . esc_html( $r->reporter_name ?? '—' ) . "
@" . esc_html( $r->reporter_username ?? '' ) . "
$reason_label$note_cell $preview…
→ Zum Beitrag
" . ( $r->thread_id ? esc_html( mb_substr( $r->thread_title ?? '', 0, 35 ) ) : '—' ) . " " . esc_html( date( 'd.m.Y H:i', strtotime( $r->created_at ) ) ) . " $status_badge $actions
'; } // ── Level-System ────────────────────────────────────────────────────────────── function wbf_admin_levels() { // ── Toggle on/off ──────────────────────────────────────────────────────── if ( isset( $_POST['wbf_toggle_levels'] ) && check_admin_referer( 'wbf_levels_toggle_nonce' ) ) { WBF_Levels::set_enabled( ! empty( $_POST['levels_enabled'] ) ); echo '

Einstellung gespeichert!

'; } // ── Reset ──────────────────────────────────────────────────────────────── if ( isset( $_POST['wbf_reset_levels'] ) && check_admin_referer( 'wbf_levels_reset_nonce' ) ) { WBF_Levels::reset_to_defaults(); echo '

Level auf Standard zurückgesetzt.

'; } // ── Level löschen ──────────────────────────────────────────────────────── if ( isset( $_GET['delete_level'] ) && check_admin_referer( 'delete_level_' . (int) $_GET['delete_level'] ) ) { $idx = (int) $_GET['delete_level']; $levels = WBF_Levels::get_all(); if ( count($levels) > 1 ) { unset( $levels[$idx] ); WBF_Levels::save( array_values($levels) ); echo '

Level gelöscht.

'; } else { echo '

Mindestens ein Level muss vorhanden sein.

'; } } // ── Level speichern (Bearbeiten) ───────────────────────────────────────── if ( isset( $_POST['wbf_save_level'] ) && check_admin_referer( 'wbf_levels_edit_nonce' ) ) { $idx = (int) $_POST['level_index']; $levels = WBF_Levels::get_all(); $levels[$idx] = [ 'min' => max( 0, (int) $_POST['level_min'] ), 'label' => sanitize_text_field( $_POST['level_label'] ), 'icon' => sanitize_text_field( $_POST['level_icon'] ), 'color' => sanitize_hex_color( $_POST['level_color'] ) ?: '#94a3b8', ]; WBF_Levels::save( array_values($levels) ); echo '

Level aktualisiert!

'; } // ── Neues Level erstellen ──────────────────────────────────────────────── if ( isset( $_POST['wbf_create_level'] ) && check_admin_referer( 'wbf_levels_create_nonce' ) ) { $levels = WBF_Levels::get_all(); $levels[] = [ 'min' => max( 0, (int) $_POST['new_level_min'] ), 'label' => sanitize_text_field( $_POST['new_level_label'] ), 'icon' => sanitize_text_field( $_POST['new_level_icon'] ), 'color' => sanitize_hex_color( $_POST['new_level_color'] ) ?: '#94a3b8', ]; WBF_Levels::save( $levels ); echo '

Neues Level erstellt!

'; } $levels = WBF_Levels::get_all(); $enabled = WBF_Levels::is_enabled(); $edit_i = isset( $_GET['edit_level'] ) ? (int) $_GET['edit_level'] : null; $edit_l = ( $edit_i !== null && isset( $levels[$edit_i] ) ) ? $levels[$edit_i] : null; echo '
'; echo '

⭐ Level-System

'; // ── Status-Toggle ──────────────────────────────────────────────────────── $status_color = $enabled ? '#56cf7e' : '#f05252'; $status_label = $enabled ? '✅ Aktiviert' : '❌ Deaktiviert'; echo "
Level-System ist: {$status_label}

Wenn deaktiviert, werden keine Level-Badges im Forum angezeigt.

" . wp_nonce_field( 'wbf_levels_toggle_nonce', '_wpnonce', true, false ) . "
"; // ── Level-Tabelle ──────────────────────────────────────────────────────── echo '

Aktuelle Level

'; echo ''; foreach ( $levels as $i => $l ) { $color = esc_attr( $l['color'] ); $icon = esc_attr( $l['icon'] ); $label = esc_html( $l['label'] ); $del_url = wp_nonce_url( "?page=wbf-levels&delete_level={$i}", "delete_level_{$i}" ); $badge_html = " {$label} "; echo ""; } echo '
# Badge-Vorschau Ab Beiträgen Icon Aktionen
" . ( $i + 1 ) . " {$badge_html} ≥ " . esc_html( $l['min'] ) . " Beiträge {$icon} Bearbeiten " . ( count($levels) > 1 ? " | Löschen" : '' ) . "
'; // ── Reset-Button ───────────────────────────────────────────────────────── echo "
" . wp_nonce_field( 'wbf_levels_reset_nonce', '_wpnonce', true, false ) . "
"; // ── Bearbeiten-Formular ────────────────────────────────────────────────── if ( $edit_l !== null ) { echo '

Level bearbeiten: ' . esc_html( $edit_l['label'] ) . '

'; echo '
'; wp_nonce_field( 'wbf_levels_edit_nonce' ); echo ''; echo wbf_level_form_fields( $edit_l, '' ); echo '
'; submit_button( 'Änderungen speichern', 'primary', 'wbf_save_level' ); echo '

'; } // ── Neues Level erstellen ──────────────────────────────────────────────── echo '

Neues Level erstellen

'; echo '
'; wp_nonce_field( 'wbf_levels_create_nonce' ); echo wbf_level_form_fields( null, 'new_' ); echo '
'; submit_button( '+ Level erstellen', 'primary', 'wbf_create_level' ); echo '
'; echo '
'; } // ── Hilfsformular für Level-Felder ──────────────────────────────────────────── function wbf_level_form_fields( $level, $prefix ) { $min = esc_attr( $level['min'] ?? '' ); $label = esc_attr( $level['label'] ?? '' ); $icon = esc_attr( $level['icon'] ?? 'fas fa-star' ); $color = esc_attr( $level['color'] ?? '#94a3b8' ); return " Ab Beiträgen *

Level ab dieser Beitragsanzahl (0 = für alle).

Bezeichnung * Farbe Icon

FontAwesome 6, z.B. fas fa-crown, fas fa-fire, fas fa-seedling

"; } // ── Reaktionen ───────────────────────────────────────────────────────────────── function wbf_admin_reactions() { if ( ! current_user_can('manage_options') ) return; $defaults = ['👍','❤️','😂','😮','😢','😡']; // Speichern if ( isset($_POST['wbf_save_reactions']) && check_admin_referer('wbf_reactions_nonce') ) { $raw = $_POST['wbf_reactions_list'] ?? ''; $emojis = []; // Split by whitespace or comma, keep only single emoji characters $parts = preg_split('/[\s,]+/', trim($raw), -1, PREG_SPLIT_NO_EMPTY); foreach ($parts as $p) { // Accept any character sequence 1-8 chars (covers multi-codepoint emojis) $p = trim($p); if ( mb_strlen($p) >= 1 && mb_strlen($p) <= 8 ) { $emojis[] = $p; } } $emojis = array_values(array_unique($emojis)); if ( empty($emojis) ) $emojis = $defaults; // Fallback $emojis = array_slice($emojis, 0, 20); // Max 20 Reaktionen update_option('wbf_reactions', $emojis); echo '

✅ Reaktionen gespeichert!

'; } // Reset if ( isset($_POST['wbf_reset_reactions']) && check_admin_referer('wbf_reactions_nonce') ) { delete_option('wbf_reactions'); echo '

✅ Reaktionen auf Standard zurückgesetzt!

'; } $current = WBF_DB::get_allowed_reactions(); // Emoji-Kategorien für die Schnellauswahl $emoji_groups = [ 'Gesichter' => ['😀','😃','😄','😁','😆','😅','😂','🤣','😊','😇','🥰','😍','🤩','😘','😗','😙','😚','🙂','🤗','🤭','🤫','🤔','🤐','🤨','😐','😑','😶','😏','😒','😞','😔','😟','😕','🙃','😣','😖','😫','😩','🥺','😢','😭','😤','😠','😡','🤬','😈','👿','💀','☠️','💩','🤡','👹','👺','👻','👽','👾','🤖'], 'Gesten' => ['👍','👎','👏','🙌','🤝','🤜','🤛','✊','👊','🤚','✋','🖐','👋','🤙','💪','🦾','🙏','🤲','🫶','❤️','🧡','💛','💚','💙','💜','🖤','🤍','🤎','💔','❣️','💕','💞','💓','💗','💖','💘','💝'], 'Symbole' => ['🔥','⭐','✨','💫','💥','💢','❗','❓','💯','✅','❌','⚠️','🚀','🎉','🎊','🏆','🥇','🎯','💡','💎','🔑','🛡️','⚔️','🗡️','🔔','📢','📣','🎵','🎶','💻','📱','🖥️','📷','🎮'], 'Natur' => ['🌟','⚡','🌈','❄️','🌊','🔮','🌸','🌺','🌻','🌹','🍀','🌿','🌱','🌲','🌳','🦋','🐉','🦄','🐺','🦊','🐸','🐧','🦅','🦁'], 'Essen' => ['🍕','🍔','🍟','🌮','🌯','🍜','🍝','🍣','🍱','🍩','🎂','🍺','🥂','☕','🧋','🍭'], ]; ?>

💬 Reaktionen verwalten

Lege fest welche Emoji-Reaktionen Nutzer unter Posts verwenden können. Maximal 20.

✏️ Aktuelle Konfiguration

Aktive Reaktionen (/20)

Emojis direkt eingeben oder unten aus der Bibliothek auswählen. Max. 20 Stück.

📚 Emoji-Bibliothek
$group_emojis): ?>
$group_emojis): ?>

🔍 Vorschau (wie im Forum)

time()) $expires_at = date('Y-m-d H:i:s', $ts); } // Als Superadmin-Forum-User erstellen $wp_user = wp_get_current_user(); $forum_user = WBF_DB::get_user_by('email', $wp_user->user_email); $creator_id = $forum_user ? $forum_user->id : 0; $code = WBF_DB::create_invite($creator_id, $max_uses, $note, $expires_at); echo '

✅ Einladungscode ' . esc_html($code) . ' erstellt!

'; } // Einladung löschen if ( isset($_GET['del_invite'], $_GET['_wpnonce']) && wp_verify_nonce($_GET['_wpnonce'], 'del_invite_' . (int)$_GET['del_invite']) ) { WBF_DB::delete_invite((int)$_GET['del_invite']); echo '

Einladung gelöscht.

'; } $invites = WBF_DB::get_all_invites(200); $forum_url = wbf_get_forum_url(); $reg_mode = wbf_get_settings()['registration_mode'] ?? 'open'; ?>

📨 Einladungen

Hinweis: Der Registrierungsmodus ist aktuell auf gestellt — Einladungscodes werden erst überprüft wenn der Modus auf Nur Einladung steht. → Jetzt ändern

+ Neue Einladung erstellen
Max. Nutzungen Wie oft kann der Code verwendet werden
Läuft ab am Leer lassen = kein Ablauf
Notiz

📭

Noch keine Einladungen erstellt.

code); $expired = $inv->expires_at && strtotime($inv->expires_at) < time(); $used_up = $inv->use_count >= $inv->max_uses; $status_ok = !$expired && !$used_up; $del_url = wp_nonce_url( admin_url('admin.php?page=wbf-invites&del_invite=' . $inv->id), 'del_invite_' . $inv->id ); ?>
Code / Link Notiz Nutzungen Ablauf Erstellt von Aktionen
code); ?> Abgelaufen Aufgebraucht Aktiv
note ?: '—'); ?> use_count; ?> / max_uses; ?> used_name): ?>
used_name); ?>
expires_at ? date('d.m.Y H:i', strtotime($inv->expires_at)) : '∞ Nie'; ?> creator_name ?? '—'); ?> ✕ Löschen
ℹ️ So funktionieren Einladungen
  1. Setze den Registrierungsmodus in Einstellungen auf Nur Einladung
  2. Erstelle hier einen Einladungslink — wähle wie oft er genutzt werden kann und ob er abläuft
  3. Teile den Link mit deinen gewünschten Nutzern (z.B. per Discord, E-Mail, etc.)
  4. Besucher die den Link öffnen, sehen das Registrierungsformular mit vorausgefülltem Code
  5. Nach erfolgreicher Registrierung wird der Code als verwendet markiert
['x'=>$r->day,'y'=>(int)$r->count], $stats['posts_per_day'])); $thread_data = wp_json_encode(array_map(fn($r)=>['x'=>$r->day,'y'=>(int)$r->count], $stats['threads_per_day'])); $reg_data = wp_json_encode(array_map(fn($r)=>['x'=>$r->day,'y'=>(int)$r->count], $stats['registrations'])); $hour_labels = wp_json_encode(array_map(fn($r)=>$r->hour.':00', $stats['active_hours'])); $hour_data = wp_json_encode(array_map(fn($r)=>(int)$r->count, $stats['active_hours'])); ?>

📊 Forum-Statistiken

'7 Tage',14=>'14 Tage',30=>'30 Tage',90=>'90 Tage'] as $d=>$l): ?>

Beiträge & Threads pro Tag

Aktivste Stunden

Neue Registrierungen

Top Poster (letzte Tage)

$p): ?>
# Nutzer Beiträge
display_name); ?> role); ?> post_count; ?>

✅ Wiederhergestellt.

'; } if (isset($_POST['wbf_perm_delete']) && check_admin_referer('wbf_trash_nonce')) { global $wpdb; $type = sanitize_key($_POST['content_type']); $id = (int)$_POST['content_id']; if ($type==='thread') $wpdb->delete("{$wpdb->prefix}forum_threads", ['id'=>$id]); else $wpdb->delete("{$wpdb->prefix}forum_posts", ['id'=>$id]); echo '

✅ Endgültig gelöscht.

'; } $items = WBF_DB::get_deleted_content(100); ?>

🗑️ Papierkorb

Gelöschte Inhalte können hier wiederhergestellt oder endgültig entfernt werden.

Papierkorb ist leer.

TypInhaltKategorie / ThreadAutorGelöscht amAktionen
type==='thread' ? '🧵 Thread' : '💬 Post'; ?> content_preview); ?> cat_name); ?> display_name); ?> deleted_at)); ?>
get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_categories"); if ( $existing === 0 || $force ) { if ($force) $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_categories"); foreach ($data['categories'] as $cat) { // BUGFIX: Original-ID behalten! Threads zeigen direkt auf diese IDs. $wpdb->query( $wpdb->prepare( "INSERT INTO {$wpdb->prefix}forum_categories (id, parent_id, name, slug, description, icon, sort_order, thread_count, post_count, min_role, guest_visible) VALUES (%d,%d,%s,%s,%s,%s,%d,%d,%d,%s,%d) ON DUPLICATE KEY UPDATE parent_id=%d, name=%s, slug=%s, description=%s, icon=%s, sort_order=%d, thread_count=%d, post_count=%d, min_role=%s, guest_visible=%d", (int)($cat['id']), (int)($cat['parent_id'] ?? 0), $cat['name'] ?? '', $cat['slug'] ?? '', $cat['description'] ?? '', $cat['icon'] ?? 'fas fa-comments', (int)($cat['sort_order'] ?? 0), (int)($cat['thread_count'] ?? 0), (int)($cat['post_count'] ?? 0), $cat['min_role'] ?? 'member', (int)($cat['guest_visible'] ?? 1), (int)($cat['parent_id'] ?? 0), $cat['name'] ?? '', $cat['slug'] ?? '', $cat['description'] ?? '', $cat['icon'] ?? 'fas fa-comments', (int)($cat['sort_order'] ?? 0), (int)($cat['thread_count'] ?? 0), (int)($cat['post_count'] ?? 0), $cat['min_role'] ?? 'member', (int)($cat['guest_visible'] ?? 1) ) ); } $imported[] = 'Kategorien (' . count($data['categories']) . ')'; } else { $imported[] = 'Kategorien übersprungen (bereits vorhanden — „Überschreiben" aktivieren)'; } } // ── Benutzer + User-Meta ────────────────────────────────── if ( ! empty($data['users']) ) { $force = ! empty($_POST['import_force_users']); $id_map = []; // old_id => new_id für User-Meta $count = 0; foreach ($data['users'] as $u) { $old_id = (int)$u['id']; $exists = $wpdb->get_var($wpdb->prepare( "SELECT id FROM {$wpdb->prefix}forum_users WHERE username=%s OR email=%s", $u['username'], $u['email'] )); if ($exists && !$force) { $id_map[$old_id] = (int)$exists; continue; } // Superadmin-Schutz $u['role'] = ($u['role'] === 'superadmin') ? 'member' : $u['role']; if ($exists && $force) { $uid = (int)$exists; unset($u['id']); $wpdb->update("{$wpdb->prefix}forum_users", $u, ['id' => $uid]); } else { unset($u['id']); $wpdb->insert("{$wpdb->prefix}forum_users", $u); $uid = (int)$wpdb->insert_id; } $id_map[$old_id] = $uid; $count++; } $imported[] = "Benutzer ($count importiert)"; // User-Meta (Profilfeld-Werte) mit gemappten IDs importieren if ( ! empty($data['user_meta']) ) { $force_meta = $force; // gleiche Option wie Benutzer if ($force_meta) $wpdb->query("DELETE FROM {$wpdb->prefix}forum_user_meta WHERE user_id IN (" . implode(',', array_values($id_map) ?: [0]) . ")"); $meta_count = 0; foreach ($data['user_meta'] as $row) { $new_uid = $id_map[(int)$row['user_id']] ?? null; if (!$new_uid) continue; unset($row['id']); $row['user_id'] = $new_uid; $wpdb->replace("{$wpdb->prefix}forum_user_meta", $row); $meta_count++; } if ($meta_count) $imported[] = "Profilfeld-Werte ($meta_count)"; } } // ── Threads, Posts, Tags, Abonnements ──────────────────── if ( ! empty($data['threads']) ) { $force = ! empty($_POST['import_force_threads']); if ($force) { // Alle abhängigen Tabellen mitbereinigen (verhindert verwaiste Einträge) $wpdb->query("SET FOREIGN_KEY_CHECKS=0"); foreach ([ 'forum_posts', 'forum_threads', 'forum_thread_tags', 'forum_tags', 'forum_subscriptions', 'forum_polls', 'forum_poll_votes', 'forum_bookmarks', 'forum_prefixes', 'forum_likes', 'forum_reactions', 'forum_notifications', 'forum_ignore_list', ] as $_tbl) { $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}$_tbl"); } $wpdb->query("SET FOREIGN_KEY_CHECKS=1"); } foreach ($data['threads'] ?? [] as $t) { $wpdb->insert("{$wpdb->prefix}forum_threads", $t); } foreach ($data['posts'] ?? [] as $p) { $wpdb->insert("{$wpdb->prefix}forum_posts", $p); } // Tags re-importieren if (!empty($data['thread_tags'])) { $tag_map = []; foreach ($data['thread_tags'] as $tt) { if (!isset($tag_map[$tt['slug']])) { $etag = $wpdb->get_var($wpdb->prepare( "SELECT id FROM {$wpdb->prefix}forum_tags WHERE slug=%s", $tt['slug'] )); if (!$etag) { $wpdb->insert("{$wpdb->prefix}forum_tags", ['name'=>$tt['name'],'slug'=>$tt['slug'],'use_count'=>$tt['use_count']]); $tag_map[$tt['slug']] = $wpdb->insert_id; } else { $tag_map[$tt['slug']] = $etag; } } $wpdb->replace("{$wpdb->prefix}forum_thread_tags", ['thread_id'=>$tt['thread_id'],'tag_id'=>$tag_map[$tt['slug']]]); } } // Abonnements if (!empty($data['subscriptions'])) { $sc = 0; foreach ($data['subscriptions'] as $row) { unset($row['id']); $wpdb->replace("{$wpdb->prefix}forum_subscriptions", $row); $sc++; } if ($sc) $imported[] = "Abonnements ($sc)"; } $imported[] = 'Threads + Posts (' . count($data['threads']) . ' / ' . count($data['posts'] ?? []) . ')'; } // ── Thread-Präfixe ──────────────────────────────────────── // ── Thread-Präfixe ──────────────────────────────────────── if ( ! empty($data['prefixes']) ) { $force = ! empty($_POST['import_force_prefixes']); if ($force) $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_prefixes"); $pc = 0; foreach ($data['prefixes'] as $row) { $wpdb->query( $wpdb->prepare( "INSERT INTO {$wpdb->prefix}forum_prefixes (id, label, color, bg_color, sort_order) VALUES (%d,%s,%s,%s,%d) ON DUPLICATE KEY UPDATE label=%s, color=%s, bg_color=%s, sort_order=%d", (int)$row['id'], $row['label'], $row['color'] ?? '#ffffff', $row['bg_color'] ?? '#475569', (int)($row['sort_order'] ?? 0), $row['label'], $row['color'] ?? '#ffffff', $row['bg_color'] ?? '#475569', (int)($row['sort_order'] ?? 0) ) ); $pc++; } if ($pc) $imported[] = "Thread-Präfixe ($pc)"; } // ── Umfragen + Abstimmungen ─────────────────────────────── if ( ! empty($data['polls']) ) { $force = ! empty($_POST['import_force_polls']); if ($force) { $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_poll_votes"); $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_polls"); } $pc = 0; foreach ($data['polls'] as $row) { $wpdb->query( $wpdb->prepare( "INSERT INTO {$wpdb->prefix}forum_polls (id, thread_id, question, options, multi, ends_at, created_at) VALUES (%d,%d,%s,%s,%d,%s,%s) ON DUPLICATE KEY UPDATE thread_id=%d, question=%s, options=%s, multi=%d, ends_at=%s", (int)$row['id'], (int)$row['thread_id'], $row['question'] ?? '', $row['options'] ?? '[]', (int)($row['multi'] ?? 0), $row['ends_at'] ?? null, $row['created_at'] ?? current_time('mysql'), (int)$row['thread_id'], $row['question'] ?? '', $row['options'] ?? '[]', (int)($row['multi'] ?? 0), $row['ends_at'] ?? null ) ); $pc++; } foreach ($data['poll_votes'] ?? [] as $row) { unset($row['id']); $wpdb->replace("{$wpdb->prefix}forum_poll_votes", $row); } $imported[] = "Umfragen ($pc)"; } // ── Lesezeichen ─────────────────────────────────────────── if ( ! empty($data['bookmarks']) ) { $force = ! empty($_POST['import_force_bookmarks']); if ($force) $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_bookmarks"); $bc = 0; foreach ($data['bookmarks'] as $row) { unset($row['id']); $wpdb->replace("{$wpdb->prefix}forum_bookmarks", $row); $bc++; } if ($bc) $imported[] = "Lesezeichen ($bc)"; } // ── Likes, Reaktionen, Benachrichtigungen ───────────────── if ( ! empty($data['likes']) ) { $force = ! empty($_POST['import_force_threads']); if ($force) $wpdb->query("DELETE FROM {$wpdb->prefix}forum_likes"); $count = 0; foreach ($data['likes'] as $row) { unset($row['id']); if (!$wpdb->get_var($wpdb->prepare( "SELECT id FROM {$wpdb->prefix}forum_likes WHERE user_id=%d AND object_id=%d AND object_type=%s", $row['user_id'], $row['object_id'], $row['object_type'] ))) { $wpdb->insert("{$wpdb->prefix}forum_likes", $row); $count++; } } $imported[] = "Likes ($count)"; } if ( ! empty($data['reactions']) ) { $force = ! empty($_POST['import_force_threads']); if ($force) $wpdb->query("DELETE FROM {$wpdb->prefix}forum_reactions"); foreach ($data['reactions'] as $row) { unset($row['id']); $wpdb->replace("{$wpdb->prefix}forum_reactions", $row); } $imported[] = 'Reaktionen (' . count($data['reactions']) . ')'; } if ( ! empty($data['notifications']) ) { $force = ! empty($_POST['import_force_threads']); if ($force) $wpdb->query("DELETE FROM {$wpdb->prefix}forum_notifications"); $count = 0; foreach ($data['notifications'] as $row) { unset($row['id']); $wpdb->insert("{$wpdb->prefix}forum_notifications", $row); $count++; } $imported[] = "Benachrichtigungen ($count)"; } // ── Privatnachrichten ───────────────────────────────────── if ( ! empty($data['messages']) ) { $force = ! empty($_POST['import_force_messages']); if ($force) $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_messages"); $count = 0; foreach ($data['messages'] as $row) { unset($row['id']); $wpdb->insert("{$wpdb->prefix}forum_messages", $row); $count++; } $imported[] = "Nachrichten ($count)"; } // ── Meldungen ───────────────────────────────────────────── if ( ! empty($data['reports']) ) { $force = ! empty($_POST['import_force_reports']); if ($force) $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_reports"); $count = 0; foreach ($data['reports'] as $row) { unset($row['id']); $wpdb->insert("{$wpdb->prefix}forum_reports", $row); $count++; } $imported[] = "Meldungen ($count)"; } // ── Einladungen ─────────────────────────────────────────── if ( ! empty($data['invites']) ) { $force = ! empty($_POST['import_force_invites']); if ($force) $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_invites"); $count = 0; foreach ($data['invites'] as $row) { unset($row['id']); // Duplicate code skip if ($wpdb->get_var($wpdb->prepare("SELECT id FROM {$wpdb->prefix}forum_invites WHERE code=%s", $row['code']))) continue; $wpdb->insert("{$wpdb->prefix}forum_invites", $row); $count++; } if ($count) $imported[] = "Einladungen ($count)"; } // ── Ignore-Liste ────────────────────────────────────────── if ( ! empty($data['ignore_list']) ) { $force = ! empty($_POST['import_force_ignore']); if ($force) $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_ignore_list"); $count = 0; foreach ($data['ignore_list'] as $row) { unset($row['id']); $wpdb->replace("{$wpdb->prefix}forum_ignore_list", $row); $count++; } if ($count) $imported[] = "Ignore-Einträge ($count)"; } if ( empty($imported) ) { $notice = ['warning', 'Nichts importiert — Datei enthielt keine gültigen Abschnitte.']; } else { $notice = ['success', '✅ Importiert: ' . implode(', ', $imported) . '. Erstellt: ' . esc_html($data['_meta']['exported']) . ' · Site: ' . esc_html($data['_meta']['site'] ?? '?')]; } } } } // ── UI ───────────────────────────────────────────────────────────────────── ?>

Export / Import

Export

Wähle was exportiert werden soll. Die Datei wird als .json heruntergeladen.

['⚙️', 'Einstellungen & Profilfelder', 'Forum-Texte, Regeln, Labels + Felddefinitionen'], 'roles' => ['🛡️', 'Rollen', 'Alle Rollen inkl. Berechtigungen & Farben'], 'levels' => ['⭐', 'Level-System', 'Level-Konfiguration & Status'], 'categories' => ['📂', 'Kategorien', 'Alle Kategorien inkl. Hierarchie'], 'users' => ['👥', 'Benutzer & Profilfelder', 'Accounts, Ban-Status, Profilfeld-Werte'], 'threads' => ['💬', 'Threads, Posts & Abos', 'Alle Inhalte, Tags & Abonnements'], 'polls' => ['📊', 'Umfragen', 'Alle Umfragen inkl. Abstimmungen'], 'bookmarks' => ['🔖', 'Lesezeichen', 'Alle gespeicherten Thread-Lesezeichen'], 'prefixes' => ['🏷️', 'Thread-Präfixe', 'Alle konfigurierten Präfix-Labels & Farben'], 'interactions' => ['❤️', 'Likes & Reaktionen', 'Likes, Reaktionen, Benachrichtigungen'], 'messages' => ['✉️', 'Privatnachrichten', 'Alle DM-Konversationen'], 'ignore_list' => ['🚫', 'Ignore-Liste', 'Alle Nutzer-Blockierungen'], 'reports' => ['🚩', 'Meldungen', 'Gemeldete Beiträge inkl. Status'], 'invites' => ['📨', 'Einladungen', 'Alle Einladungscodes inkl. Nutzungsstand'], ]; foreach ($export_options as $key => [$icon, $label, $desc]): ?>
Datei wird sofort heruntergeladen
Import

Lade eine zuvor exportierte .json-Datei hoch.

⚠️ Hinweis: Benutzer-Import enthält Passwort-Hashes. Threads-Import kann bestehende Daten überschreiben. Mache vorher ein Backup.

Überschreiben-Optionen

'Kategorien überschreiben (löscht bestehende)', 'import_force_users' => 'Benutzer aktualisieren (bei gleichem Username) inkl. Profilfeld-Werte', 'import_force_threads' => 'Threads, Posts, Likes, Reaktionen & Abonnements überschreiben', 'import_force_polls' => 'Umfragen & Abstimmungen überschreiben', 'import_force_bookmarks'=> 'Lesezeichen überschreiben', 'import_force_prefixes' => 'Thread-Präfixe überschreiben', 'import_force_ignore' => 'Ignore-Liste überschreiben', 'import_force_messages' => 'Privatnachrichten überschreiben', 'import_force_reports' => 'Meldungen überschreiben', 'import_force_invites' => 'Einladungen überschreiben', ]; foreach ($overwrite_opts as $name => $label): ?>
Nur kompatible WBF-Backups

Export-Inhalte im Überblick

$cid ) { $cid = sanitize_key( $cid ); if ( ! $cid ) continue; $cats[] = [ 'id' => $cid, 'name' => sanitize_text_field( $cat_names[$ci] ?? $cid ), 'icon' => sanitize_text_field( $cat_icons[$ci] ?? '📋' ), ]; } // Neue Kategorie if ( ! empty( trim( $_POST['new_cat_name'] ?? '' ) ) ) { $new_name = sanitize_text_field( $_POST['new_cat_name'] ); $new_icon = sanitize_text_field( $_POST['new_cat_icon'] ?? '📋' ); $new_id = 'cat_' . sanitize_key( $new_name ) . '_' . time(); $cats[] = [ 'id' => $new_id, 'name' => $new_name, 'icon' => $new_icon ]; } WBF_DB::save_profile_field_categories( $cats ); // Felder $keys = $_POST['field_key'] ?? []; $labels = $_POST['field_label'] ?? []; $types = $_POST['field_type'] ?? []; $phs = $_POST['field_placeholder'] ?? []; $req = $_POST['field_required'] ?? []; $pub = $_POST['field_public'] ?? []; $opts = $_POST['field_options'] ?? []; $cats_f = $_POST['field_category'] ?? []; $valid_cat_ids = array_column( $cats, 'id' ); $fields = []; foreach ( $keys as $i => $raw_key ) { $key = sanitize_key( $raw_key ); if ( ! $key ) continue; if ( in_array($key, ['username','email','password','display_name','bio','signature','avatar_url','role']) ) continue; $cat_id = sanitize_key( $cats_f[$i] ?? '' ); if ( ! in_array( $cat_id, $valid_cat_ids ) ) $cat_id = ''; $fields[] = [ 'key' => $key, 'label' => sanitize_text_field( $labels[$i] ?? $key ), 'type' => in_array($types[$i] ?? '', ['text','url','textarea','select','number','date']) ? $types[$i] : 'text', 'placeholder' => sanitize_text_field( $phs[$i] ?? '' ), 'required' => ! empty( $req[$i] ) ? 1 : 0, 'public' => ! empty( $pub[$i] ) ? 1 : 0, 'options' => sanitize_textarea_field( $opts[$i] ?? '' ), 'category_id' => $cat_id, ]; } WBF_DB::save_profile_field_defs( $fields ); echo '

✅ Profilfelder & Kategorien gespeichert!

'; } // Kategorie löschen if ( isset($_GET['wbf_del_cat']) && check_admin_referer('wbf_del_cat') ) { $del_id = sanitize_key( $_GET['wbf_del_cat'] ); $cats = WBF_DB::get_profile_field_categories(); $cats = array_values( array_filter( $cats, fn($c) => $c['id'] !== $del_id ) ); WBF_DB::save_profile_field_categories( $cats ); // Felder dieser Kategorie auf "keine Kategorie" setzen $fields = WBF_DB::get_profile_field_defs(); foreach ( $fields as &$f ) { if ( ($f['category_id'] ?? '') === $del_id ) $f['category_id'] = ''; } WBF_DB::save_profile_field_defs( $fields ); wp_safe_redirect( remove_query_arg(['wbf_del_cat','_wpnonce']) ); exit; } $fields = WBF_DB::get_profile_field_defs(); $cats = WBF_DB::get_profile_field_categories(); $cat_map = array_column( $cats, null, 'id' ); $type_opts = ['text'=>'Text','url'=>'URL/Link','textarea'=>'Mehrzeiliger Text','select'=>'Auswahlliste','number'=>'Zahl','date'=>'Datum (Alter)']; // Felder nach Kategorie gruppieren $by_cat = []; foreach ( $fields as $f ) { $cid = $f['category_id'] ?? ''; if ( ! $cid || ! isset($cat_map[$cid]) ) $cid = '__none__'; $by_cat[$cid][] = $f; } ?>

👤 Benutzerdefinierte Profilfelder

Felder die Nutzer in ihrem Profil ausfüllen können — z.B. Website, Ort, Discord-Name, etc.

🗂️ Kategorien verwalten

$cat ): ?>
Icon Kategorie-Name
'wbf-profile-fields','wbf_del_cat'=>$cat['id']], admin_url('admin.php')), 'wbf_del_cat' ); ?> ×
'__none__','name'=>'Ohne Kategorie','icon'=>'📋']; } foreach ( $all_sections as $cat ): $cid = $cat['id']; $c_fields = $by_cat[$cid] ?? []; ?>
Feld
$f ): $fi = $wbf_fidx; $wbf_fidx++; ?>
Schlüssel Bezeichnung Typ Platzhalter Optionen Pflicht Öffentlich Kategorie
Noch keine Felder in dieser Kategorie.
>

Nicht änderbar

> >

ℹ️ Hinweise

🔔 WP Business Forum — Updates

📦
Update verfügbar!

Version wurde veröffentlicht. Du verwendest aktuell .

📥 Zum Download
Du verwendest die neueste Version

Version ist aktuell.

Installierte Version
Neueste Version NEU Nicht abrufbar
Veröffentlicht am Uhr
Update-Quelle git.viper.ipv64.net — WP-Business-Forum
Letzter Check ✔ Cache aktiv (12h)' : ''; ?> 🔄 Jetzt prüfen

📋 Release Notes — v

Vollständige Release-Seite öffnen →
📖 Update-Anleitung
  1. Lade die neue .zip von der Release-Seite herunter.
  2. Erstelle vorher ein Backup über Export / Import.
  3. Lade die neue Version per FTP/SFTP hoch und überschreibe die alten Dateien — oder nutze den WP-Plugins-Bereich (Plugin deaktivieren → löschen → neu hochladen).
  4. Aktiviere das Plugin. Das DB-Schema wird automatisch aktualisiert.
query( "DROP TABLE IF EXISTS `{$wpdb->prefix}{$t}`" ); } // 2. Optionen $opts = [ 'wbf_settings','wbf_custom_roles','wbf_level_config','wbf_levels_enabled', 'wbf_profile_fields','wbf_reactions','wbf_forum_page_id','wbf_superadmin_email', 'wbf_db_version', ]; foreach ( $opts as $o ) { delete_option( $o ); } // 3. Transients delete_transient( 'wbf_activation_redirect' ); delete_transient( 'wbf_stats_cache' ); $wpdb->query( "DELETE FROM `{$wpdb->options}` WHERE `option_name` LIKE '_transient_wbf_%' OR `option_name` LIKE '_transient_timeout_wbf_%'" ); // 4. Cron wp_clear_scheduled_hook( 'wbf_check_expired_bans' ); // 5. Forum-Seite $page_id = get_option( 'wbf_forum_page_id' ); if ( $page_id ) wp_delete_post( (int) $page_id, true ); // 6. Upload-Verzeichnis $upload_dir = wp_upload_dir(); $wbf_dir = trailingslashit( $upload_dir['basedir'] ) . 'wbf-avatars'; if ( is_dir( $wbf_dir ) ) { // Rekursiv löschen $it = new RecursiveDirectoryIterator( $wbf_dir, RecursiveDirectoryIterator::SKIP_DOTS ); $ri = new RecursiveIteratorIterator( $it, RecursiveIteratorIterator::CHILD_FIRST ); foreach ( $ri as $file ) { $file->isDir() ? rmdir( $file->getRealPath() ) : unlink( $file->getRealPath() ); } rmdir( $wbf_dir ); } $did_uninstall = true; } elseif ( isset( $_POST['wbf_do_uninstall'] ) ) { $error = 'Bitte gib ALLES LÖSCHEN korrekt ein um fortzufahren.'; } ?>

Forum deinstallieren

✅ Alle Forumsdaten wurden erfolgreich gelöscht.
Tabellen, Optionen, Transients, Cron-Jobs und der Avatar-Ordner wurden entfernt.
Du kannst das Plugin jetzt unter Plugins deaktivieren und löschen.

Achtung — dieser Vorgang ist nicht rückgängig zu machen!

Folgendes wird dauerhaft gelöscht:

  • 🗄️ Alle Datenbanktabellen (Nutzer, Threads, Posts, Nachrichten, Umfragen, …)
  • ⚙️ Alle Plugin-Einstellungen & Optionen
  • 🖼️ Avatar-Upload-Verzeichnis (wp-content/uploads/wbf-avatars/)
  • 📄 Die Forum-Seite (erstellt vom Setup-Wizard)
  • ⏰ Alle geplanten Cron-Jobs

Das Plugin selbst bleibt installiert — du kannst es danach neu einrichten oder manuell löschen.

Gib ALLES LÖSCHEN ein um fortzufahren:

  Abbrechen

Präfix gelöscht.

'; } // Speichern (neu oder bearbeiten) if ( isset($_POST['wbf_save_prefix']) && check_admin_referer('wbf_prefix_nonce') ) { $data = [ 'label' => sanitize_text_field($_POST['label'] ?? ''), 'color' => sanitize_hex_color($_POST['color'] ?? '') ?: '#ffffff', 'bg_color' => sanitize_hex_color($_POST['bg_color'] ?? '') ?: '#475569', 'sort_order' => (int)($_POST['sort_order'] ?? 0), ]; if ( !empty($data['label']) ) { if ( !empty($_POST['prefix_id']) ) { WBF_DB::update_prefix( (int)$_POST['prefix_id'], $data ); echo '

Präfix aktualisiert!

'; } else { WBF_DB::create_prefix( $data ); echo '

Präfix erstellt!

'; } } } $prefixes = WBF_DB::get_prefixes(); $edit_id = isset($_GET['edit_prefix']) ? (int)$_GET['edit_prefix'] : 0; $edit = $edit_id ? WBF_DB::get_prefix($edit_id) : null; ?>

🏷️ Thread-Präfixe

Präfixe erscheinen farbig vor dem Thread-Titel. Nutzer können beim Erstellen eines Threads einen Präfix wählen.

Noch keine Präfixe erstellt.
id}", "delete_prefix_{$px->id}"); ?>
VorschauReihenfolgeAktionen
label); ?> sort_order; ?> Bearbeiten | Löschen
property warnings $px_label = $edit ? $edit->label : ''; $px_color = $edit ? $edit->color : '#ffffff'; $px_bg = $edit ? $edit->bg_color : '#475569'; $px_order = $edit ? (int) $edit->sort_order : 0; $px_id = $edit ? (int) $edit->id : 0; ?>

Label *
Textfarbe
Hintergrund
Reihenfolge
Abbrechen

Vorschau

Vorschau

✅ Wortfilter gespeichert!

'; } $current_list = get_option('wbf_word_filter', ''); $word_count = $current_list ? count(array_filter(array_map('trim', explode("\n", $current_list)))) : 0; ?>

🚫 Wortfilter / Zensurliste

Gesperrte Wörter werden in Threads und Antworten automatisch mit *** ersetzt. Eines pro Zeile, Groß-/Kleinschreibung wird ignoriert.

ℹ️ Hinweis: Der Filter wird beim Speichern neuer Beiträge angewendet — bestehende Inhalte bleiben unverändert. Aktuell gesperrte Wörter/Phrasen.

Auch Phrasen mit Leerzeichen sind möglich (z.B. "böse phrase"). Regex wird nicht unterstützt.

'Keine Berechtigung.']); check_ajax_referer('wbf_discord_test', 'nonce'); $s = wbf_get_settings(); $token = trim($s['discord_bot_token'] ?? ''); $guild = trim($s['discord_guild_id'] ?? ''); if ( ! $token ) { wp_send_json_error(['message' => 'Kein Bot-Token gespeichert.']); } // Bot-Info abrufen (@me) $res = wp_remote_get('https://discord.com/api/v10/users/@me', [ 'timeout' => 8, 'headers' => [ 'Authorization' => 'Bot ' . $token, 'Content-Type' => 'application/json', ], ]); if ( is_wp_error($res) ) { wp_send_json_error(['message' => 'HTTP-Fehler: ' . $res->get_error_message()]); } $code = wp_remote_retrieve_response_code($res); $body = json_decode(wp_remote_retrieve_body($res), true); if ( $code !== 200 || empty($body['id']) ) { $err = $body['message'] ?? 'Unbekannter Fehler (HTTP ' . $code . ')'; wp_send_json_error(['message' => 'Discord API: ' . $err]); } $bot_name = ($body['username'] ?? 'Unbekannt') . '#' . ($body['discriminator'] ?? '0'); // Guild-Prüfung falls Guild-ID angegeben $guild_info = ''; if ( $guild ) { $gr = wp_remote_get("https://discord.com/api/v10/guilds/{$guild}", [ 'timeout' => 6, 'headers' => ['Authorization' => 'Bot ' . $token], ]); if ( ! is_wp_error($gr) && wp_remote_retrieve_response_code($gr) === 200 ) { $gd = json_decode(wp_remote_retrieve_body($gr), true); $guild_info = ' | Server: ' . ($gd['name'] ?? $guild); } else { $guild_info = ' | ⚠️ Server nicht gefunden oder Bot kein Mitglied'; } } wp_send_json_success(['message' => 'Bot: ' . $bot_name . $guild_info]); }); // ── Discord-Cron: Rollen synchronisieren ────────────────────────────────────── add_action('wbf_discord_role_sync', 'wbf_run_discord_role_sync'); if ( ! wp_next_scheduled('wbf_discord_role_sync') ) { wp_schedule_event(time(), 'hourly', 'wbf_discord_role_sync'); } function wbf_run_discord_role_sync() { $s = wbf_get_settings(); if ( ($s['discord_role_sync'] ?? '0') !== '1' ) return; $token = trim($s['discord_bot_token'] ?? ''); $guild = trim($s['discord_guild_id'] ?? ''); $role_map = json_decode($s['discord_role_map'] ?? '{}', true) ?: []; if ( ! $token || ! $guild || empty($role_map) ) return; global $wpdb; // Alle verifizierten Discord-User holen (discord_user_id in user_meta gesetzt) $rows = $wpdb->get_results( "SELECT um.user_id, um.meta_value AS discord_user_id FROM {$wpdb->prefix}forum_user_meta um WHERE um.meta_key = 'discord_user_id' AND um.meta_value != ''" ); foreach ( $rows as $row ) { wbf_sync_discord_role_for_user((int)$row->user_id, $row->discord_user_id, $token, $guild, $role_map); } } /** * Synchronisiert die Discord-Serverrolle eines einzelnen Nutzers mit der Forum-Rolle. */ function wbf_sync_discord_role_for_user($forum_user_id, $discord_user_id, $token, $guild, $role_map) { // Guild-Member-Info abrufen $res = wp_remote_get("https://discord.com/api/v10/guilds/{$guild}/members/{$discord_user_id}", [ 'timeout' => 6, 'headers' => ['Authorization' => 'Bot ' . $token], ]); if ( is_wp_error($res) || wp_remote_retrieve_response_code($res) !== 200 ) return; $member = json_decode(wp_remote_retrieve_body($res), true); $user_roles = $member['roles'] ?? []; // Rollen-Map prüfen — erster Treffer gewinnt (Reihenfolge = Priorität) foreach ( $role_map as $dc_role_id => $forum_role ) { if ( in_array((string)$dc_role_id, array_map('strval', $user_roles), true) ) { $forum_user = WBF_DB::get_user($forum_user_id); if ( $forum_user && $forum_user->role !== 'superadmin' && $forum_user->role !== $forum_role ) { WBF_DB::update_user($forum_user_id, ['role' => $forum_role]); } return; } } } // ── Discord-Admin-Seite ─────────────────────────────────────────────────────── if ( ! function_exists('wbf_admin_discord') ) { function wbf_admin_discord() { if ( ! current_user_can('manage_options') ) return; $s = wbf_get_settings(); ?>

🎮 Discord-Integration

Konfiguriere den Discord-Bot und die Rollen-Synchronisation. Einstellungen werden in Einstellungen → Discord-Integration gespeichert.

🔌 Bot-Status

Kein Bot-Token konfiguriert.

Jetzt einrichten

✅ Bot-Token gespeichert

' . esc_html($guild) . '' : '⚠️ Keine Guild-ID gesetzt'; ?>

Rollen-Sync:

🔗 Aktive Rollen-Zuordnungen

Keine Zuordnungen konfiguriert.

Jetzt einrichten $fr_key): $fr_label = $all_roles[$fr_key]['label'] ?? $fr_key; ?>
Discord Rollen-IDForum-Rolle
Bearbeiten

👥 Verknüpfte Forum-Nutzer

get_results( "SELECT fu.id, fu.username, fu.display_name, fu.role, MAX(CASE WHEN um.meta_key='discord_username' THEN um.meta_value END) AS discord_name, MAX(CASE WHEN um.meta_key='discord_user_id' THEN um.meta_value END) AS discord_uid FROM {$wpdb->prefix}forum_users fu JOIN {$wpdb->prefix}forum_user_meta um ON um.user_id = fu.id WHERE um.meta_key IN ('discord_username','discord_user_id') GROUP BY fu.id HAVING discord_name != '' AND discord_name IS NOT NULL ORDER BY fu.username" ); if (empty($linked)): ?>

Noch keine verknüpften Nutzer.

Forum-NutzerRolleDiscord-NameDiscord-ID
display_name); ?> @username); ?> role); ?> discord_name ?: '–'); ?> discord_uid ?: '–'); ?>