' . (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', []);
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 '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;
}
}
$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'");
// ── 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));
};
?>
WP Business Forum
Admin Dashboard · v ·
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
0?$banned_count:0],
['wbf-reports', 'fas fa-flag', 'Meldungen', $open_reports>0?$open_reports:0],
['wbf-levels', 'fas fa-star', 'Level-System'],
['wbf-reactions', 'fas fa-face-smile', 'Reaktionen'],
['wbf-invites', 'fas fa-envelope-open-text','Einladungen', $invite_count>0?$invite_count:0],
['wbf-stats', 'fas fa-chart-line', 'Statistiken'],
null,
['wbf-trash', 'fas fa-trash-can', 'Papierkorb', $deleted_count>0?$deleted_count:0, true],
['wbf-export', 'fas fa-database', 'Export / Import'],
['wbf-settings', 'fas fa-gear', 'Einstellungen'],
];
foreach ($nav as $n):
if ($n===null) { echo '
'; continue; }
[$pg,$ico,$lbl] = $n;
$badge = $n[3] ?? 0;
$danger = $n[4] ?? false;
?>
Live-Aktivität
letzte 15 Aktionen
$role):
$rc=esc_attr($role['color']); $rb=esc_attr($role['bg_color']);?>
Keine Mitglieder.
role); $mc=esc_attr($mr['color']); $mb=esc_attr($mr['bg_color']);?>
post_count;?> Beitr.
Keine Kategorien.
$cat):
$bpct=$max_cnt>0?round($cat->cnt/$max_cnt*100):0; $rc=$rk[$i]??'#94a3b8';?>
name,0,20));?>
cnt;?>
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 '';
}
}
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 '';
}
}
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 '';
echo '
Rolle Level Permissions Beschreibung Aktionen ';
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 "
$badge " . ( ( $role['locked'] ?? false ) ? '(🔒) ' : '' ) . "
" . esc_html( $role['level'] ) . "
$perms
" . esc_html( $role['description'] ?? '' ) . "
$actions
";
}
echo '
';
if ( $edit_role && $edit_key !== WBF_Roles::SUPERADMIN ) {
echo '
Bearbeiten: ' . esc_html( $edit_role['label'] ) . ' ';
echo '
';
}
echo '
Neue Rolle erstellen ';
}
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
Textfarbe
Hintergrund
Icon
FontAwesome 6, z.B. fas fa-crown
Beschreibung
Permissions ";
foreach ( $all_perms as $p => $label ) {
$checked = in_array( $p, $perms ) ? 'checked' : '';
echo " " . esc_html( $label ) . " ";
}
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 '';
} else {
$wpdb->insert( "{$wpdb->prefix}forum_categories", $data );
echo '';
}
}
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 '';
echo '
Kategorie Threads Min. Rolle Aktionen ';
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 =
''
. '';
echo "
" . esc_html( $p->name ) . "$lockbadge
" . esc_html( $p->thread_count ) . "
" . esc_html( $role['label'] ) . "
{$reorder_p} Bearbeiten | Löschen
";
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' );
$reorder_c =
''
. '';
echo "
↳ " . esc_html( $c->name ) . "
" . esc_html( $c->thread_count ) . "
" . esc_html( $crole['label'] ) . "$clockbadge$cguestbadge
{$reorder_c} Bearbeiten | Löschen
";
}
}
echo '
';
$parent_opts = '
— Keine (Elternkategorie) — ';
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 .= "
$indent" . esc_html( $c->name ) . " ";
}
$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 .= "
" . esc_html( $r['label'] ) . " (Lvl {$r['level']}) ";
}
echo '
' . ( $edit ? 'Bearbeiten: ' . esc_html( $edit->name ) : 'Neue Kategorie' ) . ' ';
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 '';
after_role_save:;
}
}
// ── 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!
';
}
}
}
$members = WBF_DB::get_all_users( 200 );
?>
Mitglieder
+ Neuen Nutzer anlegen
Neuen Forum-Nutzer anlegen
Nutzer anlegen
Abbrechen
# Nutzer E-Mail
Aktuelle Rolle Beiträge
Registriert Rolle ändern
role );
$color = esc_attr( $role['color'] );
$bg = esc_attr( $role['bg_color'] );
$icon = esc_attr( $role['icon'] ?? 'fas fa-user' );
$is_sa = ( $m->role === WBF_Roles::SUPERADMIN );
$ban_reason = esc_attr( $m->ban_reason ?? '' );
$opts = '';
foreach ( $roles as $k => $r ) {
if ( $k === WBF_Roles::SUPERADMIN ) continue;
$sel = $m->role === $k ? ' selected' : '';
$opts .= '' . esc_html( $r['label'] ) . ' ';
}
?>
id ); ?>
display_name ); ?>
@username ); ?>
email ); ?>
(WP-Admin)
post_count ); ?>
registered ) ) ); ?>
Automatisch (WP-Admin)
Speichern
Profil bearbeiten
Profil bearbeiten: display_name); ?>
Änderungen speichern
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 '
';
foreach ( $tabs as $k => $label ) {
$url = esc_url( add_query_arg( ['page' => 'wbf-reports', 'filter' => $k] ) );
$active = ( $filter === $k ) ? ' class="current"' : '';
echo "$label | ";
}
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 '
# Gemeldet von Grund Vorschau Thread Datum Status Aktionen ';
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 "
" . 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
";
}
echo '
';
}
// ── 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 '';
}
// ── 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 '';
} 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 '';
}
// ── 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 '';
}
$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 ) . "
" . ( $enabled ? '⏸ Deaktivieren' : '▶ Aktivieren' ) . "
";
// ── Level-Tabelle ────────────────────────────────────────────────────────
echo '
Aktuelle Level ';
echo '
#
Badge-Vorschau
Ab Beiträgen
Icon
Aktionen
';
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 "
" . ( $i + 1 ) . "
{$badge_html}
≥ " . esc_html( $l['min'] ) . " Beiträge
{$icon}
Bearbeiten
" . ( count($levels) > 1 ? " | Löschen " : '' ) . "
";
}
echo '
';
// ── Reset-Button ─────────────────────────────────────────────────────────
echo "
" . wp_nonce_field( 'wbf_levels_reset_nonce', '_wpnonce', true, false ) . "
↺ Auf Standard zurücksetzen
";
// ── Bearbeiten-Formular ──────────────────────────────────────────────────
if ( $edit_l !== null ) {
echo '
Level bearbeiten: ' . esc_html( $edit_l['label'] ) . ' ';
echo '
';
submit_button( 'Änderungen speichern', 'primary', 'wbf_save_level' );
echo ' ';
}
// ── Neues Level erstellen ────────────────────────────────────────────────
echo '
Neues Level erstellen ';
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)
✕
Reaktionen (durch Leerzeichen getrennt)
Emojis direkt eingeben oder unten aus der Bibliothek auswählen. Max. 20 Stück.
💾 Speichern
↺ Standard wiederherstellen
📚 Emoji-Bibliothek
$group_emojis): ?>
$group_emojis): ?>
🔍 Vorschau (wie im Forum)
0
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 '';
}
$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
Einladungslink generieren
📭
Noch keine Einladungen erstellt.
Code / Link
Notiz
Nutzungen
Ablauf
Erstellt von
Aktionen
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 kopieren
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
Setze den Registrierungsmodus in Einstellungen auf Nur Einladung
Erstelle hier einen Einladungslink — wähle wie oft er genutzt werden kann und ob er abläuft
Teile den Link mit deinen gewünschten Nutzern (z.B. per Discord, E-Mail, etc.)
Besucher die den Link öffnen, sehen das Registrierungsformular mit vorausgefülltem Code
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)
#
Nutzer
Beiträge
$p): ?>
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 '';
}
$items = WBF_DB::get_deleted_content(100);
?>
🗑️ Papierkorb
Gelöschte Inhalte können hier wiederhergestellt oder endgültig entfernt werden.
Typ Inhalt Kategorie / Thread Autor Gelöscht am Aktionen
type==='thread' ? '🧵 Thread ' : '💬 Post '; ?>
content_preview); ?>
cat_name); ?>
display_name); ?>
deleted_at)); ?>
↺ Wiederherstellen
✕ Löschen
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']);
$wpdb->insert("{$wpdb->prefix}forum_categories", $cat);
}
$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) {
$wpdb->query("TRUNCATE TABLE {$wpdb->prefix}forum_posts");
$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); }
// 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'] ?? []) . ')';
}
// ── 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)";
}
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'],
'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]):
?>
Als JSON exportieren
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.
Backup-Datei (.json)
Importieren
Nur kompatible WBF-Backups
Export-Inhalte im Überblick
$raw_key ) {
$key = sanitize_key( $raw_key );
if ( ! $key ) continue;
// Reservierte Keys sperren
if ( in_array($key, ['username','email','password','display_name','bio','signature','avatar_url','role']) ) continue;
$fields[] = [
'key' => $key,
'label' => sanitize_text_field( $labels[$i] ?? $key ),
'type' => in_array($types[$i] ?? '', ['text','url','textarea','select','number']) ? $types[$i] : 'text',
'placeholder' => sanitize_text_field( $placeholders[$i] ?? '' ),
'required' => ! empty( $required[$i] ) ? 1 : 0,
'public' => ! empty( $public[$i] ) ? 1 : 0,
'options' => sanitize_textarea_field( $options[$i] ?? '' ),
];
}
WBF_DB::save_profile_field_defs( $fields );
echo '✅ Profilfelder gespeichert!
';
}
$fields = WBF_DB::get_profile_field_defs();
$types = ['text'=>'Text','url'=>'URL/Link','textarea'=>'Mehrzeiliger Text','select'=>'Auswahlliste','number'=>'Zahl'];
?>
👤 Benutzerdefinierte Profilfelder
Felder die Nutzer in ihrem Profil ausfüllen können — z.B. Website, Ort, Discord-Name, etc.
+ Feld hinzufügen
ℹ️ 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.
Alle Forumsdaten unwiderruflich löschen
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 '';
} else {
WBF_DB::create_prefix( $data );
echo '';
}
}
}
$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.
Vorschau Reihenfolge Aktionen
id}", "delete_prefix_{$px->id}"); ?>
label); ?>
sort_order; ?>
Bearbeiten |
Löschen
label) : 'Neuer Präfix'; ?>
✅ 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.
Gesperrte Wörter (eines pro Zeile)
Auch Phrasen mit Leerzeichen sind möglich (z.B. "böse phrase"). Regex wird nicht unterstützt.
💾 Wortfilter speichern