1469 lines
84 KiB
PHP
1469 lines
84 KiB
PHP
<?php
|
|
/**
|
|
* forum-admin.php
|
|
* Backend-Verwaltung für WP Business Forum.
|
|
* Enthält: Dashboard, Rollen, Kategorien, Mitglieder, Meldungen.
|
|
* Einstellungen (wbf_admin_settings + wbf_get_settings) → admin/forum-settings.php
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
|
|
|
// ── Menü ──────────────────────────────────────────────────────────────────────
|
|
|
|
add_action( 'admin_menu', function() {
|
|
add_menu_page(
|
|
'Business Forum', 'Business Forum', 'manage_options',
|
|
'wbf-admin', 'wbf_admin_page', 'dashicons-format-chat', 30
|
|
);
|
|
add_submenu_page( 'wbf-admin', 'Übersicht', 'Übersicht', 'manage_options', 'wbf-admin', 'wbf_admin_page' );
|
|
add_submenu_page( 'wbf-admin', 'Kategorien', 'Kategorien', 'manage_options', 'wbf-categories', 'wbf_admin_categories' );
|
|
add_submenu_page( 'wbf-admin', 'Rollen', 'Rollen', 'manage_options', 'wbf-roles', 'wbf_admin_roles' );
|
|
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', 'Einstellungen', 'Einstellungen', 'manage_options', 'wbf-settings', 'wbf_admin_settings' );
|
|
add_submenu_page( 'wbf-admin', 'Export / Import','Export / Import','manage_options', 'wbf-export', 'wbf_admin_export' );
|
|
}, 10 );
|
|
|
|
// Meldungs-Badge im Menü (separater Hook mit Priorität 999, läuft nach der Registrierung)
|
|
add_action( 'admin_menu', function() {
|
|
$count = WBF_DB::count_open_reports();
|
|
if ( $count < 1 ) return;
|
|
global $submenu;
|
|
if ( ! isset( $submenu['wbf-admin'] ) ) return;
|
|
foreach ( $submenu['wbf-admin'] as &$item ) {
|
|
if ( $item[2] === 'wbf-reports' ) {
|
|
$item[0] .= ' <span class="awaiting-mod">' . (int) $count . '</span>';
|
|
break;
|
|
}
|
|
}
|
|
}, 999 );
|
|
|
|
// ── 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', []);
|
|
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, post_count, registered, last_active, ban_reason
|
|
FROM {$wpdb->prefix}forum_users ORDER BY 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
|
|
);
|
|
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;
|
|
}
|
|
}
|
|
|
|
$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 '<style>
|
|
/* ── Shared ───────────────────────────────────────────── */
|
|
.wbf-role-preview { display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:4px;border:1px solid;font-size:12px;font-weight:700; }
|
|
.wbf-perm-grid { display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:6px;margin-top:6px; }
|
|
.wbf-perm-item { display:flex;align-items:center;gap:6px;font-size:13px; }
|
|
.wbf-color-row { display:flex;align-items:center;gap:12px;flex-wrap:wrap; }
|
|
|
|
/* ── Dashboard ────────────────────────────────────────── */
|
|
.wbf-dash { max-width:1200px; }
|
|
.wbf-dash h1 { display:flex;align-items:center;gap:10px;margin-bottom:24px; }
|
|
.wbf-dash-grid {
|
|
display:grid;
|
|
grid-template-columns:repeat(auto-fill,minmax(180px,1fr));
|
|
gap:16px;
|
|
margin-bottom:28px;
|
|
}
|
|
.wbf-stat-card {
|
|
background:#fff;
|
|
border:1px solid #e5e7eb;
|
|
border-radius:10px;
|
|
padding:20px 18px;
|
|
display:flex;
|
|
align-items:center;
|
|
gap:16px;
|
|
box-shadow:0 1px 3px rgba(0,0,0,.06);
|
|
transition:box-shadow .15s;
|
|
height:80px;
|
|
box-sizing:border-box;
|
|
}
|
|
.wbf-stat-card:hover { box-shadow:0 3px 10px rgba(0,0,0,.1); }
|
|
.wbf-stat-card__icon {
|
|
width:46px;height:46px;border-radius:10px;
|
|
display:flex;align-items:center;justify-content:center;
|
|
font-size:1.25rem;flex-shrink:0;
|
|
}
|
|
.wbf-stat-card__num { font-size:1.9rem;font-weight:700;line-height:1; }
|
|
.wbf-stat-card__label{ font-size:.78rem;color:#6b7280;margin-top:2px;white-space:nowrap; }
|
|
|
|
.wbf-dash-row { display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:28px; }
|
|
@media(max-width:900px){ .wbf-dash-row{ grid-template-columns:1fr; } }
|
|
|
|
.wbf-dash-panel {
|
|
background:#fff;border:1px solid #e5e7eb;
|
|
border-radius:10px;overflow:hidden;
|
|
box-shadow:0 1px 3px rgba(0,0,0,.06);
|
|
}
|
|
.wbf-dash-panel__header {
|
|
padding:12px 18px;background:#f9fafb;
|
|
border-bottom:1px solid #e5e7eb;
|
|
display:flex;align-items:center;gap:8px;
|
|
font-size:.82rem;font-weight:700;text-transform:uppercase;
|
|
letter-spacing:.05em;color:#374151;
|
|
}
|
|
.wbf-dash-panel__header i { color:#00b4d8; }
|
|
.wbf-dash-panel__body { padding:16px 18px; }
|
|
|
|
.wbf-role-list { display:flex;flex-wrap:wrap;gap:8px; }
|
|
.wbf-role-chip {
|
|
display:inline-flex;align-items:center;gap:6px;
|
|
padding:5px 12px;border-radius:20px;border:1.5px solid;
|
|
font-size:.8rem;font-weight:700;
|
|
text-decoration:none;cursor:pointer;transition:opacity .15s;
|
|
}
|
|
.wbf-role-chip:hover { opacity:.8; }
|
|
|
|
.wbf-quick-links { display:flex;flex-direction:column;gap:6px; }
|
|
.wbf-quick-link {
|
|
display:flex;align-items:center;gap:10px;
|
|
padding:9px 12px;border-radius:7px;
|
|
background:#f3f4f6;border:1px solid transparent;
|
|
color:#374151;text-decoration:none;font-size:.875rem;
|
|
transition:background .12s,border-color .12s;
|
|
}
|
|
.wbf-quick-link:hover { background:#e5e7eb;border-color:#d1d5db;color:#111827; }
|
|
.wbf-quick-link i { width:18px;text-align:center;color:#00b4d8; }
|
|
.wbf-quick-link__badge {
|
|
margin-left:auto;background:#ef4444;color:#fff;
|
|
font-size:.7rem;font-weight:700;padding:1px 7px;
|
|
border-radius:20px;
|
|
}
|
|
|
|
.wbf-recent-list { list-style:none;margin:0;padding:0; }
|
|
.wbf-recent-item {
|
|
display:flex;align-items:center;gap:10px;
|
|
padding:9px 0;border-bottom:1px solid #f3f4f6;
|
|
font-size:.85rem;
|
|
}
|
|
.wbf-recent-item:last-child { border-bottom:none;padding-bottom:0; }
|
|
.wbf-recent-item i { color:#9ca3af;width:16px;text-align:center;flex-shrink:0; }
|
|
.wbf-recent-item a { color:#111827;text-decoration:none;font-weight:500; }
|
|
.wbf-recent-item a:hover { color:#00b4d8; }
|
|
.wbf-recent-item__meta { margin-left:auto;font-size:.75rem;color:#9ca3af;white-space:nowrap; }
|
|
|
|
.wbf-info-box {
|
|
background:linear-gradient(135deg,#0ea5e9 0%,#00b4d8 100%);
|
|
border-radius:10px;padding:18px 20px;color:#fff;
|
|
display:flex;align-items:center;gap:16px;
|
|
margin-bottom:28px;
|
|
}
|
|
.wbf-info-box i { font-size:2rem;opacity:.85; }
|
|
.wbf-info-box__text strong { display:block;font-size:1rem;margin-bottom:4px; }
|
|
.wbf-info-box__text span { font-size:.85rem;opacity:.85; }
|
|
.wbf-info-box code {
|
|
background:rgba(255,255,255,.25);padding:2px 8px;
|
|
border-radius:4px;font-family:monospace;
|
|
}
|
|
</style>';
|
|
} );
|
|
|
|
// ── Dashboard ─────────────────────────────────────────────────────────────────
|
|
|
|
function wbf_admin_page() {
|
|
$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'] ?? '—';
|
|
|
|
$admin_url = admin_url('admin.php');
|
|
?>
|
|
<div class="wrap wbf-dash">
|
|
<h1><i class="dashicons dashicons-format-chat" style="color:#00b4d8"></i> Business Forum — Dashboard</h1>
|
|
|
|
<!-- ── Info-Banner ─────────────────────────────────── -->
|
|
<div class="wbf-info-box">
|
|
<i class="dashicons dashicons-shortcode"></i>
|
|
<div class="wbf-info-box__text">
|
|
<strong>Forum-Shortcode</strong>
|
|
<span>Füge <code>[business_forum]</code> auf einer Seite ein, um das Forum anzuzeigen.</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Stat-Karten ─────────────────────────────────── -->
|
|
<div class="wbf-dash-grid">
|
|
<?php
|
|
$stat_items = [
|
|
['icon'=>'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));
|
|
?>
|
|
<?php if ($link): ?><a href="<?php echo $link; ?>" style="text-decoration:none"><?php endif; ?>
|
|
<div class="wbf-stat-card">
|
|
<div class="wbf-stat-card__icon" style="background:<?php echo esc_attr($si['color']); ?>">
|
|
<i class="<?php echo esc_attr($si['icon']); ?>" style="color:<?php echo esc_attr($si['icolor']); ?>"></i>
|
|
</div>
|
|
<div>
|
|
<div class="wbf-stat-card__num" style="color:<?php echo esc_attr($si['icolor']); ?>"><?php echo number_format((int)$si['val']); ?></div>
|
|
<div class="wbf-stat-card__label"><?php echo esc_html($si['label']); ?></div>
|
|
</div>
|
|
</div>
|
|
<?php if ($link): ?></a><?php endif; ?>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<!-- ── Haupt-Grid: Rollen + Quick Links | Neue Threads ── -->
|
|
<div class="wbf-dash-row">
|
|
|
|
<!-- Links: Rollen + Quick Links -->
|
|
<div style="display:flex;flex-direction:column;gap:20px">
|
|
|
|
<div class="wbf-dash-panel">
|
|
<div class="wbf-dash-panel__header">
|
|
<i class="fas fa-shield-halved"></i> Aktive Rollen
|
|
</div>
|
|
<div class="wbf-dash-panel__body">
|
|
<div class="wbf-role-list">
|
|
<?php foreach ( $roles as $key => $role ):
|
|
$color = esc_attr($role['color']); $bg = esc_attr($role['bg_color']); ?>
|
|
<a href="<?php echo esc_url(add_query_arg(['page'=>'wbf-roles','edit_role'=>$key], $admin_url)); ?>"
|
|
class="wbf-role-chip"
|
|
style="color:<?php echo $color; ?>;background:<?php echo $bg; ?>;border-color:<?php echo $color; ?>">
|
|
<i class="<?php echo esc_attr($role['icon'] ?? 'fas fa-user'); ?>"></i>
|
|
<?php echo esc_html($role['label']); ?>
|
|
<span style="opacity:.65;font-weight:400">Lvl <?php echo (int)$role['level']; ?></span>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<p style="margin:12px 0 0;font-size:.8rem;color:#6b7280">
|
|
<i class="fas fa-info-circle"></i>
|
|
<strong>Superadmin</strong> ist automatisch an den WordPress-Administrator gebunden.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wbf-dash-panel">
|
|
<div class="wbf-dash-panel__header">
|
|
<i class="fas fa-bolt"></i> Schnellzugriff
|
|
</div>
|
|
<div class="wbf-dash-panel__body">
|
|
<div class="wbf-quick-links">
|
|
<?php
|
|
$links = [
|
|
['page'=>'wbf-categories', 'icon'=>'fas fa-folder-open', 'label'=>'Kategorien verwalten'],
|
|
['page'=>'wbf-roles', 'icon'=>'fas fa-shield-halved','label'=>'Rollen bearbeiten'],
|
|
['page'=>'wbf-levels', 'icon'=>'fas fa-star', 'label'=>'Level-System'],
|
|
['page'=>'wbf-members', 'icon'=>'fas fa-users', 'label'=>'Mitglieder'],
|
|
['page'=>'wbf-reports', 'icon'=>'fas fa-flag', 'label'=>'Meldungen',
|
|
'badge'=> $open_reports > 0 ? $open_reports : 0],
|
|
['page'=>'wbf-settings', 'icon'=>'fas fa-gear', 'label'=>'Einstellungen'],
|
|
];
|
|
foreach ($links as $ln):
|
|
$href = esc_url(add_query_arg(['page'=>$ln['page']], $admin_url));
|
|
?>
|
|
<a href="<?php echo $href; ?>" class="wbf-quick-link">
|
|
<i class="<?php echo esc_attr($ln['icon']); ?>"></i>
|
|
<?php echo esc_html($ln['label']); ?>
|
|
<?php if (!empty($ln['badge'])): ?>
|
|
<span class="wbf-quick-link__badge"><?php echo (int)$ln['badge']; ?></span>
|
|
<?php endif; ?>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Rechts: Letzte Threads + Neueste Mitglieder -->
|
|
<div style="display:flex;flex-direction:column;gap:20px">
|
|
|
|
<div class="wbf-dash-panel">
|
|
<div class="wbf-dash-panel__header">
|
|
<i class="fas fa-clock"></i> Letzte Threads
|
|
</div>
|
|
<div class="wbf-dash-panel__body">
|
|
<?php if (empty($recent)): ?>
|
|
<p style="color:#9ca3af;font-size:.875rem;margin:0">Noch keine Threads vorhanden.</p>
|
|
<?php else: ?>
|
|
<ul class="wbf-recent-list">
|
|
<?php foreach ($recent as $r): ?>
|
|
<li class="wbf-recent-item">
|
|
<i class="fas fa-comment-dots"></i>
|
|
<div style="min-width:0">
|
|
<a href="<?php echo esc_url(get_permalink() . '?forum_thread=' . (int)$r->id); ?>" target="_blank">
|
|
<?php echo esc_html(mb_substr($r->title, 0, 48)); ?>
|
|
</a>
|
|
<div style="font-size:.73rem;color:#9ca3af;margin-top:1px">
|
|
<?php echo esc_html($r->cat_name); ?> · <?php echo esc_html($r->display_name); ?>
|
|
</div>
|
|
</div>
|
|
<span class="wbf-recent-item__meta">
|
|
<?php echo esc_html(date_i18n('d.m.', strtotime($r->created_at))); ?>
|
|
</span>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wbf-dash-panel">
|
|
<div class="wbf-dash-panel__header">
|
|
<i class="fas fa-user-plus"></i> Neueste Mitglieder
|
|
</div>
|
|
<div class="wbf-dash-panel__body">
|
|
<?php if (empty($members)): ?>
|
|
<p style="color:#9ca3af;font-size:.875rem;margin:0">Noch keine Mitglieder.</p>
|
|
<?php else: ?>
|
|
<ul class="wbf-recent-list">
|
|
<?php foreach ($members as $m):
|
|
$role = WBF_Roles::get($m->role);
|
|
$col = esc_attr($role['color']);
|
|
$bg = esc_attr($role['bg_color']);
|
|
?>
|
|
<li class="wbf-recent-item">
|
|
<img src="<?php echo esc_url($m->avatar_url ?: 'https://www.gravatar.com/avatar/0?d=identicon&s=32'); ?>"
|
|
width="28" height="28"
|
|
style="border-radius:50%;object-fit:cover;flex-shrink:0">
|
|
<div style="min-width:0">
|
|
<a href="<?php echo esc_url(add_query_arg(['page'=>'wbf-members'], $admin_url)); ?>">
|
|
<?php echo esc_html($m->display_name); ?>
|
|
</a>
|
|
<div style="margin-top:2px">
|
|
<span style="font-size:.7rem;font-weight:700;color:<?php echo $col; ?>;background:<?php echo $bg; ?>;padding:1px 7px;border-radius:20px;border:1px solid <?php echo $col; ?>">
|
|
<i class="<?php echo esc_attr($role['icon'] ?? 'fas fa-user'); ?>"></i>
|
|
<?php echo esc_html($role['label']); ?>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<span class="wbf-recent-item__meta"><?php echo (int)$m->post_count; ?> Beitr.</span>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
// ── Rollen ────────────────────────────────────────────────────────────────────
|
|
|
|
function wbf_admin_roles() {
|
|
$all_perms = WBF_Roles::all_permissions();
|
|
|
|
if ( isset( $_POST['wbf_save_role'] ) && check_admin_referer( 'wbf_role_edit_nonce' ) ) {
|
|
$key = sanitize_key( $_POST['role_key'] ?? '' );
|
|
if ( $key && $key !== WBF_Roles::SUPERADMIN ) {
|
|
$perms = [];
|
|
foreach ( $all_perms as $p => $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 '<div class="notice notice-success is-dismissible"><p>Rolle gespeichert!</p></div>';
|
|
}
|
|
}
|
|
|
|
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 '<div class="notice notice-success is-dismissible"><p>Neue Rolle erstellt!</p></div>';
|
|
}
|
|
}
|
|
|
|
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 '<div class="notice notice-success is-dismissible"><p>Rolle gelöscht. Nutzer auf "member" gesetzt.</p></div>';
|
|
} else {
|
|
echo '<div class="notice notice-error"><p>Diese Rolle kann nicht gelöscht werden.</p></div>';
|
|
}
|
|
}
|
|
|
|
$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 '<div class="wrap"><h1>Rollen-Verwaltung <a href="?page=wbf-roles#new-role" class="page-title-action">+ Neue Rolle</a></h1>';
|
|
echo '<table class="widefat striped" style="margin-bottom:2rem">
|
|
<thead><tr><th style="width:180px">Rolle</th><th>Level</th><th>Permissions</th><th>Beschreibung</th><th>Aktionen</th></tr></thead><tbody>';
|
|
|
|
foreach ( $roles as $key => $role ) {
|
|
$color = esc_attr( $role['color'] ); $bg = esc_attr( $role['bg_color'] );
|
|
$badge = "<span class='wbf-role-preview' style='color:{$color};background:{$bg};border-color:{$color}'>
|
|
<i class='" . esc_attr( $role['icon'] ?? 'fas fa-user' ) . "'></i> " . esc_html( $role['label'] ) . "</span>";
|
|
$perms = implode( ', ', array_map( fn($p) => esc_html( $all_perms[$p] ?? $p ), $role['permissions'] ?? [] ) );
|
|
if ( in_array( 'all', $role['permissions'] ?? [] ) ) $perms = '<em>Alle Rechte</em>';
|
|
|
|
$actions = '';
|
|
if ( $key !== WBF_Roles::SUPERADMIN ) {
|
|
$actions .= "<a href='?page=wbf-roles&edit_role={$key}'>Bearbeiten</a>";
|
|
if ( ! in_array( $key, ['member'] ) ) {
|
|
$del_url = wp_nonce_url( "?page=wbf-roles&delete_role={$key}", "delete_role_{$key}" );
|
|
$actions .= " | <a href='" . esc_url( $del_url ) . "' style='color:#dc2626' onclick='return confirm(\"Rolle löschen?\")'>Löschen</a>";
|
|
}
|
|
} else {
|
|
$actions = '<em style="color:#999">Systemrolle — unveränderlich</em>';
|
|
}
|
|
|
|
echo "<tr>
|
|
<td>$badge " . ( ( $role['locked'] ?? false ) ? '<span style="color:#999;font-size:.8em">(🔒)</span>' : '' ) . "</td>
|
|
<td><strong>" . esc_html( $role['level'] ) . "</strong></td>
|
|
<td style='font-size:.82em;color:#555'>$perms</td>
|
|
<td style='font-size:.82em;color:#666'>" . esc_html( $role['description'] ?? '' ) . "</td>
|
|
<td>$actions</td>
|
|
</tr>";
|
|
}
|
|
echo '</tbody></table>';
|
|
|
|
if ( $edit_role && $edit_key !== WBF_Roles::SUPERADMIN ) {
|
|
echo '<h2>Bearbeiten: <em>' . esc_html( $edit_role['label'] ) . '</em></h2>';
|
|
echo '<form method="post"><table class="form-table">';
|
|
wp_nonce_field( 'wbf_role_edit_nonce' );
|
|
echo '<input type="hidden" name="role_key" value="' . esc_attr( $edit_key ) . '">';
|
|
wbf_role_form_fields( $edit_role, $all_perms, '' );
|
|
echo '</table>';
|
|
submit_button( 'Änderungen speichern', 'primary', 'wbf_save_role' );
|
|
echo '</form><hr>';
|
|
}
|
|
|
|
echo '<h2 id="new-role">Neue Rolle erstellen</h2><form method="post"><table class="form-table">';
|
|
wp_nonce_field( 'wbf_role_create_nonce' );
|
|
echo '<tr><th>Rollen-Schlüssel *</th><td>
|
|
<input name="new_role_key" placeholder="z.B. trusted_member" class="regular-text" pattern="[a-z0-9_]+" required>
|
|
<p class="description">Nur Kleinbuchstaben, Zahlen, Unterstriche. Unveränderlich.</p>
|
|
</td></tr>';
|
|
wbf_role_form_fields( null, $all_perms, 'new_' );
|
|
echo '</table>';
|
|
submit_button( 'Rolle erstellen', 'primary', 'wbf_create_role' );
|
|
echo '</form></div>';
|
|
}
|
|
|
|
function wbf_role_form_fields( $role, $all_perms, $prefix ) {
|
|
$v = fn($k, $d = '') => esc_attr( $role[$k] ?? $d );
|
|
$perms = $role['permissions'] ?? [];
|
|
echo "
|
|
<tr><th>Anzeigename *</th><td><input name='{$prefix}role_label' value='" . $v('label') . "' class='regular-text' required></td></tr>
|
|
<tr><th>Level</th><td>
|
|
<input name='{$prefix}role_level' type='number' value='" . $v('level','10') . "' class='small-text' min='-1' max='99'>
|
|
<p class='description'>Superadmin=100, Admin≤80, Mod≤50, Member=10, Banned=-1</p>
|
|
</td></tr>
|
|
<tr><th>Farbe</th><td class='wbf-color-row'>
|
|
<label>Textfarbe <input name='{$prefix}role_color' type='color' value='" . $v('color','#94a3b8') . "'></label>
|
|
<label>Hintergrund <input name='{$prefix}role_bg' value='" . $v('bg_color','rgba(148,163,184,.1)') . "' style='width:220px' placeholder='rgba(…)'></label>
|
|
</td></tr>
|
|
<tr><th>Icon</th><td>
|
|
<input name='{$prefix}role_icon' value='" . $v('icon','fas fa-user') . "' class='regular-text'>
|
|
<p class='description'>FontAwesome 6, z.B. <code>fas fa-crown</code></p>
|
|
</td></tr>
|
|
<tr><th>Beschreibung</th><td><textarea name='{$prefix}role_desc' rows='2' class='large-text'>" . $v('description') . "</textarea></td></tr>
|
|
<tr><th>Permissions</th><td><div class='wbf-perm-grid'>";
|
|
foreach ( $all_perms as $p => $label ) {
|
|
$checked = in_array( $p, $perms ) ? 'checked' : '';
|
|
echo "<label class='wbf-perm-item'><input type='checkbox' name='{$prefix}perm_{$p}' $checked> " . esc_html( $label ) . "</label>";
|
|
}
|
|
echo "</div></td></tr>";
|
|
}
|
|
|
|
// ── 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' ),
|
|
];
|
|
if ( ! empty( $_POST['cat_id'] ) ) {
|
|
$wpdb->update( "{$wpdb->prefix}forum_categories", $data, ['id' => (int) $_POST['cat_id']] );
|
|
echo '<div class="notice notice-success is-dismissible"><p>Gespeichert!</p></div>';
|
|
} else {
|
|
$wpdb->insert( "{$wpdb->prefix}forum_categories", $data );
|
|
echo '<div class="notice notice-success is-dismissible"><p>Erstellt!</p></div>';
|
|
}
|
|
}
|
|
|
|
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 '<div class="wrap"><h1>Kategorien <a href="?page=wbf-categories" class="page-title-action">+ Neue Kategorie</a></h1>';
|
|
echo '<table class="widefat fixed striped" style="margin-bottom:2rem">
|
|
<thead><tr><th>Kategorie</th><th>Threads</th><th>Min. Rolle</th><th>Aktionen</th></tr></thead><tbody>';
|
|
|
|
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'
|
|
? " <span style='color:" . esc_attr( $role['color'] ) . ";font-size:.8em'>🔒 " . esc_html( $role['label'] ) . "</span>" : '';
|
|
$reorder_p =
|
|
'<form method="post" style="display:inline-flex;gap:2px">'
|
|
. wp_nonce_field( 'wbf_reorder_nonce', '_wpnonce', true, false )
|
|
. '<input type="hidden" name="cat_id" value="' . (int)$p->id . '">'
|
|
. '<input type="hidden" name="parent_id" value="0">'
|
|
. '<input type="hidden" name="direction" value="up">'
|
|
. '<button type="submit" name="wbf_reorder_cat" value="1" style="background:none;border:1px solid #ccc;border-radius:3px;cursor:pointer;padding:1px 6px;font-size:13px">↑</button>'
|
|
. '</form>'
|
|
. '<form method="post" style="display:inline-flex;gap:2px">'
|
|
. wp_nonce_field( 'wbf_reorder_nonce', '_wpnonce', true, false )
|
|
. '<input type="hidden" name="cat_id" value="' . (int)$p->id . '">'
|
|
. '<input type="hidden" name="parent_id" value="0">'
|
|
. '<input type="hidden" name="direction" value="down">'
|
|
. '<button type="submit" name="wbf_reorder_cat" value="1" style="background:none;border:1px solid #ccc;border-radius:3px;cursor:pointer;padding:1px 6px;font-size:13px">↓</button>'
|
|
. '</form>';
|
|
echo "<tr style='background:#f0f0f0;font-weight:600'>
|
|
<td><i class='dashicons dashicons-category'></i> " . esc_html( $p->name ) . "$lockbadge</td>
|
|
<td>" . esc_html( $p->thread_count ) . "</td>
|
|
<td>" . esc_html( $role['label'] ) . "</td>
|
|
<td style='white-space:nowrap'>{$reorder_p} <a href='?page=wbf-categories&edit={$p->id}'>Bearbeiten</a> | <a href='" . esc_url( $d ) . "' onclick='return confirm(\"Löschen?\")'>Löschen</a></td>
|
|
</tr>";
|
|
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 =
|
|
'<form method="post" style="display:inline-flex;gap:2px">'
|
|
. wp_nonce_field( 'wbf_reorder_nonce', '_wpnonce', true, false )
|
|
. '<input type="hidden" name="cat_id" value="' . (int)$c->id . '">'
|
|
. '<input type="hidden" name="parent_id" value="' . (int)$p->id . '">'
|
|
. '<input type="hidden" name="direction" value="up">'
|
|
. '<button type="submit" name="wbf_reorder_cat" value="1" style="background:none;border:1px solid #ccc;border-radius:3px;cursor:pointer;padding:1px 6px;font-size:13px">↑</button>'
|
|
. '</form>'
|
|
. '<form method="post" style="display:inline-flex;gap:2px">'
|
|
. wp_nonce_field( 'wbf_reorder_nonce', '_wpnonce', true, false )
|
|
. '<input type="hidden" name="cat_id" value="' . (int)$c->id . '">'
|
|
. '<input type="hidden" name="parent_id" value="' . (int)$p->id . '">'
|
|
. '<input type="hidden" name="direction" value="down">'
|
|
. '<button type="submit" name="wbf_reorder_cat" value="1" style="background:none;border:1px solid #ccc;border-radius:3px;cursor:pointer;padding:1px 6px;font-size:13px">↓</button>'
|
|
. '</form>';
|
|
echo "<tr>
|
|
<td style='padding-left:2.5rem'>↳ " . esc_html( $c->name ) . "</td>
|
|
<td>" . esc_html( $c->thread_count ) . "</td>
|
|
<td>" . esc_html( $crole['label'] ) . "</td>
|
|
<td style='white-space:nowrap'>{$reorder_c} <a href='?page=wbf-categories&edit={$c->id}'>Bearbeiten</a> | <a href='" . esc_url( $cd ) . "' onclick='return confirm(\"Löschen?\")'>Löschen</a></td>
|
|
</tr>";
|
|
}
|
|
}
|
|
echo '</tbody></table>';
|
|
|
|
$parent_opts = '<option value="0">— Keine (Elternkategorie) —</option>';
|
|
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 .= "<option value='{$c->id}'$sel>$indent" . esc_html( $c->name ) . "</option>";
|
|
}
|
|
|
|
$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 .= "<option value='$k'$sel>" . esc_html( $r['label'] ) . " (Lvl {$r['level']})</option>";
|
|
}
|
|
|
|
echo '<h2>' . ( $edit ? 'Bearbeiten: ' . esc_html( $edit->name ) : 'Neue Kategorie' ) . '</h2>';
|
|
echo '<form method="post"><table class="form-table">';
|
|
wp_nonce_field( 'wbf_cat_nonce' );
|
|
if ( $edit ) echo '<input type="hidden" name="cat_id" value="' . esc_attr( $edit->id ) . '">';
|
|
echo '
|
|
<tr><th>Elternkategorie</th><td><select name="parent_id">' . $parent_opts . '</select></td></tr>
|
|
<tr><th>Name *</th><td><input name="name" value="' . esc_attr( $edit ? $edit->name : '' ) . '" class="regular-text" required></td></tr>
|
|
<tr><th>Beschreibung</th><td><textarea name="description" rows="2" class="large-text">' . esc_textarea( $edit ? $edit->description : '' ) . '</textarea></td></tr>
|
|
<tr><th>Icon</th><td><input name="icon" value="' . esc_attr( $edit ? $edit->icon : 'fas fa-comments' ) . '" class="regular-text">
|
|
<p class="description">z.B. <code>fas fa-home</code>, <code>fas fa-bug</code></p></td></tr>
|
|
<tr><th>Min. Rolle</th><td><select name="min_role">' . $role_opts . '</select></td></tr>
|
|
<tr><th>Reihenfolge</th><td><input name="sort_order" type="number" value="' . esc_attr( $edit ? $edit->sort_order : 0 ) . '" class="small-text"></td></tr>';
|
|
echo '</table>';
|
|
submit_button( $edit ? 'Speichern' : 'Erstellen', 'primary', 'wbf_save_cat' );
|
|
echo '</form></div>';
|
|
}
|
|
|
|
// ── Mitglieder ────────────────────────────────────────────────────────────────
|
|
|
|
function wbf_admin_members() {
|
|
$roles = WBF_Roles::get_sorted();
|
|
|
|
// ── 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'] ?? '' );
|
|
} else {
|
|
$update['ban_reason'] = '';
|
|
}
|
|
WBF_DB::update_user( $uid, $update );
|
|
echo '<div class="notice notice-success is-dismissible"><p>Rolle aktualisiert!</p></div>';
|
|
}
|
|
}
|
|
|
|
// ── 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 );
|
|
}
|
|
if ( ! empty($update) ) {
|
|
WBF_DB::update_user( $uid, $update );
|
|
echo '<div class="notice notice-success is-dismissible"><p>Profil von <strong>' . esc_html($user->display_name) . '</strong> aktualisiert!</p></div>';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$members = WBF_DB::get_all_users( 200 );
|
|
?>
|
|
<div class="wrap">
|
|
<h1>Mitglieder</h1>
|
|
<table class="widefat striped">
|
|
<thead>
|
|
<tr>
|
|
<th>#</th><th>Nutzer</th><th>E-Mail</th>
|
|
<th>Aktuelle Rolle</th><th>Beiträge</th>
|
|
<th>Registriert</th><th>Rolle ändern</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ( $members as $m ) :
|
|
$role = WBF_Roles::get( $m->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 .= '<option value="' . esc_attr($k) . '"' . $sel . '>' . esc_html( $r['label'] ) . '</option>';
|
|
}
|
|
?>
|
|
<tr>
|
|
<td><?php echo esc_html( $m->id ); ?></td>
|
|
<td>
|
|
<strong><?php echo esc_html( $m->display_name ); ?></strong><br>
|
|
<small style="color:#999">@<?php echo esc_html( $m->username ); ?></small>
|
|
</td>
|
|
<td><?php echo esc_html( $m->email ); ?></td>
|
|
<td>
|
|
<span class="wbf-role-preview" style="color:<?php echo $color; ?>;background:<?php echo $bg; ?>;border-color:<?php echo $color; ?>">
|
|
<i class="<?php echo $icon; ?>"></i> <?php echo esc_html( $role['label'] ); ?>
|
|
</span>
|
|
<?php if ( $is_sa ) : ?><em style="color:#999;font-size:.8em">(WP-Admin)</em><?php endif; ?>
|
|
</td>
|
|
<td><?php echo esc_html( $m->post_count ); ?></td>
|
|
<td><?php echo esc_html( date( 'd.m.Y', strtotime( $m->registered ) ) ); ?></td>
|
|
<td>
|
|
<?php if ( $is_sa ) : ?>
|
|
<em style="color:#999">Automatisch (WP-Admin)</em>
|
|
<?php else : ?>
|
|
<form method="post" style="display:flex;flex-direction:column;gap:5px">
|
|
<?php wp_nonce_field( 'wbf_member_role_nonce' ); ?>
|
|
<input type="hidden" name="user_id" value="<?php echo esc_attr( $m->id ); ?>">
|
|
<div style="display:flex;gap:6px;align-items:center">
|
|
<select name="new_role" style="height:28px"
|
|
onchange="var r=this.closest('form').querySelector('.wbf-ban-reason');r.style.display=this.value==='banned'?'block':'none'">
|
|
<?php echo $opts; ?>
|
|
</select>
|
|
<button type="submit" name="wbf_change_role" class="button button-small">Speichern</button>
|
|
<button type="button" class="button button-small"
|
|
onclick="var d=document.getElementById('wbf-edit-user-<?php echo (int)$m->id; ?>');d.style.display=d.style.display==='none'?'block':'none'">
|
|
Profil bearbeiten
|
|
</button>
|
|
</div>
|
|
<div class="wbf-ban-reason" style="display:<?php echo $m->role === 'banned' ? 'block' : 'none'; ?>;margin-top:3px">
|
|
<input type="text" name="ban_reason" value="<?php echo $ban_reason; ?>"
|
|
placeholder="Sperrgrund (wird dem Nutzer beim Login angezeigt)"
|
|
style="width:100%;max-width:340px;font-size:12px;height:26px">
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Inline Profil-Editor -->
|
|
<div id="wbf-edit-user-<?php echo (int)$m->id; ?>" style="display:none;margin-top:8px;padding:10px;background:#f9f9f9;border:1px solid #ddd;border-radius:4px;max-width:400px">
|
|
<strong style="font-size:13px">Profil bearbeiten: <?php echo esc_html($m->display_name); ?></strong>
|
|
<form method="post" style="margin-top:8px">
|
|
<?php wp_nonce_field( 'wbf_edit_user_nonce' ); ?>
|
|
<input type="hidden" name="user_id" value="<?php echo esc_attr( $m->id ); ?>">
|
|
<table style="width:100%;font-size:13px">
|
|
<tr>
|
|
<td style="padding:3px 6px 3px 0;white-space:nowrap"><label>Anzeigename</label></td>
|
|
<td><input type="text" name="display_name" value="<?php echo esc_attr($m->display_name); ?>" style="width:100%;height:26px;font-size:12px"></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding:3px 6px 3px 0;white-space:nowrap"><label>E-Mail</label></td>
|
|
<td><input type="email" name="email" value="<?php echo esc_attr($m->email); ?>" style="width:100%;height:26px;font-size:12px"></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding:3px 6px 3px 0;white-space:nowrap"><label>Neues Passwort</label></td>
|
|
<td>
|
|
<input type="password" name="new_password" placeholder="Leer = nicht ändern" style="width:100%;height:26px;font-size:12px">
|
|
<p style="margin:2px 0 0;font-size:11px;color:#666">Min. 6 Zeichen</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<div style="margin-top:8px">
|
|
<button type="submit" name="wbf_edit_user" class="button button-primary button-small">Änderungen speichern</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
// ── Meldungen ─────────────────────────────────────────────────────────────────
|
|
|
|
function wbf_admin_reports() {
|
|
|
|
if ( isset( $_GET['report_action'], $_GET['report_id'] ) && check_admin_referer( 'wbf_report_action' ) ) {
|
|
$rid = (int) $_GET['report_id'];
|
|
$action = sanitize_key( $_GET['report_action'] );
|
|
if ( in_array( $action, ['resolved', 'dismissed', 'open'] ) ) {
|
|
WBF_DB::update_report( $rid, $action );
|
|
echo '<div class="notice notice-success is-dismissible"><p>Meldung aktualisiert.</p></div>';
|
|
}
|
|
}
|
|
|
|
$filter = sanitize_key( $_GET['filter'] ?? 'open' );
|
|
$reports = WBF_DB::get_reports( $filter, 100 );
|
|
$count = WBF_DB::count_open_reports();
|
|
|
|
echo '<div class="wrap"><h1>Meldungen ' . ( $count > 0 ? '<span class="awaiting-mod">' . (int) $count . '</span>' : '' ) . '</h1>';
|
|
|
|
$tabs = ['open' => 'Offen', 'resolved' => 'Erledigt', 'dismissed' => 'Verworfen', 'all' => 'Alle'];
|
|
echo '<ul class="subsubsub" style="margin-bottom:1rem">';
|
|
foreach ( $tabs as $k => $label ) {
|
|
$url = esc_url( add_query_arg( ['page' => 'wbf-reports', 'filter' => $k] ) );
|
|
$active = ( $filter === $k ) ? ' class="current"' : '';
|
|
echo "<li><a href='$url'$active>$label</a> | </li>";
|
|
}
|
|
echo '</ul>';
|
|
|
|
if ( empty( $reports ) ) {
|
|
echo '<p style="color:#999">Keine Meldungen vorhanden.</p></div>';
|
|
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 '<table class="widefat striped">
|
|
<thead><tr><th>#</th><th>Gemeldet von</th><th>Grund</th><th>Vorschau</th><th>Thread</th><th>Datum</th><th>Status</th><th>Aktionen</th></tr></thead><tbody>';
|
|
|
|
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 = "<span style='color:{$sc};font-weight:600'>" . esc_html( ucfirst( $r->status ) ) . "</span>";
|
|
$note_cell = $r->note ? '<br><small style="color:#999">' . esc_html( $r->note ) . '</small>' : '';
|
|
|
|
$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 = "<a href='$res_url' class='button button-small' style='color:#56cf7e'>✔ Erledigt</a> ";
|
|
$actions .= "<a href='$dis_url' class='button button-small' style='color:#6b7a99'>✖ Verwerfen</a>";
|
|
} 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 = "<a href='$reopen_url' class='button button-small'>↩ Wieder öffnen</a>";
|
|
}
|
|
|
|
echo "<tr>
|
|
<td>" . esc_html( $r->id ) . "</td>
|
|
<td><strong>" . esc_html( $r->reporter_name ?? '—' ) . "</strong><br><small style='color:#999'>@" . esc_html( $r->reporter_username ?? '' ) . "</small></td>
|
|
<td><span style='background:#f59e0b22;color:#f59e0b;padding:2px 8px;border-radius:4px;font-size:.8em;font-weight:600'>$reason_label</span>$note_cell</td>
|
|
<td style='font-size:.82em;color:#555;max-width:200px'>$preview…<br>
|
|
<a href='" . esc_url( get_permalink() ) . "?forum_thread=" . esc_attr( $r->thread_id ) . "#post-" . esc_attr( $r->object_id ) . "' target='_blank' style='font-size:.78em'>→ Zum Beitrag</a></td>
|
|
<td style='font-size:.82em'>" . ( $r->thread_id ? esc_html( mb_substr( $r->thread_title ?? '', 0, 35 ) ) : '—' ) . "</td>
|
|
<td style='font-size:.82em;color:#666'>" . esc_html( date( 'd.m.Y H:i', strtotime( $r->created_at ) ) ) . "</td>
|
|
<td>$status_badge</td>
|
|
<td style='white-space:nowrap'>$actions</td>
|
|
</tr>";
|
|
}
|
|
|
|
echo '</tbody></table></div>';
|
|
}
|
|
|
|
// ── 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 '<div class="notice notice-success is-dismissible"><p>Einstellung gespeichert!</p></div>';
|
|
}
|
|
|
|
// ── Reset ────────────────────────────────────────────────────────────────
|
|
if ( isset( $_POST['wbf_reset_levels'] ) && check_admin_referer( 'wbf_levels_reset_nonce' ) ) {
|
|
WBF_Levels::reset_to_defaults();
|
|
echo '<div class="notice notice-success is-dismissible"><p>Level auf Standard zurückgesetzt.</p></div>';
|
|
}
|
|
|
|
// ── 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 '<div class="notice notice-success is-dismissible"><p>Level gelöscht.</p></div>';
|
|
} else {
|
|
echo '<div class="notice notice-error"><p>Mindestens ein Level muss vorhanden sein.</p></div>';
|
|
}
|
|
}
|
|
|
|
// ── 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 '<div class="notice notice-success is-dismissible"><p>Level aktualisiert!</p></div>';
|
|
}
|
|
|
|
// ── 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 '<div class="notice notice-success is-dismissible"><p>Neues Level erstellt!</p></div>';
|
|
}
|
|
|
|
$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 '<div class="wrap">';
|
|
echo '<h1>⭐ Level-System</h1>';
|
|
|
|
// ── Status-Toggle ────────────────────────────────────────────────────────
|
|
$status_color = $enabled ? '#56cf7e' : '#f05252';
|
|
$status_label = $enabled ? '✅ Aktiviert' : '❌ Deaktiviert';
|
|
echo "<div style='display:flex;align-items:center;gap:16px;background:#fff;border:1px solid #ddd;border-radius:8px;padding:16px 20px;margin-bottom:1.5rem;max-width:500px'>
|
|
<div>
|
|
<strong>Level-System ist:</strong>
|
|
<span style='color:{$status_color};font-weight:700;margin-left:8px'>{$status_label}</span>
|
|
<p style='margin:.4rem 0 0;color:#666;font-size:.85em'>Wenn deaktiviert, werden keine Level-Badges im Forum angezeigt.</p>
|
|
</div>
|
|
<form method='post' style='margin-left:auto;flex-shrink:0'>
|
|
" . wp_nonce_field( 'wbf_levels_toggle_nonce', '_wpnonce', true, false ) . "
|
|
<input type='hidden' name='levels_enabled' value='" . ( $enabled ? '' : '1' ) . "'>
|
|
<button type='submit' name='wbf_toggle_levels' class='button " . ( $enabled ? 'button-secondary' : 'button-primary' ) . "'>
|
|
" . ( $enabled ? '⏸ Deaktivieren' : '▶ Aktivieren' ) . "
|
|
</button>
|
|
</form>
|
|
</div>";
|
|
|
|
// ── Level-Tabelle ────────────────────────────────────────────────────────
|
|
echo '<h2>Aktuelle Level</h2>';
|
|
echo '<table class="widefat striped" style="margin-bottom:2rem;max-width:760px">
|
|
<thead><tr>
|
|
<th style="width:60px">#</th>
|
|
<th>Badge-Vorschau</th>
|
|
<th>Ab Beiträgen</th>
|
|
<th>Icon</th>
|
|
<th>Aktionen</th>
|
|
</tr></thead><tbody>';
|
|
|
|
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 = "<span style='display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:700;
|
|
color:{$color};border:1.5px solid {$color};background:rgba(0,0,0,.07);
|
|
padding:2px 9px;border-radius:20px;'>
|
|
<i class='{$icon}'></i> {$label}
|
|
</span>";
|
|
|
|
echo "<tr>
|
|
<td><strong>" . ( $i + 1 ) . "</strong></td>
|
|
<td>{$badge_html}</td>
|
|
<td><strong>≥ " . esc_html( $l['min'] ) . "</strong> Beiträge</td>
|
|
<td><code style='font-size:.8em'>{$icon}</code></td>
|
|
<td style='white-space:nowrap'>
|
|
<a href='?page=wbf-levels&edit_level={$i}'>Bearbeiten</a>
|
|
" . ( count($levels) > 1 ? " | <a href='" . esc_url($del_url) . "' style='color:#dc2626' onclick='return confirm(\"Level wirklich löschen?\")'>Löschen</a>" : '' ) . "
|
|
</td>
|
|
</tr>";
|
|
}
|
|
echo '</tbody></table>';
|
|
|
|
// ── Reset-Button ─────────────────────────────────────────────────────────
|
|
echo "<form method='post' style='margin-bottom:2rem'>
|
|
" . wp_nonce_field( 'wbf_levels_reset_nonce', '_wpnonce', true, false ) . "
|
|
<button type='submit' name='wbf_reset_levels' class='button'
|
|
onclick='return confirm(\"Alle Level auf Standard zurücksetzen?\")'>
|
|
↺ Auf Standard zurücksetzen
|
|
</button>
|
|
</form>";
|
|
|
|
// ── Bearbeiten-Formular ──────────────────────────────────────────────────
|
|
if ( $edit_l !== null ) {
|
|
echo '<h2>Level bearbeiten: <em>' . esc_html( $edit_l['label'] ) . '</em></h2>';
|
|
echo '<form method="post" style="max-width:500px"><table class="form-table">';
|
|
wp_nonce_field( 'wbf_levels_edit_nonce' );
|
|
echo '<input type="hidden" name="level_index" value="' . esc_attr( $edit_i ) . '">';
|
|
echo wbf_level_form_fields( $edit_l, '' );
|
|
echo '</table>';
|
|
submit_button( 'Änderungen speichern', 'primary', 'wbf_save_level' );
|
|
echo '</form><hr style="margin:2rem 0">';
|
|
}
|
|
|
|
// ── Neues Level erstellen ────────────────────────────────────────────────
|
|
echo '<h2 id="new-level">Neues Level erstellen</h2>';
|
|
echo '<form method="post" style="max-width:500px"><table class="form-table">';
|
|
wp_nonce_field( 'wbf_levels_create_nonce' );
|
|
echo wbf_level_form_fields( null, 'new_' );
|
|
echo '</table>';
|
|
submit_button( '+ Level erstellen', 'primary', 'wbf_create_level' );
|
|
echo '</form>';
|
|
|
|
echo '</div>';
|
|
}
|
|
|
|
// ── 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 "
|
|
<tr>
|
|
<th>Ab Beiträgen *</th>
|
|
<td>
|
|
<input name='{$prefix}level_min' type='number' value='{$min}' class='small-text' min='0' required>
|
|
<p class='description'>Level ab dieser Beitragsanzahl (0 = für alle).</p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Bezeichnung *</th>
|
|
<td><input name='{$prefix}level_label' value='{$label}' class='regular-text' required></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Farbe</th>
|
|
<td><input name='{$prefix}level_color' type='color' value='{$color}'></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Icon</th>
|
|
<td>
|
|
<input name='{$prefix}level_icon' value='{$icon}' class='regular-text'>
|
|
<p class='description'>FontAwesome 6, z.B. <code>fas fa-crown</code>, <code>fas fa-fire</code>, <code>fas fa-seedling</code></p>
|
|
</td>
|
|
</tr>";
|
|
}
|
|
|
|
|
|
// ── Export / Import ───────────────────────────────────────────────────────────
|
|
|
|
function wbf_admin_export() {
|
|
if ( ! current_user_can('manage_options') ) return;
|
|
|
|
$notice = '';
|
|
|
|
// Export wird von admin_init verarbeitet (vor HTTP-Header-Output)
|
|
|
|
// ── IMPORT ────────────────────────────────────────────────────────────────
|
|
if ( isset( $_POST['wbf_do_import'] ) && check_admin_referer('wbf_import_nonce') ) {
|
|
|
|
if ( empty( $_FILES['import_file']['tmp_name'] ) ) {
|
|
$notice = ['error', 'Keine Datei hochgeladen.'];
|
|
} else {
|
|
$raw = file_get_contents( $_FILES['import_file']['tmp_name'] );
|
|
$data = json_decode( $raw, true );
|
|
|
|
if ( ! $data || ! isset($data['_meta']) ) {
|
|
$notice = ['error', 'Ungültige Datei — kein gültiges WBF-Backup.'];
|
|
} else {
|
|
global $wpdb;
|
|
$imported = [];
|
|
$sections = $data['_meta']['sections'] ?? array_keys($data);
|
|
|
|
if ( ! empty($data['settings']) ) {
|
|
update_option('wbf_settings', $data['settings']);
|
|
$imported[] = 'Einstellungen';
|
|
}
|
|
if ( ! empty($data['roles']) ) {
|
|
// Superadmin-Schutz: niemals überschreiben
|
|
$roles = $data['roles'];
|
|
unset($roles['superadmin']);
|
|
$current = get_option('wbf_custom_roles', []);
|
|
$current['superadmin'] = WBF_Roles::get('superadmin');
|
|
update_option('wbf_custom_roles', array_merge($current, $roles));
|
|
$imported[] = 'Rollen';
|
|
}
|
|
if ( ! empty($data['levels']) ) {
|
|
if ( isset($data['levels']['config']) ) update_option('wbf_level_config', $data['levels']['config']);
|
|
if ( isset($data['levels']['enabled']) ) update_option('wbf_levels_enabled', $data['levels']['enabled']);
|
|
$imported[] = 'Level';
|
|
}
|
|
if ( ! empty($data['categories']) ) {
|
|
// Nur importieren wenn Tabelle leer oder Force-Flag gesetzt
|
|
$force = ! empty($_POST['import_force_cats']);
|
|
$existing = (int)$wpdb->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
|
|
$wpdb->insert("{$wpdb->prefix}forum_categories", $cat);
|
|
}
|
|
$imported[] = 'Kategorien (' . count($data['categories']) . ')';
|
|
} else {
|
|
$imported[] = 'Kategorien übersprungen (bereits vorhanden — „Überschreiben" aktivieren)';
|
|
}
|
|
}
|
|
if ( ! empty($data['users']) ) {
|
|
$force = ! empty($_POST['import_force_users']);
|
|
$count = 0;
|
|
foreach ($data['users'] as $u) {
|
|
$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) {
|
|
unset($u['id']);
|
|
$wpdb->update("{$wpdb->prefix}forum_users", $u, ['id'=>$exists]);
|
|
} else {
|
|
unset($u['id']);
|
|
$u['role'] = ($u['role'] === 'superadmin') ? 'member' : $u['role'];
|
|
$wpdb->insert("{$wpdb->prefix}forum_users", $u);
|
|
}
|
|
$count++;
|
|
}
|
|
$imported[] = "Benutzer ($count importiert)";
|
|
}
|
|
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");
|
|
}
|
|
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
|
|
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(
|
|
"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']
|
|
]);
|
|
$tag_map[$tt['slug']] = $wpdb->insert_id;
|
|
} else {
|
|
$tag_map[$tt['slug']] = $existing_tag;
|
|
}
|
|
}
|
|
$wpdb->replace("{$wpdb->prefix}forum_thread_tags", [
|
|
'thread_id'=>$tt['thread_id'], 'tag_id'=>$tag_map[$tt['slug']]
|
|
]);
|
|
}
|
|
}
|
|
$imported[] = 'Threads + Posts (' . count($data['threads']) . ' / ' . count($data['posts'] ?? []) . ')';
|
|
}
|
|
|
|
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(
|
|
"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++; }
|
|
}
|
|
$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)";
|
|
}
|
|
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)";
|
|
}
|
|
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)";
|
|
}
|
|
|
|
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'] ?? '?')];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── UI ─────────────────────────────────────────────────────────────────────
|
|
?>
|
|
<div class="wrap" style="max-width:860px">
|
|
<h1 style="display:flex;align-items:center;gap:10px">
|
|
<span class="dashicons dashicons-database-import" style="font-size:1.5rem;width:auto;color:#00b4d8"></span>
|
|
Export / Import
|
|
</h1>
|
|
|
|
<?php if ($notice): ?>
|
|
<div class="notice notice-<?php echo $notice[0]; ?> is-dismissible"><p><?php echo esc_html($notice[1]); ?></p></div>
|
|
<?php endif; ?>
|
|
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:20px">
|
|
|
|
<!-- ── EXPORT ─────────────────────────────────────────── -->
|
|
<div style="background:#fff;border:1px solid #e5e7eb;border-radius:10px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.06)">
|
|
<div style="padding:14px 18px;background:#f0fdf4;border-bottom:1px solid #dcfce7;display:flex;align-items:center;gap:8px">
|
|
<span class="dashicons dashicons-download" style="color:#16a34a;font-size:1.1rem;width:auto"></span>
|
|
<strong style="color:#15803d;font-size:.9rem;text-transform:uppercase;letter-spacing:.04em">Export</strong>
|
|
</div>
|
|
<div style="padding:18px">
|
|
<p style="color:#6b7280;font-size:.85rem;margin:0 0 14px">Wähle was exportiert werden soll. Die Datei wird als <code>.json</code> heruntergeladen.</p>
|
|
<form method="post" action="<?php echo esc_url( admin_url('admin.php?page=wbf-export') ); ?>">
|
|
<?php wp_nonce_field('wbf_export_nonce'); ?>
|
|
<table style="width:100%;border-collapse:collapse">
|
|
<?php
|
|
$export_options = [
|
|
'settings' => ['⚙️', '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'],
|
|
];
|
|
foreach ($export_options as $key => [$icon, $label, $desc]):
|
|
?>
|
|
<tr style="border-bottom:1px solid #f3f4f6">
|
|
<td style="padding:10px 0;width:36px;text-align:center;font-size:1.1rem"><?php echo $icon; ?></td>
|
|
<td style="padding:10px 8px">
|
|
<label style="display:block;font-weight:600;font-size:.875rem;color:#111827;cursor:pointer">
|
|
<?php echo esc_html($label); ?>
|
|
</label>
|
|
<span style="font-size:.78rem;color:#9ca3af"><?php echo esc_html($desc); ?></span>
|
|
</td>
|
|
<td style="text-align:right;padding:10px 0">
|
|
<input type="checkbox" name="export_sections[<?php echo $key; ?>]" value="1" checked
|
|
style="width:16px;height:16px;accent-color:#16a34a;cursor:pointer">
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</table>
|
|
<div style="margin-top:14px;padding-top:12px;border-top:1px solid #f3f4f6;display:flex;align-items:center;gap:10px">
|
|
<button type="submit" name="wbf_do_export" class="button button-primary" style="background:#16a34a;border-color:#16a34a">
|
|
<span class="dashicons dashicons-download" style="margin-top:3px"></span>
|
|
Als JSON exportieren
|
|
</button>
|
|
<span style="font-size:.78rem;color:#9ca3af">Datei wird sofort heruntergeladen</span>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── IMPORT ─────────────────────────────────────────── -->
|
|
<div style="background:#fff;border:1px solid #e5e7eb;border-radius:10px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.06)">
|
|
<div style="padding:14px 18px;background:#eff6ff;border-bottom:1px solid #dbeafe;display:flex;align-items:center;gap:8px">
|
|
<span class="dashicons dashicons-upload" style="color:#2563eb;font-size:1.1rem;width:auto"></span>
|
|
<strong style="color:#1d4ed8;font-size:.9rem;text-transform:uppercase;letter-spacing:.04em">Import</strong>
|
|
</div>
|
|
<div style="padding:18px">
|
|
<p style="color:#6b7280;font-size:.85rem;margin:0 0 14px">Lade eine zuvor exportierte <code>.json</code>-Datei hoch.</p>
|
|
|
|
<div style="background:#fff7ed;border:1px solid #fed7aa;border-radius:7px;padding:10px 12px;margin-bottom:14px;font-size:.82rem;color:#92400e">
|
|
<strong>⚠️ Hinweis:</strong> Benutzer-Import enthält Passwort-Hashes. Threads-Import kann bestehende Daten überschreiben. Mache vorher ein Backup.
|
|
</div>
|
|
|
|
<form method="post" enctype="multipart/form-data" action="<?php echo esc_url( admin_url('admin.php?page=wbf-export') ); ?>">
|
|
<?php wp_nonce_field('wbf_import_nonce'); ?>
|
|
<div style="margin-bottom:12px">
|
|
<label style="display:block;font-size:.82rem;font-weight:700;color:#374151;margin-bottom:5px;text-transform:uppercase;letter-spacing:.04em">
|
|
Backup-Datei (.json)
|
|
</label>
|
|
<input type="file" name="import_file" accept=".json,application/json"
|
|
style="width:100%;padding:6px;border:1.5px solid #d1d5db;border-radius:6px;font-size:.875rem">
|
|
</div>
|
|
|
|
<div style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:7px;padding:12px;margin-bottom:12px">
|
|
<p style="font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;margin:0 0 8px">Überschreiben-Optionen</p>
|
|
<?php
|
|
$overwrite_opts = [
|
|
'import_force_cats' => 'Kategorien überschreiben (löscht bestehende)',
|
|
'import_force_users' => 'Benutzer aktualisieren (bei gleichem Username)',
|
|
'import_force_threads' => 'Threads, Posts, Likes & Reaktionen überschreiben',
|
|
'import_force_messages' => 'Privatnachrichten überschreiben',
|
|
'import_force_reports' => 'Meldungen überschreiben',
|
|
];
|
|
foreach ($overwrite_opts as $name => $label): ?>
|
|
<label style="display:flex;align-items:center;gap:8px;font-size:.82rem;color:#374151;cursor:pointer;margin-bottom:5px">
|
|
<input type="checkbox" name="<?php echo $name; ?>" value="1"
|
|
style="width:15px;height:15px;accent-color:#dc2626;cursor:pointer">
|
|
<?php echo esc_html($label); ?>
|
|
</label>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<div style="display:flex;align-items:center;gap:10px">
|
|
<button type="submit" name="wbf_do_import" class="button button-primary"
|
|
onclick="return confirm('Import starten? Bestehende Daten können überschrieben werden.')"
|
|
style="background:#2563eb;border-color:#2563eb">
|
|
<span class="dashicons dashicons-upload" style="margin-top:3px"></span>
|
|
Importieren
|
|
</button>
|
|
<span style="font-size:.78rem;color:#9ca3af">Nur kompatible WBF-Backups</span>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Info-Box ────────────────────────────────────────────── -->
|
|
<div style="margin-top:20px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;padding:16px 20px">
|
|
<h3 style="margin:0 0 10px;font-size:.9rem;display:flex;align-items:center;gap:7px">
|
|
<span class="dashicons dashicons-info" style="color:#00b4d8;width:auto"></span>
|
|
Export-Inhalte im Überblick
|
|
</h3>
|
|
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px">
|
|
<?php
|
|
$info = [
|
|
['⚙️ Einstellungen', 'Forum-Texte, Labels, Buttons, Auto-Logout — keine DB-Daten'],
|
|
['🛡️ Rollen', 'Alle benutzerdefinierten Rollen mit Permissions & Design (Superadmin nie überschrieben)'],
|
|
['⭐ Level', 'Level-Bezeichnungen, Schwellenwerte, Icons, Farben, An/Aus-Status'],
|
|
['📂 Kategorien', 'Kategoriestruktur inkl. Eltern-Kind-Hierarchie, Icons, Min-Rolle'],
|
|
['👥 Benutzer', 'Alle Forum-Accounts mit gehashten Passwörtern — für Migration zwischen Sites'],
|
|
['💬 Threads & Posts', 'Alle Inhalte inkl. Tag-Zuordnungen — für vollständige Datenmigration'],
|
|
['❤️ Likes & Reaktionen', 'Alle Likes auf Threads/Posts + Emoji-Reaktionen + Benachrichtigungen'],
|
|
['✉️ Privatnachrichten', 'Alle DM-Konversationen zwischen Nutzern'],
|
|
['🚩 Meldungen', 'Gemeldete Beiträge inkl. Status (offen/erledigt/verworfen)'],
|
|
];
|
|
foreach ($info as [$title, $desc]): ?>
|
|
<div style="background:#fff;border:1px solid #e5e7eb;border-radius:7px;padding:10px 12px">
|
|
<strong style="font-size:.82rem;display:block;margin-bottom:3px"><?php echo $title; ?></strong>
|
|
<span style="font-size:.76rem;color:#9ca3af;line-height:1.4"><?php echo esc_html($desc); ?></span>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|