From 28e574d04c828d93755d7d8fb1b3157ba7a61cbc Mon Sep 17 00:00:00 2001 From: M_Viper Date: Sat, 21 Mar 2026 18:47:43 +0100 Subject: [PATCH] Update from Git Manager GUI --- admin/forum-admin.php | 2436 ++++++++++++++++++++++++++++++++------ admin/forum-settings.php | 204 +++- 2 files changed, 2276 insertions(+), 364 deletions(-) diff --git a/admin/forum-admin.php b/admin/forum-admin.php index ec6b2d4..3786939 100644 --- a/admin/forum-admin.php +++ b/admin/forum-admin.php @@ -21,8 +21,16 @@ add_action( 'admin_menu', function() { add_submenu_page( 'wbf-admin', 'Level', 'Level', 'manage_options', 'wbf-levels', 'wbf_admin_levels' ); add_submenu_page( 'wbf-admin', 'Mitglieder', 'Mitglieder', 'manage_options', 'wbf-members', 'wbf_admin_members' ); add_submenu_page( 'wbf-admin', 'Meldungen', 'Meldungen', 'manage_options', 'wbf-reports', 'wbf_admin_reports' ); + add_submenu_page( 'wbf-admin', 'Profilfelder', 'Profilfelder', 'manage_options', 'wbf-profile-fields', 'wbf_admin_profile_fields' ); add_submenu_page( 'wbf-admin', 'Einstellungen', 'Einstellungen', 'manage_options', 'wbf-settings', 'wbf_admin_settings' ); + add_submenu_page( 'wbf-admin', 'Reaktionen', 'Reaktionen', 'manage_options', 'wbf-reactions', 'wbf_admin_reactions' ); + add_submenu_page( 'wbf-admin', 'Einladungen', 'Einladungen', 'manage_options', 'wbf-invites', 'wbf_admin_invites' ); + add_submenu_page( 'wbf-admin', 'Statistiken', 'Statistiken', 'manage_options', 'wbf-stats', 'wbf_admin_stats' ); + add_submenu_page( 'wbf-admin', 'Papierkorb', 'Papierkorb', 'manage_options', 'wbf-trash', 'wbf_admin_trash' ); + add_submenu_page( 'wbf-admin', 'Thread-Präfixe','Thread-Präfixe','manage_options', 'wbf-prefixes', 'wbf_admin_prefixes' ); + add_submenu_page( 'wbf-admin', 'Wortfilter', 'Wortfilter', 'manage_options', 'wbf-wordfilter', 'wbf_admin_wordfilter' ); add_submenu_page( 'wbf-admin', 'Export / Import','Export / Import','manage_options', 'wbf-export', 'wbf_admin_export' ); + add_submenu_page( 'wbf-admin', '⚠️ Deinstallieren', '⚠️ Deinstallieren', 'manage_options', 'wbf-uninstall', 'wbf_admin_uninstall' ); }, 10 ); // Meldungs-Badge im Menü (separater Hook mit Priorität 999, läuft nach der Registrierung) @@ -39,6 +47,19 @@ add_action( 'admin_menu', function() { } }, 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; @@ -61,7 +82,8 @@ add_action( 'admin_init', function() { foreach ( $sections as $sec ) { switch ( $sec ) { case 'settings': - $data['settings'] = get_option('wbf_settings', []); + $data['settings'] = get_option('wbf_settings', []); + $data['profile_fields'] = get_option('wbf_profile_fields', []); break; case 'roles': $data['roles'] = get_option('wbf_custom_roles', []); @@ -81,10 +103,15 @@ add_action( 'admin_init', function() { case 'users': $data['users'] = $wpdb->get_results( "SELECT id, username, email, password, display_name, avatar_url, bio, signature, - role, post_count, registered, last_active, ban_reason + 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 ); @@ -96,6 +123,7 @@ add_action( 'admin_init', function() { 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 'interactions': $data['likes'] = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}forum_likes ORDER BY id ASC", ARRAY_A ); @@ -108,6 +136,9 @@ add_action( 'admin_init', function() { 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; } } @@ -128,306 +159,666 @@ add_action( 'admin_init', function() { 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(6); - $members = WBF_DB::get_all_users(5); - $newest = $stats['newest'] ?? '—'; + $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(); - $admin_url = admin_url('admin.php'); + // ── 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'"); + + // ── 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)); + + // ── 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)); + }; ?> -
-

Business Forum — Dashboard

- -
- -
- Forum-Shortcode - Füge [business_forum] auf einer Seite ein, um das Forum anzuzeigen. +
+ + +
+
+ +
+
WP Business Forum
+
Admin Dashboard · v ·
- - -
- 'fas fa-comments', 'color'=>'#eff6ff','icolor'=>'#3b82f6', 'val'=>$stats['threads'], 'label'=>'Threads'], - ['icon'=>'fas fa-comment-dots','color'=>'#f0fdf4','icolor'=>'#22c55e', 'val'=>$stats['posts'], 'label'=>'Beiträge'], - ['icon'=>'fas fa-users', 'color'=>'#fdf4ff','icolor'=>'#a855f7', 'val'=>$stats['members'], 'label'=>'Mitglieder'], - ['icon'=>'fas fa-flag', 'color'=>'#fff7ed','icolor'=>'#f97316', 'val'=>$open_reports, 'label'=>'Meldungen offen'], - ['icon'=>'fas fa-tags', 'color'=>'#f0fdfa','icolor'=>'#14b8a6', 'val'=>0, 'label'=>'Tags gesamt'], - ]; - // Get tag count - global $wpdb; - $stat_items[4]['val'] = (int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_tags"); - foreach ( $stat_items as $si ): - $link = ''; - if ($si['label']==='Offene Meldungen') $link = esc_url(add_query_arg(['page'=>'wbf-reports'], $admin_url)); - if ($si['label']==='Mitglieder') $link = esc_url(add_query_arg(['page'=>'wbf-members'], $admin_url)); - ?> - -
-
- -
-
-
-
-
-
-
- +
+ + Wartungsmodus aktiv + + Forum online + + + Einstellungen + + + + Forum öffnen + +
- - -
- - -
- -
-
- Aktive Rollen -
-
-
- $role ): - $color = esc_attr($role['color']); $bg = esc_attr($role['bg_color']); ?> - - - - Lvl - - -
-

- - Superadmin ist automatisch an den WordPress-Administrator gebunden. -

-
-
- -
-
- Schnellzugriff -
-
- -
-
-
- - -
- -
-
- Letzte Threads -
-
- -

Noch keine Threads vorhanden.

- - - -
-
- -
-
- Neueste Mitglieder -
-
- -

Noch keine Mitglieder.

- -
    - role); - $col = esc_attr($role['color']); - $bg = esc_attr($role['bg_color']); - ?> -
  • - -
    - - display_name); ?> - -
    - - - - -
    -
    - post_count; ?> Beitr. -
  • - -
- -
-
- -
-
-
+ + + +
+
+ + 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 + +
+ + Tabellen OK + + Fehlende Tabellen: + + 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.

+ +
    + +
  • +
    +
    + title,0,42));?> +
    cat_name);?> · display_name);?>
    +
    + created_at)));?> +
  • + +
+ +
+
+ + +
+ + +
+
+ + Rollen + Bearbeiten → +
+
+ $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));?>"
+ +
+ + +
+
+ +
+ +
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' ), + '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']] ); @@ -644,6 +1036,8 @@ function wbf_admin_categories() { $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 ) @@ -686,7 +1080,7 @@ function wbf_admin_categories() { echo " ↳ " . esc_html( $c->name ) . " " . esc_html( $c->thread_count ) . " - " . esc_html( $crole['label'] ) . " + " . esc_html( $crole['label'] ) . "$clockbadge$cguestbadge {$reorder_c} Bearbeiten | Löschen "; } @@ -718,7 +1112,15 @@ function wbf_admin_categories() { Beschreibung Icon

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

- Min. Rolle + 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'; echo ''; submit_button( $edit ? 'Speichern' : 'Erstellen', 'primary', 'wbf_save_cat' ); @@ -730,6 +1132,39 @@ function wbf_admin_categories() { 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']; @@ -738,11 +1173,31 @@ function wbf_admin_members() { $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_reason'] = ''; + $update['ban_until'] = null; + $update['pre_ban_role'] = ''; } WBF_DB::update_user( $uid, $update ); echo '

Rolle aktualisiert!

'; + after_role_save:; } } @@ -757,15 +1212,28 @@ function wbf_admin_members() { $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 ); - } - if ( ! empty($update) ) { - WBF_DB::update_user( $uid, $update ); - echo '

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

'; + 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!

'; } } } @@ -773,7 +1241,60 @@ function wbf_admin_members() { $members = WBF_DB::get_all_users( 200 ); ?>
-

Mitglieder

+

+ Mitglieder + +

+ + + + @@ -833,34 +1354,121 @@ function wbf_admin_members() {
+ style="width:100%;max-width:340px;font-size:12px;height:26px;margin-bottom:4px"> +
+ + + 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 +
+
-
+
- + - + - + + + + + + + + + + id ); + ?> + + + + + + +

Min. 6 Zeichen

+ +

Max. 300 Zeichen

+
+ Weitere Profilangaben +
+
+ + + + + + + + + +
-
+
@@ -941,7 +1549,7 @@ function wbf_admin_reports() { " . esc_html( $r->reporter_name ?? '—' ) . "
@" . esc_html( $r->reporter_username ?? '' ) . " $reason_label$note_cell $preview…
- → Zum Beitrag + → 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 @@ -1136,6 +1744,582 @@ function wbf_level_form_fields( $level, $prefix ) { } + +// ── 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 / LinkNotizNutzungenAblaufErstellt vonAktionen
+
+ + 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. +
  3. Erstelle hier einen Einladungslink — wähle wie oft er genutzt werden kann und ob er abläuft
  4. +
  5. Teile den Link mit deinen gewünschten Nutzern (z.B. per Discord, E-Mail, etc.)
  6. +
  7. Besucher die den Link öffnen, sehen das Registrierungsformular mit vorausgefülltem Code
  8. +
  9. Nach erfolgreicher Registrierung wird der Code als verwendet markiert
  10. +
+
+
+ ['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): ?> + + + + + + + +
#NutzerBeiträ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) { - unset($cat['id']); // auto-increment + unset($cat['id']); $wpdb->insert("{$wpdb->prefix}forum_categories", $cat); } $imported[] = 'Kategorien (' . count($data['categories']) . ')'; @@ -1194,27 +2386,56 @@ function wbf_admin_export() { $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) continue; + 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'=>$exists]); + $wpdb->update("{$wpdb->prefix}forum_users", $u, ['id' => $uid]); } else { unset($u['id']); - $u['role'] = ($u['role'] === 'superadmin') ? 'member' : $u['role']; $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) { @@ -1222,95 +2443,106 @@ function wbf_admin_export() { $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_threads"); $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_thread_tags"); $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_tags"); + $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_subscriptions"); } foreach ($data['threads'] ?? [] as $t) { $wpdb->insert("{$wpdb->prefix}forum_threads", $t); } foreach ($data['posts'] ?? [] as $p) { $wpdb->insert("{$wpdb->prefix}forum_posts", $p); } - // Re-import tags + // Tags re-importieren if (!empty($data['thread_tags'])) { $tag_map = []; foreach ($data['thread_tags'] as $tt) { if (!isset($tag_map[$tt['slug']])) { - $existing_tag = $wpdb->get_var($wpdb->prepare( + $etag = $wpdb->get_var($wpdb->prepare( "SELECT id FROM {$wpdb->prefix}forum_tags WHERE slug=%s", $tt['slug'] )); - if (!$existing_tag) { - $wpdb->insert("{$wpdb->prefix}forum_tags", [ - 'name'=>$tt['name'],'slug'=>$tt['slug'],'use_count'=>$tt['use_count'] - ]); + 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']] = $existing_tag; + $tag_map[$tt['slug']] = $etag; } } - $wpdb->replace("{$wpdb->prefix}forum_thread_tags", [ - 'thread_id'=>$tt['thread_id'], 'tag_id'=>$tag_map[$tt['slug']] - ]); + $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'] ?? []) . ')'; } + // ── 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']); - $existing = $wpdb->get_var($wpdb->prepare( + 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'] - )); - if (!$existing) { $wpdb->insert("{$wpdb->prefix}forum_likes", $row); $count++; } + ))) { $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); - } + 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++; - } + 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++; - } + 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) { + 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']); - $wpdb->insert("{$wpdb->prefix}forum_reports", $row); + // 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++; } - $imported[] = "Meldungen ($count)"; + if ($count) $imported[] = "Einladungen ($count)"; } if ( empty($imported) ) { $notice = ['warning', 'Nichts importiert — Datei enthielt keine gültigen Abschnitte.']; } else { - $notice = ['success', '✅ Importiert: ' . implode(', ', $imported) . '. Erstellt: ' . $data['_meta']['exported'] . ' · Site: ' . esc_html($data['_meta']['site'] ?? '?')]; + $notice = ['success', '✅ Importiert: ' . implode(', ', $imported) . '. Erstellt: ' . esc_html($data['_meta']['exported']) . ' · Site: ' . esc_html($data['_meta']['site'] ?? '?')]; } } } @@ -1343,15 +2575,16 @@ function wbf_admin_export() { ['⚙️', 'Einstellungen', 'Forum-Texte, Auto-Logout, Labels'], - 'roles' => ['🛡️', 'Rollen', 'Alle Rollen inkl. Berechtigungen & Farben'], - 'levels' => ['⭐', 'Level-System', 'Level-Konfiguration & Status'], - 'categories' => ['📂', 'Kategorien', 'Alle Kategorien inkl. Hierarchie'], - 'users' => ['👥', 'Benutzer', 'Accounts inkl. Passwort-Hashes'], - 'threads' => ['💬', 'Threads & Posts', 'Alle Inhalte inkl. Tags'], - 'interactions' => ['❤️', 'Likes & Reaktionen', 'Likes, Reaktionen, Benachrichtigungen'], - 'messages' => ['✉️', 'Privatnachrichten', 'Alle DM-Konversationen'], - 'reports' => ['🚩', 'Meldungen', 'Alle gemeldeten Beiträge'], + 'settings' => ['⚙️', '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'], + 'interactions' => ['❤️', 'Likes & Reaktionen', 'Likes, Reaktionen, Benachrichtigungen'], + 'messages' => ['✉️', 'Privatnachrichten', 'Alle DM-Konversationen'], + 'reports' => ['🚩', 'Meldungen', 'Gemeldete Beiträge inkl. Status'], + 'invites' => ['📨', 'Einladungen', 'Alle Einladungscodes inkl. Nutzungsstand'], ]; foreach ($export_options as $key => [$icon, $label, $desc]): ?> @@ -1409,10 +2642,11 @@ function wbf_admin_export() { 'Kategorien überschreiben (löscht bestehende)', - 'import_force_users' => 'Benutzer aktualisieren (bei gleichem Username)', - 'import_force_threads' => 'Threads, Posts, Likes & Reaktionen überschreiben', + 'import_force_users' => 'Benutzer aktualisieren (bei gleichem Username) inkl. Profilfeld-Werte', + 'import_force_threads' => 'Threads, Posts, Likes, Reaktionen & Abonnements überschreiben', 'import_force_messages' => 'Privatnachrichten überschreiben', 'import_force_reports' => 'Meldungen überschreiben', + 'import_force_invites' => 'Einladungen überschreiben', ]; foreach ($overwrite_opts as $name => $label): ?>
+ + + + + + + + + + + + + + '','label'=>'','type'=>'text','placeholder'=>'','required'=>0,'public'=>1,'options'=>'']; + foreach ( $rows as $i => $f ): + ?> + + + + + + + + + + + + +
SchlüsselBezeichnungTypPlatzhalterOptionen (bei Auswahl)PflichtÖffentlich
+ > + +

Schlüssel kann nach Erstellung nicht geändert werden.

+ +
+ + + + + + > + + > + + +
+ +
+ + +
+ + +
+

ℹ️ Hinweise

+
    +
  • Schlüssel: Einmalig, nur Kleinbuchstaben/Zahlen/Unterstrich. Kann nach dem ersten Speichern nicht mehr geändert werden.
  • +
  • Typ URL: Wird automatisch als klickbarer Link dargestellt.
  • +
  • Typ Auswahlliste: Trage die Optionen zeilenweise ins Optionen-Feld ein.
  • +
  • Öffentlich: Wenn deaktiviert, sieht nur der Nutzer selbst das Feld in seinem Profil.
  • +
  • Pflicht: Verhindert das Speichern wenn das Feld leer ist.
  • +
+
+ + + 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 +
+ +
+ + +
+

label) : 'Neuer Präfix'; ?>

+
+ + + + + + + + + + + + + + + + + + + +
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. +

+ +
+
+
+ 'Mein Profil', 'sidebar_login' => 'Login / Registrieren', // Sicherheit - 'auto_logout_minutes' => '30', + 'auto_logout_minutes' => '30', + // Wartungsmodus + 'maintenance_mode' => '0', + 'maintenance_title' => 'Wartungsarbeiten', + 'maintenance_message' => 'Das Forum wird gerade gewartet. Bitte versuche es später erneut.', + 'registration_enabled' => '1', + 'registration_mode' => 'open', + 'post_edit_limit' => '30', + 'profile_public_default' => '1', + 'spam_min_seconds' => '30', // open | invite | disabled + 'flood_interval' => '30', // Flood Control: Sekunden zwischen Posts + 'invite_message' => 'Registrierung ist aktuell nur auf Einladung möglich.', + // Forum-Regeln / Nutzungsbedingungen + 'rules_enabled' => '1', + 'rules_accept_required' => '1', + 'rules_title' => 'Forum-Regeln & Nutzungsbedingungen', + 'rules_content' => "**1. Respektvoller Umgang**\nBehandle alle Mitglieder freundlich und respektvoll. Beleidigungen, Mobbing und Diskriminierung sind nicht toleriert.\n\n**2. Keine Spam-Inhalte**\nWerbung, Spam und irrelevante Links sind verboten.\n\n**3. Keine illegalen Inhalte**\nJegliche Inhalte, die gegen geltendes Recht verstoßen, sind streng verboten.\n\n**4. Themenrelevanz**\nBeiträge sollten zur jeweiligen Kategorie passen.\n\n**5. Urheberrecht**\nVeröffentliche keine Inhalte, an denen du keine Rechte besitzt.\n\n**6. Datenschutz**\nTeile keine persönlichen Daten anderer Personen ohne deren Zustimmung.\n\n**7. Moderations-Entscheidungen**\nEntscheidungen der Moderatoren sind zu respektieren. Bei Fragen wende dich direkt ans Team.\n\nVerstöße können zur Verwarnung oder dauerhaften Sperrung führen.", ]; $saved = get_option( 'wbf_settings', [] ); @@ -60,13 +76,25 @@ function wbf_admin_settings() { 'section_cats', 'section_recent', 'btn_new_thread', 'btn_login', 'btn_register', 'btn_logout', 'sidebar_profile', 'sidebar_login', - 'auto_logout_minutes', + 'auto_logout_minutes', 'post_edit_limit', 'spam_min_seconds', 'flood_interval', 'profile_public_default', + 'registration_enabled', 'registration_mode', 'invite_message', + 'maintenance_mode', 'maintenance_title', 'maintenance_message', + 'post_edit_limit', + 'profile_public_default', + 'spam_min_seconds', + 'rules_enabled', 'rules_accept_required', 'rules_title', ]; $settings = []; foreach ( $fields as $key ) { - $settings[ $key ] = sanitize_text_field( $_POST[ $key ] ?? '' ); + if ( in_array( $key, ['maintenance_message', 'rules_content'] ) ) { + $settings[$key] = sanitize_textarea_field($_POST[$key] ?? ''); + } else { + $settings[ $key ] = sanitize_text_field( $_POST[ $key ] ?? '' ); + } } + // rules_content separat (nicht in $fields, da textarea mit eigener Behandlung) + $settings['rules_content'] = sanitize_textarea_field( $_POST['rules_content'] ?? '' ); update_option( 'wbf_settings', $settings ); echo '

✅ Einstellungen gespeichert!

'; @@ -203,6 +231,176 @@ function wbf_admin_settings() { + +

+ ✏️ Beiträge & Spam-Schutz +

+ + + + + + + + + + + + + + + + +

+ 🔧 Wartungsmodus +

+ + + + + + + + + + + + + + + + +

+ 📝 Registrierung +

+ + + + + + + + + + + + +

+ 📜 Forum-Regeln / Nutzungsbedingungen +

+ + + + + + + + + + + + + + + + + + +