1743 lines
82 KiB
PHP
1743 lines
82 KiB
PHP
<?php
|
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
|
|
|
class WBF_Ajax {
|
|
// ── Discord-Rollen-Sync manuell anstoßen ────────────────────────────────
|
|
public static function handle_manual_discord_sync() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user || WBF_Roles::level($user->role) < 80) {
|
|
wp_send_json_error(['message' => 'Keine Berechtigung.']);
|
|
}
|
|
if (!function_exists('wbf_run_discord_role_sync')) {
|
|
wp_send_json_error(['message' => 'Sync-Funktion nicht gefunden.']);
|
|
}
|
|
// Sync anstoßen (läuft synchron, kann bei vielen Usern etwas dauern)
|
|
wbf_run_discord_role_sync();
|
|
wp_send_json_success(['message' => 'Discord-Rollen-Sync wurde ausgeführt.']);
|
|
}
|
|
|
|
// ── Discord-Rollen-Sync für einzelnen Nutzer ─────────────────────────────
|
|
public static function handle_discord_sync_user() {
|
|
self::verify();
|
|
$admin = WBF_Auth::get_current_user();
|
|
if ( ! $admin || WBF_Roles::level( $admin->role ) < 80 ) {
|
|
wp_send_json_error( [ 'message' => 'Keine Berechtigung.' ] );
|
|
}
|
|
|
|
$target_id = (int) ( $_POST['user_id'] ?? 0 );
|
|
if ( ! $target_id ) {
|
|
wp_send_json_error( [ 'message' => 'Keine Nutzer-ID.' ] );
|
|
}
|
|
|
|
$s = function_exists( 'wbf_get_settings' ) ? wbf_get_settings() : [];
|
|
$token = trim( $s['discord_bot_token'] ?? '' );
|
|
$guild = trim( $s['discord_guild_id'] ?? '' );
|
|
$role_map = json_decode( $s['discord_role_map'] ?? '{}', true ) ?: [];
|
|
|
|
if ( ! $token || ! $guild || empty( $role_map ) ) {
|
|
wp_send_json_error( [ 'message' => 'Discord nicht konfiguriert.' ] );
|
|
}
|
|
|
|
global $wpdb;
|
|
$discord_uid = $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT meta_value FROM {$wpdb->prefix}forum_user_meta
|
|
WHERE user_id = %d AND meta_key = 'discord_user_id'",
|
|
$target_id
|
|
) );
|
|
|
|
if ( ! $discord_uid ) {
|
|
wp_send_json_error( [ 'message' => 'Nutzer hat kein verknüpftes Discord-Konto.' ] );
|
|
}
|
|
|
|
// Beide Richtungen: Discord → Forum
|
|
if ( function_exists( 'wbf_sync_discord_role_for_user' ) ) {
|
|
wbf_sync_discord_role_for_user( $target_id, $discord_uid, $token, $guild, $role_map );
|
|
}
|
|
|
|
// Frisch geladene Rolle zurückgeben damit die UI sofort aktualisiert werden kann
|
|
$updated = WBF_DB::get_user( $target_id );
|
|
wp_send_json_success( [
|
|
'message' => 'Sync abgeschlossen.',
|
|
'new_role' => $updated ? $updated->role : '',
|
|
] );
|
|
}
|
|
|
|
public static function init() {
|
|
$actions = [
|
|
'wbf_login', 'wbf_register', 'wbf_logout',
|
|
'wbf_new_thread', 'wbf_new_post', 'wbf_toggle_like',
|
|
'wbf_update_profile', 'wbf_upload_avatar', 'wbf_upload_banner', 'wbf_upload_post_image',
|
|
'wbf_forgot_password', 'wbf_reset_password', 'wbf_load_more_messages',
|
|
'wbf_create_invite', 'wbf_delete_invite',
|
|
'wbf_toggle_subscribe', 'wbf_restore_content', 'wbf_toggle_profile_visibility',
|
|
'wbf_mod_action', 'wbf_report_post', 'wbf_edit_post', 'wbf_edit_thread', 'wbf_search', 'wbf_get_notifications', 'wbf_mark_notifications_read', 'wbf_move_thread', 'wbf_tag_suggest',
|
|
'wbf_set_reaction', 'wbf_send_message', 'wbf_get_inbox', 'wbf_get_conversation', 'wbf_mark_messages_read', 'wbf_get_online_users', 'wbf_user_suggest', 'wbf_delete_message', 'wbf_get_new_messages',
|
|
'wbf_delete_account',
|
|
'wbf_vote_poll',
|
|
'wbf_create_poll',
|
|
'wbf_toggle_bookmark',
|
|
'wbf_set_thread_prefix',
|
|
'wbf_toggle_ignore',
|
|
'wbf_change_email',
|
|
'wbf_save_notification_prefs',
|
|
'wbf_save_discord',
|
|
'wbf_discord_send_code',
|
|
'wbf_discord_verify_code',
|
|
'wbf_manual_discord_sync',
|
|
'wbf_discord_sync_user',
|
|
];
|
|
foreach ($actions as $action) {
|
|
add_action('wp_ajax_nopriv_' . $action, [__CLASS__, str_replace('wbf_','handle_',$action)]);
|
|
add_action('wp_ajax_' . $action, [__CLASS__, str_replace('wbf_','handle_',$action)]);
|
|
}
|
|
}
|
|
|
|
private static function verify() {
|
|
if (!check_ajax_referer('wbf_nonce','nonce',false)) {
|
|
wp_send_json_error(['message'=>'Sicherheitsfehler.']);
|
|
exit;
|
|
}
|
|
// Update last_active for online status tracking
|
|
if ( WBF_Auth::is_forum_logged_in() ) {
|
|
$u = WBF_Auth::get_current_user();
|
|
if ($u) WBF_DB::touch_last_active($u->id);
|
|
}
|
|
}
|
|
|
|
// ── Auth ──────────────────────────────────────────────────────────────────
|
|
|
|
public static function handle_login() {
|
|
// Brute-Force-Schutz: max. 10 Versuche pro IP in 15 Minuten
|
|
$ip_key = 'wbf_login_fail_' . md5( $_SERVER['REMOTE_ADDR'] ?? 'unknown' );
|
|
$fails = (int) get_transient( $ip_key );
|
|
if ( $fails >= 10 ) {
|
|
wp_send_json_error([
|
|
'message' => 'Zu viele fehlgeschlagene Loginversuche. Bitte warte 15 Minuten.',
|
|
'locked' => true,
|
|
]);
|
|
}
|
|
|
|
// Login braucht keinen Nonce — Credentials sind die Authentifizierung
|
|
$result = WBF_Auth::login(
|
|
sanitize_text_field($_POST['username'] ?? ''),
|
|
$_POST['password'] ?? ''
|
|
);
|
|
if ($result['success']) {
|
|
// Erfolgreicher Login: Fehlzähler löschen
|
|
delete_transient( $ip_key );
|
|
$u = $result['user'];
|
|
if ( ! empty($_POST['remember_me']) ) {
|
|
WBF_Auth::set_remember_cookie($u->id);
|
|
}
|
|
wp_send_json_success(['display_name'=>$u->display_name,'avatar_url'=>$u->avatar_url,'user_id'=>$u->id]);
|
|
} else {
|
|
// Fehlversuch zählen — außer bei gesperrten Konten (kein Passwortfehler)
|
|
if ( empty($result['banned']) ) {
|
|
set_transient( $ip_key, $fails + 1, 15 * MINUTE_IN_SECONDS );
|
|
}
|
|
wp_send_json_error($result);
|
|
}
|
|
}
|
|
|
|
public static function handle_register() {
|
|
// Brute-Force/Spam-Schutz: max. 5 Registrierungen pro IP pro Stunde
|
|
$reg_ip_key = 'wbf_reg_ip_' . md5( $_SERVER['REMOTE_ADDR'] ?? 'unknown' );
|
|
$reg_fails = (int) get_transient( $reg_ip_key );
|
|
if ( $reg_fails >= 5 ) {
|
|
wp_send_json_error(['message' => 'Zu viele Registrierungsversuche von dieser IP. Bitte warte eine Stunde.']);
|
|
}
|
|
|
|
// Spam-Schutz: Honeypot + Zeitlimit
|
|
if ( ! empty($_POST['wbf_website']) ) {
|
|
wp_send_json_error(['message' => 'Spam erkannt.']);
|
|
}
|
|
$min_secs = (int)(wbf_get_settings()['spam_min_seconds'] ?? 30);
|
|
if ( $min_secs > 0 ) {
|
|
$form_time = (int)($_POST['wbf_form_time'] ?? 0);
|
|
if ( $form_time > 0 && (time() - $form_time) < $min_secs ) {
|
|
wp_send_json_error(['message' => 'Bitte warte noch einen Moment, bevor du das Formular absendest.']);
|
|
}
|
|
}
|
|
// Registrierungsmodus prüfen
|
|
$reg_mode = wbf_get_settings()['registration_mode'] ?? 'open';
|
|
if ( $reg_mode === 'disabled' ) {
|
|
wp_send_json_error(['message' => 'Registrierung ist deaktiviert.']);
|
|
}
|
|
// Regel-Akzeptierung prüfen (wenn Pflicht aktiviert)
|
|
$rules_required = ( wbf_get_settings()['rules_accept_required'] ?? '1' ) === '1';
|
|
$rules_enabled = ( wbf_get_settings()['rules_enabled'] ?? '1' ) === '1';
|
|
if ( $rules_enabled && $rules_required && empty( $_POST['rules_accepted'] ) ) {
|
|
wp_send_json_error(['message' => 'Bitte akzeptiere die Forum-Regeln um fortzufahren.']);
|
|
}
|
|
if ( $reg_mode === 'invite' ) {
|
|
$code = strtoupper( trim( sanitize_text_field( $_POST['invite_code'] ?? '' ) ) );
|
|
if ( ! $code ) {
|
|
wp_send_json_error(['message' => 'Einladungscode erforderlich.', 'need_invite' => true]);
|
|
}
|
|
if ( ! WBF_DB::verify_invite( $code ) ) {
|
|
wp_send_json_error(['message' => 'Einladungscode ungültig oder abgelaufen.', 'need_invite' => true]);
|
|
}
|
|
}
|
|
$result = WBF_Auth::register(
|
|
sanitize_text_field($_POST['username'] ?? ''),
|
|
sanitize_email( $_POST['email'] ?? ''),
|
|
$_POST['password'] ?? '',
|
|
sanitize_text_field($_POST['display_name'] ?? '')
|
|
);
|
|
if ($result['success']) {
|
|
// Registrierungs-Zähler für IP erhöhen
|
|
set_transient( $reg_ip_key, $reg_fails + 1, HOUR_IN_SECONDS );
|
|
$u = $result['user'];
|
|
// Einladungscode einlösen
|
|
$reg_mode2 = wbf_get_settings()['registration_mode'] ?? 'open';
|
|
if ( $reg_mode2 === 'invite' ) {
|
|
$code2 = strtoupper( trim( sanitize_text_field( $_POST['invite_code'] ?? '' ) ) );
|
|
if ( $code2 ) WBF_DB::use_invite( $code2, $u->id );
|
|
}
|
|
wp_send_json_success(['display_name'=>$u->display_name,'avatar_url'=>$u->avatar_url,'user_id'=>$u->id]);
|
|
} else {
|
|
wp_send_json_error($result);
|
|
}
|
|
}
|
|
|
|
public static function handle_logout() {
|
|
// Kein Nonce-Check für Logout nötig — Session-Clearing ist sicher
|
|
WBF_Auth::logout();
|
|
wp_send_json_success(['message' => 'logged_out']);
|
|
}
|
|
|
|
// ── Threads ───────────────────────────────────────────────────────────────
|
|
|
|
public static function handle_new_thread() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
if (!WBF_DB::can($user, 'create_thread')) wp_send_json_error(['message'=>'Keine Berechtigung.']);
|
|
|
|
// Flood Control
|
|
if ( ! WBF_DB::check_flood( $user->id ) ) {
|
|
$secs = (int)( wbf_get_settings()['flood_interval'] ?? 30 );
|
|
wp_send_json_error(['message'=>"Bitte warte {$secs} Sekunden zwischen Beiträgen.", 'flood'=>true]);
|
|
}
|
|
|
|
$title = sanitize_text_field($_POST['title'] ?? '');
|
|
$content = WBF_BBCode::sanitize( $_POST['content'] ?? '' );
|
|
$category_id = (int)($_POST['category_id'] ?? 0);
|
|
$prefix_id = (int)($_POST['prefix_id'] ?? 0) ?: null;
|
|
|
|
if (strlen($title) < 5) wp_send_json_error(['message'=>'Titel zu kurz (min. 5 Zeichen).']);
|
|
if (!$category_id) wp_send_json_error(['message'=>'Keine Kategorie gewählt.']);
|
|
|
|
// Inhalt nur prüfen wenn KEIN Poll mitgeschickt wird
|
|
$has_poll = ! empty( sanitize_text_field($_POST['poll_question'] ?? '') );
|
|
if ( ! $has_poll && strlen($content) < 10 ) {
|
|
wp_send_json_error(['message'=>'Inhalt zu kurz (min. 10 Zeichen).']);
|
|
}
|
|
// Bei Umfrage ohne Inhalt: Platzhalter setzen
|
|
if ( $has_poll && strlen($content) < 1 ) {
|
|
$content = '—';
|
|
}
|
|
|
|
$cat = WBF_DB::get_category($category_id);
|
|
if (!$cat || !WBF_DB::can_post_in($user, $cat)) wp_send_json_error(['message'=>'Keine Berechtigung für diese Kategorie.']);
|
|
|
|
$id = WBF_DB::create_thread([
|
|
'category_id' => $category_id,
|
|
'user_id' => $user->id,
|
|
'title' => WBF_DB::apply_word_filter($title),
|
|
'slug' => sanitize_title($title) . '-' . time(),
|
|
'content' => WBF_DB::apply_word_filter($content),
|
|
'prefix_id' => $prefix_id,
|
|
]);
|
|
|
|
// Tags speichern
|
|
$raw_tags = sanitize_text_field( $_POST['tags'] ?? '' );
|
|
if ( $raw_tags ) {
|
|
WBF_DB::sync_thread_tags( $id, $raw_tags );
|
|
}
|
|
|
|
// Umfrage erstellen (optional)
|
|
$poll_question = sanitize_text_field( $_POST['poll_question'] ?? '' );
|
|
$poll_opts_raw = $_POST['poll_options'] ?? [];
|
|
if ( $poll_question && is_array($poll_opts_raw) ) {
|
|
$poll_options = array_values( array_filter( array_map( 'sanitize_text_field', $poll_opts_raw ) ) );
|
|
if ( count($poll_options) >= 2 ) {
|
|
$poll_multi = ! empty($_POST['poll_multi']) ? true : false;
|
|
$poll_ends = sanitize_text_field( $_POST['poll_ends_at'] ?? '' );
|
|
$poll_ends_dt = null;
|
|
if ( $poll_ends ) {
|
|
$ts = strtotime($poll_ends);
|
|
if ( $ts && $ts > time() ) $poll_ends_dt = date('Y-m-d H:i:s', $ts);
|
|
}
|
|
WBF_DB::create_poll( $id, $poll_question, $poll_options, $poll_multi, $poll_ends_dt );
|
|
}
|
|
}
|
|
|
|
wp_send_json_success(['thread_id'=>$id,'message'=>'Thread erstellt!']);
|
|
}
|
|
|
|
// ── Posts ─────────────────────────────────────────────────────────────────
|
|
|
|
public static function handle_new_post() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
if (!WBF_DB::can($user, 'post')) wp_send_json_error(['message'=>'Keine Berechtigung.']);
|
|
|
|
// Flood Control
|
|
if ( ! WBF_DB::check_flood( $user->id ) ) {
|
|
$secs = (int)( wbf_get_settings()['flood_interval'] ?? 30 );
|
|
wp_send_json_error(['message'=>"Bitte warte {$secs} Sekunden zwischen Beiträgen.", 'flood'=>true]);
|
|
}
|
|
|
|
$thread_id = (int)($_POST['thread_id'] ?? 0);
|
|
$content = WBF_BBCode::sanitize( $_POST['content'] ?? '' );
|
|
$content = WBF_DB::apply_word_filter( $content );
|
|
|
|
if (strlen($content) < 3) wp_send_json_error(['message'=>'Antwort zu kurz.']);
|
|
if (!$thread_id) wp_send_json_error(['message'=>'Ungültiger Thread.']);
|
|
|
|
$thread = WBF_DB::get_thread($thread_id);
|
|
if (!$thread || $thread->status === 'closed') wp_send_json_error(['message'=>'Thread ist geschlossen.']);
|
|
if ($thread->status === 'archived') wp_send_json_error(['message'=>'Thread ist archiviert.']);
|
|
|
|
$id = WBF_DB::create_post(['thread_id'=>$thread_id,'user_id'=>$user->id,'content'=>$content]);
|
|
|
|
// Benachrichtigungen: Thread-Ersteller + alle bisherigen Antwortenden
|
|
$notif_users = WBF_DB::get_thread_participants($thread_id);
|
|
foreach ($notif_users as $participant_id) {
|
|
WBF_DB::create_notification($participant_id, 'reply', $thread_id, $user->id);
|
|
$notif_user = WBF_DB::get_user($participant_id);
|
|
self::send_notification_email($notif_user, 'reply', $user->display_name, [
|
|
'thread_id' => $thread_id,
|
|
'thread_title' => $thread->title,
|
|
]);
|
|
}
|
|
// Thread-Abonnenten benachrichtigen
|
|
$subscribers = WBF_DB::get_thread_subscribers($thread_id);
|
|
// $notif_users is a flat array of IDs (from get_col) — cast to int for comparison
|
|
$notif_ids = array_map('intval', $notif_users);
|
|
foreach ($subscribers as $sub) {
|
|
if ((int)$sub->id === (int)$user->id) continue; // nicht sich selbst
|
|
if (in_array((int)$sub->id, $notif_ids, true)) continue; // schon benachrichtigt
|
|
self::send_notification_email($sub, 'reply', $user->display_name, [
|
|
'thread_id' => $thread_id,
|
|
'thread_title' => $thread->title,
|
|
]);
|
|
}
|
|
// Ersteller auto-abonniert
|
|
WBF_DB::subscribe($thread->user_id, $thread_id);
|
|
// @Erwähnungen
|
|
$mentioned = WBF_DB::extract_mentions($content);
|
|
foreach ($mentioned as $m_user) {
|
|
if ((int)$m_user->id !== (int)$user->id) {
|
|
WBF_DB::create_notification($m_user->id, 'mention', $thread_id, $user->id);
|
|
// E-Mail
|
|
self::send_notification_email($m_user, 'mention', $user->display_name, [
|
|
'thread_id' => $thread_id,
|
|
'thread_title' => $thread->title,
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Direkt den neuen Post laden — nicht alle Posts fetchen
|
|
global $wpdb;
|
|
$new_post = $wpdb->get_row( $wpdb->prepare(
|
|
"SELECT p.*, u.display_name, u.avatar_url, u.username, u.signature,
|
|
u.post_count as author_posts, u.role as author_role, u.registered as author_registered
|
|
FROM {$wpdb->prefix}forum_posts p
|
|
JOIN {$wpdb->prefix}forum_users u ON u.id = p.user_id
|
|
WHERE p.id = %d", $id
|
|
) );
|
|
|
|
ob_start();
|
|
WBF_Shortcodes::render_single_post($new_post, $user);
|
|
$html = ob_get_clean();
|
|
|
|
wp_send_json_success(['html'=>$html,'post_id'=>$id]);
|
|
}
|
|
|
|
// ── Mod Actions ───────────────────────────────────────────────────────────
|
|
|
|
public static function handle_mod_action() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
|
|
$action = sanitize_key($_POST['mod_action'] ?? '');
|
|
$object_id = (int)($_POST['object_id'] ?? 0);
|
|
|
|
switch ($action) {
|
|
|
|
case 'pin_thread':
|
|
case 'unpin_thread':
|
|
if (!WBF_DB::can($user,'pin_thread')) wp_send_json_error(['message'=>'Keine Berechtigung.']);
|
|
WBF_DB::update_thread($object_id, ['pinned' => $action === 'pin_thread' ? 1 : 0]);
|
|
wp_send_json_success(['action'=>$action,'message'=>$action==='pin_thread'?'Gepinnt!':'Entpinnt!']);
|
|
break;
|
|
|
|
case 'close_thread':
|
|
case 'open_thread':
|
|
if (!WBF_DB::can($user,'close_thread')) wp_send_json_error(['message'=>'Keine Berechtigung.']);
|
|
WBF_DB::update_thread($object_id, ['status' => $action === 'close_thread' ? 'closed' : 'open']);
|
|
wp_send_json_success(['action'=>$action,'message'=>$action==='close_thread'?'Thread geschlossen.':'Thread geöffnet.']);
|
|
break;
|
|
|
|
case 'delete_thread':
|
|
if (!WBF_DB::can($user,'delete_thread')) wp_send_json_error(['message'=>'Keine Berechtigung.']);
|
|
$thread = WBF_DB::get_thread($object_id);
|
|
if (!$thread) wp_send_json_error(['message'=>'Thread nicht gefunden.']);
|
|
WBF_DB::soft_delete_thread($object_id);
|
|
wp_send_json_success(['action'=>'deleted','redirect'=>'?forum_cat='.urlencode('')]);
|
|
break;
|
|
|
|
case 'delete_post':
|
|
if (!WBF_DB::can($user,'delete_post')) wp_send_json_error(['message'=>'Keine Berechtigung.']);
|
|
WBF_DB::soft_delete_post($object_id);
|
|
wp_send_json_success(['action'=>'post_deleted']);
|
|
break;
|
|
|
|
case 'archive_thread':
|
|
case 'unarchive_thread':
|
|
if (!WBF_DB::can($user,'close_thread')) wp_send_json_error(['message'=>'Keine Berechtigung.']);
|
|
$thread = WBF_DB::get_thread($object_id);
|
|
if (!$thread) wp_send_json_error(['message'=>'Thread nicht gefunden.']);
|
|
if ($action === 'archive_thread') {
|
|
if ($thread->status !== 'archived') {
|
|
global $wpdb;
|
|
$wpdb->query($wpdb->prepare(
|
|
"UPDATE {$wpdb->prefix}forum_categories SET thread_count=GREATEST(thread_count-1,0) WHERE id=%d",
|
|
$thread->category_id
|
|
));
|
|
}
|
|
WBF_DB::update_thread($object_id, ['status' => 'archived']);
|
|
wp_send_json_success(['action'=>'archived','message'=>'Thread archiviert.']);
|
|
} else {
|
|
WBF_DB::update_thread($object_id, ['status' => 'open']);
|
|
global $wpdb;
|
|
$wpdb->query($wpdb->prepare(
|
|
"UPDATE {$wpdb->prefix}forum_categories SET thread_count=thread_count+1 WHERE id=%d",
|
|
$thread->category_id
|
|
));
|
|
wp_send_json_success(['action'=>'unarchived','message'=>'Thread wiederhergestellt.']);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
wp_send_json_error(['message'=>'Unbekannte Aktion.']);
|
|
}
|
|
}
|
|
|
|
// ── Likes ─────────────────────────────────────────────────────────────────
|
|
|
|
public static function handle_toggle_like() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Bitte einloggen um zu liken.']);
|
|
if (!WBF_DB::can($user,'like')) wp_send_json_error(['message'=>'Keine Berechtigung.']);
|
|
|
|
$object_id = (int)($_POST['object_id'] ?? 0);
|
|
$type = sanitize_key($_POST['object_type'] ?? 'post');
|
|
if (!in_array($type, ['thread','post'])) wp_send_json_error(['message'=>'Ungültiger Typ.']);
|
|
|
|
$action = WBF_DB::toggle_like($user->id, $object_id, $type);
|
|
$count = WBF_DB::get_like_count($object_id, $type);
|
|
wp_send_json_success(['action'=>$action,'count'=>$count]);
|
|
}
|
|
|
|
// ── Profile ───────────────────────────────────────────────────────────────
|
|
|
|
public static function handle_update_profile() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
|
|
$update = [];
|
|
$display_name = sanitize_text_field($_POST['display_name'] ?? '');
|
|
$bio = sanitize_textarea_field($_POST['bio'] ?? '');
|
|
$signature = sanitize_textarea_field($_POST['signature'] ?? '');
|
|
|
|
if (!empty($display_name)) $update['display_name'] = $display_name;
|
|
$update['bio'] = $bio;
|
|
$update['signature'] = mb_substr($signature, 0, 300); // max 300 Zeichen
|
|
|
|
if (!empty($_POST['new_password'])) {
|
|
if (strlen($_POST['new_password']) < 6) wp_send_json_error(['message'=>'Passwort mindestens 6 Zeichen.']);
|
|
// Sicherheit: aktuelles Passwort muss zur Bestätigung angegeben werden
|
|
$current_pw = $_POST['current_password'] ?? '';
|
|
if ( empty($current_pw) ) {
|
|
wp_send_json_error(['message'=>'Bitte aktuelles Passwort zur Bestätigung eingeben.']);
|
|
}
|
|
if ( ! password_verify($current_pw, $user->password) ) {
|
|
wp_send_json_error(['message'=>'Aktuelles Passwort ist falsch.']);
|
|
}
|
|
// Bestätigungsfeld server-seitig prüfen
|
|
$new_pw2 = $_POST['new_password2'] ?? '';
|
|
if ( ! empty($new_pw2) && $new_pw2 !== $_POST['new_password'] ) {
|
|
wp_send_json_error(['message'=>'Die Passwörter stimmen nicht überein.']);
|
|
}
|
|
$update['password'] = password_hash($_POST['new_password'], PASSWORD_DEFAULT);
|
|
}
|
|
|
|
WBF_DB::update_user($user->id, $update);
|
|
|
|
// Benutzerdefinierte Profilfelder speichern
|
|
$field_defs = WBF_DB::get_profile_field_defs();
|
|
foreach ( $field_defs as $def ) {
|
|
$key = sanitize_key( $def['key'] );
|
|
if ( ! $key ) continue;
|
|
$raw = $_POST[ 'cf_' . $key ] ?? null;
|
|
if ( $raw === null ) continue; // nicht übermittelt — nicht anfassen
|
|
|
|
// Pflichtfeld-Prüfung
|
|
if ( ! empty($def['required']) && trim($raw) === '' ) {
|
|
wp_send_json_error(['message' => sprintf('Das Feld "%s" ist ein Pflichtfeld.', $def['label'])]);
|
|
}
|
|
|
|
// Sanitisierung je nach Typ
|
|
if ( $def['type'] === 'url' ) {
|
|
$value = esc_url_raw( trim($raw) );
|
|
} elseif ( $def['type'] === 'textarea' ) {
|
|
$value = sanitize_textarea_field( $raw );
|
|
} elseif ( $def['type'] === 'number' ) {
|
|
$value = is_numeric($raw) ? (string)(float)$raw : '';
|
|
} elseif ( $def['type'] === 'date' ) {
|
|
// Datum validieren — nur YYYY-MM-DD, nicht in der Zukunft
|
|
$raw_date = sanitize_text_field( trim($raw) );
|
|
if ( preg_match('/^\d{4}-\d{2}-\d{2}$/', $raw_date) ) {
|
|
$ts = strtotime($raw_date);
|
|
$value = ($ts && $ts <= time()) ? $raw_date : '';
|
|
} else {
|
|
$value = '';
|
|
}
|
|
} else {
|
|
$value = sanitize_text_field( $raw );
|
|
}
|
|
|
|
WBF_DB::set_user_meta( $user->id, $key, $value );
|
|
}
|
|
|
|
wp_send_json_success(['message'=>'Profil gespeichert!']);
|
|
}
|
|
|
|
public static function handle_upload_avatar() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
if (empty($_FILES['avatar'])) wp_send_json_error(['message'=>'Keine Datei.']);
|
|
|
|
$allowed_types = ['image/jpeg','image/png','image/gif','image/webp'];
|
|
|
|
// Dateigröße vor dem MIME-Check prüfen
|
|
if ( $_FILES['avatar']['size'] > 2 * 1024 * 1024 ) {
|
|
wp_send_json_error(['message'=>'Maximale Dateigröße: 2 MB.']);
|
|
}
|
|
|
|
// Server-seitige MIME-Typ-Prüfung — $_FILES['type'] kommt vom Client
|
|
// und ist beliebig fälschbar (z.B. PHP-Datei als image/jpeg getarnt).
|
|
// finfo_file() liest den echten Magic-Byte der temporären Datei.
|
|
$tmp = $_FILES['avatar']['tmp_name'] ?? '';
|
|
if ( ! $tmp || ! is_uploaded_file( $tmp ) ) {
|
|
wp_send_json_error(['message'=>'Ungültiger Datei-Upload.']);
|
|
}
|
|
if ( function_exists('finfo_open') ) {
|
|
$finfo = finfo_open( FILEINFO_MIME_TYPE );
|
|
$real_mime = finfo_file( $finfo, $tmp );
|
|
finfo_close( $finfo );
|
|
} else {
|
|
// Fallback: exif_imagetype() wenn finfo nicht verfügbar
|
|
$et_map = [
|
|
IMAGETYPE_JPEG => 'image/jpeg',
|
|
IMAGETYPE_PNG => 'image/png',
|
|
IMAGETYPE_GIF => 'image/gif',
|
|
IMAGETYPE_WEBP => 'image/webp',
|
|
];
|
|
$et = @exif_imagetype( $tmp );
|
|
$real_mime = $et_map[$et] ?? '';
|
|
}
|
|
if ( ! in_array( $real_mime, $allowed_types, true ) ) {
|
|
wp_send_json_error(['message'=>'Nur JPG, PNG, GIF und WebP erlaubt.']);
|
|
}
|
|
|
|
require_once ABSPATH . 'wp-admin/includes/image.php';
|
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
|
require_once ABSPATH . 'wp-admin/includes/media.php';
|
|
|
|
$id = media_handle_upload('avatar', 0);
|
|
if (is_wp_error($id)) wp_send_json_error(['message'=>$id->get_error_message()]);
|
|
|
|
$url = wp_get_attachment_url($id);
|
|
WBF_DB::update_user($user->id, ['avatar_url'=>$url]);
|
|
wp_send_json_success(['avatar_url'=>$url]);
|
|
}
|
|
|
|
// ── Banner Upload ────────────────────────────────────────────────────────
|
|
|
|
public static function handle_upload_banner() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
if (empty($_FILES['banner'])) wp_send_json_error(['message'=>'Keine Datei.']);
|
|
|
|
$allowed_types = ['image/jpeg','image/png','image/gif','image/webp'];
|
|
|
|
// Max 4 MB für Banner (größer als Avatar)
|
|
if ( $_FILES['banner']['size'] > 4 * 1024 * 1024 ) {
|
|
wp_send_json_error(['message'=>'Maximale Dateigröße: 4 MB.']);
|
|
}
|
|
|
|
// Server-seitige MIME-Typ-Prüfung
|
|
$tmp = $_FILES['banner']['tmp_name'] ?? '';
|
|
if ( ! $tmp || ! is_uploaded_file( $tmp ) ) {
|
|
wp_send_json_error(['message'=>'Ungültiger Datei-Upload.']);
|
|
}
|
|
if ( function_exists('finfo_open') ) {
|
|
$finfo = finfo_open( FILEINFO_MIME_TYPE );
|
|
$real_mime = finfo_file( $finfo, $tmp );
|
|
finfo_close( $finfo );
|
|
} else {
|
|
$et_map = [
|
|
IMAGETYPE_JPEG => 'image/jpeg',
|
|
IMAGETYPE_PNG => 'image/png',
|
|
IMAGETYPE_GIF => 'image/gif',
|
|
IMAGETYPE_WEBP => 'image/webp',
|
|
];
|
|
$et = @exif_imagetype( $tmp );
|
|
$real_mime = $et_map[$et] ?? '';
|
|
}
|
|
if ( ! in_array( $real_mime, $allowed_types, true ) ) {
|
|
wp_send_json_error(['message'=>'Nur JPG, PNG, GIF und WebP erlaubt.']);
|
|
}
|
|
|
|
require_once ABSPATH . 'wp-admin/includes/image.php';
|
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
|
require_once ABSPATH . 'wp-admin/includes/media.php';
|
|
|
|
$id = media_handle_upload('banner', 0);
|
|
if (is_wp_error($id)) wp_send_json_error(['message'=>$id->get_error_message()]);
|
|
|
|
$url = wp_get_attachment_url($id);
|
|
WBF_DB::update_user($user->id, ['banner_url'=>$url]);
|
|
wp_send_json_success(['banner_url'=>$url]);
|
|
}
|
|
|
|
// ── Report ────────────────────────────────────────────────────────────────
|
|
|
|
public static function handle_report_post() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Bitte einloggen um zu melden.']);
|
|
if (!WBF_Roles::can($user, 'post')) wp_send_json_error(['message'=>'Keine Berechtigung.']);
|
|
|
|
$object_id = (int)($_POST['object_id'] ?? 0);
|
|
$type = sanitize_key($_POST['object_type'] ?? 'post');
|
|
$reason = sanitize_text_field($_POST['reason'] ?? '');
|
|
$note = sanitize_textarea_field($_POST['note'] ?? '');
|
|
|
|
if (!$object_id) wp_send_json_error(['message'=>'Ungültiges Objekt.']);
|
|
if (!in_array($type, ['post','thread'])) wp_send_json_error(['message'=>'Ungültiger Typ.']);
|
|
if (empty($reason)) wp_send_json_error(['message'=>'Bitte einen Grund angeben.']);
|
|
if (WBF_DB::has_reported($user->id, $object_id, $type))
|
|
wp_send_json_error(['message'=>'Du hast diesen Beitrag bereits gemeldet.']);
|
|
|
|
WBF_DB::create_report([
|
|
'object_id' => $object_id,
|
|
'object_type' => $type,
|
|
'reporter_id' => $user->id,
|
|
'reason' => $reason,
|
|
'note' => mb_substr($note, 0, 500),
|
|
'status' => 'open',
|
|
]);
|
|
|
|
wp_send_json_success(['message'=>'Beitrag wurde gemeldet. Danke!']);
|
|
}
|
|
// ── Post-Bild Upload ──────────────────────────────────────────────────────
|
|
|
|
public static function handle_upload_post_image() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
if ( ! WBF_Roles::can($user, 'post') ) wp_send_json_error(['message' => 'Keine Berechtigung.']);
|
|
if ( empty($_FILES['image']) ) wp_send_json_error(['message' => 'Keine Datei empfangen.']);
|
|
|
|
// Nur Bilder erlauben
|
|
$allowed_types = ['image/jpeg','image/png','image/gif','image/webp'];
|
|
|
|
// Max 5 MB — Größe zuerst prüfen bevor teure MIME-Erkennung läuft
|
|
if ( $_FILES['image']['size'] > 5 * 1024 * 1024 ) {
|
|
wp_send_json_error(['message' => 'Maximale Dateigröße: 5 MB.']);
|
|
}
|
|
|
|
// Server-seitige MIME-Typ-Prüfung — $_FILES['type'] ist client-kontrolliert
|
|
// und kann beliebig auf 'image/jpeg' gesetzt werden, auch für .php-Dateien.
|
|
$tmp = $_FILES['image']['tmp_name'] ?? '';
|
|
if ( ! $tmp || ! is_uploaded_file( $tmp ) ) {
|
|
wp_send_json_error(['message' => 'Ungültiger Datei-Upload.']);
|
|
}
|
|
if ( function_exists('finfo_open') ) {
|
|
$finfo = finfo_open( FILEINFO_MIME_TYPE );
|
|
$real_mime = finfo_file( $finfo, $tmp );
|
|
finfo_close( $finfo );
|
|
} else {
|
|
$et_map = [
|
|
IMAGETYPE_JPEG => 'image/jpeg',
|
|
IMAGETYPE_PNG => 'image/png',
|
|
IMAGETYPE_GIF => 'image/gif',
|
|
IMAGETYPE_WEBP => 'image/webp',
|
|
];
|
|
$et = @exif_imagetype( $tmp );
|
|
$real_mime = $et_map[$et] ?? '';
|
|
}
|
|
if ( ! in_array( $real_mime, $allowed_types, true ) ) {
|
|
wp_send_json_error(['message' => 'Nur JPG, PNG, GIF und WebP erlaubt.']);
|
|
}
|
|
|
|
require_once ABSPATH . 'wp-admin/includes/image.php';
|
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
|
require_once ABSPATH . 'wp-admin/includes/media.php';
|
|
|
|
// $_FILES-Schlüssel auf 'image' umbenennen → media_handle_upload erwartet den Feldnamen
|
|
$attachment_id = media_handle_upload('image', 0);
|
|
if ( is_wp_error($attachment_id) ) {
|
|
wp_send_json_error(['message' => $attachment_id->get_error_message()]);
|
|
}
|
|
|
|
$url = wp_get_attachment_url($attachment_id);
|
|
wp_send_json_success(['url' => $url]);
|
|
}
|
|
// ── Post bearbeiten ───────────────────────────────────────────────────────
|
|
|
|
public static function handle_edit_post() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
|
|
$post_id = (int)( $_POST['post_id'] ?? 0 );
|
|
$content = WBF_BBCode::sanitize( $_POST['content'] ?? '' );
|
|
|
|
if ( ! $post_id ) wp_send_json_error(['message' => 'Ungültiger Beitrag.']);
|
|
if ( strlen($content) < 3 ) wp_send_json_error(['message' => 'Inhalt zu kurz.']);
|
|
|
|
global $wpdb;
|
|
$db_post = $wpdb->get_row( $wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}forum_posts WHERE id=%d", $post_id
|
|
) );
|
|
if ( ! $db_post ) wp_send_json_error(['message' => 'Beitrag nicht gefunden.']);
|
|
|
|
$is_own = (int)$db_post->user_id === (int)$user->id;
|
|
$is_mod = WBF_DB::can( $user, 'delete_post' );
|
|
|
|
if ( ! $is_own && ! $is_mod ) {
|
|
wp_send_json_error(['message' => 'Keine Berechtigung.']);
|
|
}
|
|
|
|
// Post-Bearbeitungslimit prüfen
|
|
if ($is_own && !$is_mod) {
|
|
$limit_min = (int)(wbf_get_settings()['post_edit_limit'] ?? 30);
|
|
if ($limit_min > 0) {
|
|
$age_min = (time() - strtotime($db_post->created_at)) / 60;
|
|
if ($age_min > $limit_min) {
|
|
wp_send_json_error(['message' => "Bearbeitung nur innerhalb von {$limit_min} Minuten nach dem Posten möglich."]);
|
|
}
|
|
}
|
|
}
|
|
|
|
$wpdb->update(
|
|
"{$wpdb->prefix}forum_posts",
|
|
['content' => $content, 'updated_at' => current_time('mysql')],
|
|
['id' => $post_id]
|
|
);
|
|
|
|
wp_send_json_success(['message' => 'Beitrag aktualisiert!', 'content' => WBF_BBCode::render($content)]);
|
|
}
|
|
|
|
// ── Suche ─────────────────────────────────────────────────────────────────
|
|
|
|
public static function handle_search() {
|
|
self::verify();
|
|
$query = sanitize_text_field( $_POST['query'] ?? '' );
|
|
if ( mb_strlen( $query ) < 2 ) wp_send_json_error(['message' => 'Suchbegriff zu kurz.']);
|
|
$current_search = WBF_Auth::get_current_user();
|
|
$results = WBF_DB::search( $query, 40, $current_search );
|
|
wp_send_json_success(['results' => $results, 'query' => $query]);
|
|
}
|
|
|
|
// ── Benachrichtigungen ────────────────────────────────────────────────────
|
|
|
|
public static function handle_get_notifications() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
$notifs = WBF_DB::get_notifications( $user->id, 20 );
|
|
$unread = WBF_DB::count_unread_notifications( $user->id );
|
|
wp_send_json_success(['notifications' => $notifs, 'unread' => $unread]);
|
|
}
|
|
|
|
public static function handle_mark_notifications_read() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
WBF_DB::mark_notifications_read( $user->id );
|
|
wp_send_json_success(['message' => 'Gelesen.']);
|
|
}
|
|
|
|
// ── Thread-Inhalt bearbeiten (OP) ────────────────────────────────────────
|
|
|
|
public static function handle_edit_thread() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
|
|
$thread_id = (int)( $_POST['thread_id'] ?? 0 );
|
|
$content = WBF_BBCode::sanitize( $_POST['content'] ?? '' );
|
|
$title = sanitize_text_field( $_POST['title'] ?? '' );
|
|
|
|
if ( ! $thread_id ) wp_send_json_error(['message' => 'Ungültiger Thread.']);
|
|
if ( strlen($content) < 5 ) wp_send_json_error(['message' => 'Inhalt zu kurz.']);
|
|
if ( strlen($title) < 3 ) wp_send_json_error(['message' => 'Titel zu kurz (min. 3 Zeichen).']);
|
|
|
|
$thread = WBF_DB::get_thread($thread_id);
|
|
if ( ! $thread ) wp_send_json_error(['message' => 'Thread nicht gefunden.']);
|
|
|
|
$is_own = (int)$thread->user_id === (int)$user->id;
|
|
$is_mod = WBF_DB::can($user, 'delete_post');
|
|
if ( ! $is_own && ! $is_mod ) wp_send_json_error(['message' => 'Keine Berechtigung.']);
|
|
|
|
// Post-Bearbeitungslimit prüfen — gilt auch für Thread-Erstbeiträge
|
|
// (spiegelt identisches Verhalten zu handle_edit_post() wider)
|
|
if ( $is_own && ! $is_mod ) {
|
|
$limit_min = (int)( wbf_get_settings()['post_edit_limit'] ?? 30 );
|
|
if ( $limit_min > 0 ) {
|
|
$age_min = ( time() - strtotime( $thread->created_at ) ) / 60;
|
|
if ( $age_min > $limit_min ) {
|
|
wp_send_json_error(['message' => "Bearbeitung nur innerhalb von {$limit_min} Minuten nach dem Erstellen möglich."]);
|
|
}
|
|
}
|
|
}
|
|
|
|
global $wpdb;
|
|
$wpdb->update(
|
|
"{$wpdb->prefix}forum_threads",
|
|
[
|
|
'title' => $title,
|
|
'content' => $content,
|
|
'updated_at' => current_time('mysql'),
|
|
],
|
|
['id' => $thread_id]
|
|
);
|
|
|
|
wp_send_json_success([
|
|
'message' => 'Thread aktualisiert!',
|
|
'title' => esc_html($title),
|
|
'content' => WBF_BBCode::render($content),
|
|
]);
|
|
}
|
|
|
|
// ── Thread verschieben ────────────────────────────────────────────────────
|
|
|
|
public static function handle_move_thread() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
if ( ! WBF_DB::can($user, 'manage_cats') ) wp_send_json_error(['message' => 'Keine Berechtigung.']);
|
|
|
|
$thread_id = (int)( $_POST['thread_id'] ?? 0 );
|
|
$category_id = (int)( $_POST['category_id'] ?? 0 );
|
|
|
|
if ( ! $thread_id || ! $category_id ) wp_send_json_error(['message' => 'Ungültige Daten.']);
|
|
|
|
$thread = WBF_DB::get_thread($thread_id);
|
|
$new_cat = WBF_DB::get_category($category_id);
|
|
if ( ! $thread ) wp_send_json_error(['message' => 'Thread nicht gefunden.']);
|
|
if ( ! $new_cat ) wp_send_json_error(['message' => 'Kategorie nicht gefunden.']);
|
|
if ( (int)$thread->category_id === $category_id ) wp_send_json_error(['message' => 'Thread ist bereits in dieser Kategorie.']);
|
|
|
|
WBF_DB::move_thread($thread_id, $category_id);
|
|
wp_send_json_success([
|
|
'message' => 'Thread verschoben nach: ' . $new_cat->name,
|
|
'cat_name' => $new_cat->name,
|
|
'cat_slug' => $new_cat->slug,
|
|
]);
|
|
}
|
|
|
|
// ── Tag-Autocomplete ──────────────────────────────────────────────────────
|
|
|
|
public static function handle_tag_suggest() {
|
|
// Kein Nonce-Check nötig — nur lesend, öffentlich
|
|
$q = sanitize_text_field( $_POST['q'] ?? $_GET['q'] ?? '' );
|
|
if ( mb_strlen($q) < 1 ) wp_send_json_success(['tags' => []]);
|
|
$tags = WBF_DB::suggest_tags( $q, 8 );
|
|
wp_send_json_success(['tags' => $tags]);
|
|
}
|
|
|
|
// ── Reaktionen ────────────────────────────────────────────────────────────
|
|
|
|
public static function handle_set_reaction() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
if (!WBF_DB::can($user,'like')) wp_send_json_error(['message'=>'Keine Berechtigung.']);
|
|
|
|
$object_id = (int)($_POST['object_id'] ?? 0);
|
|
$object_type = sanitize_key($_POST['object_type'] ?? 'post');
|
|
$reaction = $_POST['reaction'] ?? '';
|
|
|
|
if (!$object_id || !in_array($object_type, ['post','thread'])) {
|
|
wp_send_json_error(['message'=>'Ungültige Daten.']);
|
|
}
|
|
|
|
$result = WBF_DB::set_reaction($user->id, $object_id, $object_type, $reaction);
|
|
if ($result === false) wp_send_json_error(['message'=>'Ungültige Reaktion.']);
|
|
|
|
$data = WBF_DB::get_reactions($object_id, $object_type, $user->id);
|
|
wp_send_json_success(['action'=>$result,'counts'=>$data['counts'],'mine'=>$data['mine']]);
|
|
}
|
|
|
|
// ── Private Nachrichten ───────────────────────────────────────────────────
|
|
|
|
public static function handle_send_message() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
if (WBF_Roles::level($user->role) < 0) wp_send_json_error(['message'=>'Kein Zugriff.']);
|
|
|
|
$to_id = (int)($_POST['to_id'] ?? 0);
|
|
$content = sanitize_textarea_field($_POST['content'] ?? '');
|
|
|
|
if (!$to_id) wp_send_json_error(['message'=>'Empfänger fehlt.']);
|
|
if (mb_strlen($content) < 1) wp_send_json_error(['message'=>'Nachricht leer.']);
|
|
if ($to_id === (int)$user->id) wp_send_json_error(['message'=>'Du kannst dir nicht selbst schreiben.']);
|
|
if (!WBF_DB::get_user($to_id)) wp_send_json_error(['message'=>'Empfänger nicht gefunden.']);
|
|
|
|
// DM-Blockierung: Empfänger hat Sender ignoriert
|
|
if ( WBF_DB::is_ignored( $to_id, $user->id ) ) {
|
|
wp_send_json_error(['message' => 'Diese Person akzeptiert keine Nachrichten von dir.']);
|
|
}
|
|
|
|
$id = WBF_DB::send_message($user->id, $to_id, $content);
|
|
// Notify recipient
|
|
WBF_DB::create_notification($to_id, 'message', $id, $user->id);
|
|
// E-Mail
|
|
$to_user = WBF_DB::get_user($to_id);
|
|
self::send_notification_email($to_user, 'message', $user->display_name);
|
|
|
|
wp_send_json_success(['message_id'=>$id,'message'=>'Gesendet!',
|
|
'content'=>esc_html($content),
|
|
'created_at'=>current_time('mysql'),
|
|
'sender_name'=>$user->display_name,
|
|
'sender_avatar'=>$user->avatar_url,
|
|
]);
|
|
}
|
|
|
|
public static function handle_get_inbox() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
$inbox = WBF_DB::get_inbox($user->id);
|
|
$unread = WBF_DB::count_unread_messages($user->id);
|
|
wp_send_json_success(['inbox'=>$inbox,'unread'=>$unread]);
|
|
}
|
|
|
|
public static function handle_get_conversation() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
$partner_id = (int)($_POST['partner_id'] ?? 0);
|
|
if (!$partner_id) wp_send_json_error(['message'=>'Ungültig.']);
|
|
WBF_DB::mark_messages_read($user->id, $partner_id);
|
|
$total = WBF_DB::count_conversation($user->id, $partner_id);
|
|
$msgs = WBF_DB::get_conversation($user->id, $partner_id, 50, max(0, $total - 50));
|
|
$partner = WBF_DB::get_user($partner_id);
|
|
wp_send_json_success(['messages'=>$msgs,'partner'=>$partner,'my_id'=>$user->id,'total'=>$total]);
|
|
}
|
|
|
|
public static function handle_mark_messages_read() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
$partner_id = (int)($_POST['partner_id'] ?? 0);
|
|
if ($partner_id) WBF_DB::mark_messages_read($user->id, $partner_id);
|
|
wp_send_json_success();
|
|
}
|
|
|
|
// ── Online-Status ─────────────────────────────────────────────────────────
|
|
|
|
public static function handle_get_online_users() {
|
|
// Public — kein Login nötig
|
|
$users = WBF_DB::get_online_users(15);
|
|
wp_send_json_success(['users' => $users]);
|
|
}
|
|
|
|
// ── User-Autocomplete (für @Erwähnungen + DM) ─────────────────────────────
|
|
|
|
public static function handle_user_suggest() {
|
|
// Nur eingeloggte Nutzer dürfen die User-Suche nutzen
|
|
// (verhindert Enumeration aller Usernamen + Rollendaten durch Gäste)
|
|
if ( ! WBF_Auth::is_forum_logged_in() ) {
|
|
wp_send_json_success(['users'=>[]]);
|
|
}
|
|
$q = sanitize_text_field($_POST['q'] ?? $_GET['q'] ?? '');
|
|
if (mb_strlen($q) < 1) wp_send_json_success(['users'=>[]]);
|
|
global $wpdb;
|
|
$like = $wpdb->esc_like($q) . '%';
|
|
// Rolle wird bewusst NICHT zurückgegeben — nicht für Autocomplete nötig
|
|
// und verhindert Informationsleck über Rollen-Verteilung im Forum.
|
|
$users = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT id, username, display_name, avatar_url
|
|
FROM {$wpdb->prefix}forum_users
|
|
WHERE (username LIKE %s OR display_name LIKE %s)
|
|
AND role != 'banned'
|
|
ORDER BY display_name ASC LIMIT 8",
|
|
$like, $like
|
|
));
|
|
wp_send_json_success(['users'=>$users]);
|
|
}
|
|
|
|
// ── Nachricht löschen ─────────────────────────────────────────────────────
|
|
|
|
public static function handle_delete_message() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
|
|
$msg_id = (int)( $_POST['message_id'] ?? 0 );
|
|
if ( ! $msg_id ) wp_send_json_error(['message' => 'Ungültige Nachricht.']);
|
|
|
|
global $wpdb;
|
|
$msg = $wpdb->get_row( $wpdb->prepare(
|
|
"SELECT * FROM {$wpdb->prefix}forum_messages WHERE id = %d", $msg_id
|
|
));
|
|
if ( ! $msg ) wp_send_json_error(['message' => 'Nachricht nicht gefunden.']);
|
|
|
|
$uid = (int) $user->id;
|
|
if ( (int)$msg->from_id === $uid ) {
|
|
$wpdb->update( "{$wpdb->prefix}forum_messages", ['deleted_by_sender' => 1], ['id' => $msg_id] );
|
|
} elseif ( (int)$msg->to_id === $uid ) {
|
|
$wpdb->update( "{$wpdb->prefix}forum_messages", ['deleted_by_receiver' => 1], ['id' => $msg_id] );
|
|
} else {
|
|
wp_send_json_error(['message' => 'Keine Berechtigung.']);
|
|
}
|
|
|
|
wp_send_json_success(['message_id' => $msg_id]);
|
|
}
|
|
|
|
// ── Neue Nachrichten seit ID (Live-Polling) ───────────────────────────────
|
|
|
|
public static function handle_get_new_messages() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
|
|
$partner_id = (int)( $_POST['partner_id'] ?? 0 );
|
|
$since_id = (int)( $_POST['since_id'] ?? 0 );
|
|
|
|
if ( ! $partner_id ) wp_send_json_error(['message' => 'Ungueltig.']);
|
|
|
|
global $wpdb;
|
|
$msgs = $wpdb->get_results( $wpdb->prepare(
|
|
"SELECT m.*, u.display_name AS sender_name, u.avatar_url AS sender_avatar
|
|
FROM {$wpdb->prefix}forum_messages m
|
|
JOIN {$wpdb->prefix}forum_users u ON u.id = m.from_id
|
|
WHERE m.id > %d
|
|
AND ( (m.from_id=%d AND m.to_id=%d AND m.deleted_by_sender=0)
|
|
OR (m.from_id=%d AND m.to_id=%d AND m.deleted_by_receiver=0) )
|
|
ORDER BY m.created_at ASC
|
|
LIMIT 50",
|
|
$since_id,
|
|
(int)$user->id, $partner_id,
|
|
$partner_id, (int)$user->id
|
|
));
|
|
|
|
wp_send_json_success(['messages' => $msgs]);
|
|
}
|
|
|
|
|
|
// ── E-Mail Benachrichtigungen ─────────────────────────────────────────────
|
|
|
|
private static function send_notification_email( $to_user, $type, $actor_name, $extra = [] ) {
|
|
if ( ! $to_user || empty($to_user->email) ) return;
|
|
|
|
// Prüfen ob der User diesen Benachrichtigungstyp aktiviert hat
|
|
// Standard: alle aktiviert (1). User kann im Profil deaktivieren (0).
|
|
$pref_key = 'notify_' . $type; // notify_reply, notify_mention, notify_message
|
|
$meta = WBF_DB::get_user_meta( $to_user->id );
|
|
// Nur deaktivieren wenn explizit auf '0' gesetzt — Standard ist aktiviert
|
|
if ( isset($meta[$pref_key]) && $meta[$pref_key] === '0' ) return;
|
|
|
|
$blog_name = get_bloginfo('name');
|
|
$forum_url = wbf_get_forum_url();
|
|
$from_email = get_option('admin_email');
|
|
$from_name = $blog_name . ' Forum';
|
|
|
|
$headers = [
|
|
'Content-Type: text/html; charset=UTF-8',
|
|
"From: {$from_name} <{$from_email}>",
|
|
];
|
|
|
|
switch ( $type ) {
|
|
case 'reply':
|
|
$thread_id = $extra['thread_id'] ?? 0;
|
|
$thread_title = $extra['thread_title'] ?? '';
|
|
$subject = "[{$blog_name}] {$actor_name} hat auf deinen Thread geantwortet";
|
|
$body = self::email_template(
|
|
$to_user->display_name,
|
|
"<strong>{$actor_name}</strong> hat in deinem Thread <em>" . esc_html($thread_title) . "</em> geantwortet.",
|
|
$forum_url . '?forum_thread=' . (int)$thread_id,
|
|
'Thread ansehen',
|
|
$blog_name
|
|
);
|
|
break;
|
|
|
|
case 'mention':
|
|
$thread_id = $extra['thread_id'] ?? 0;
|
|
$thread_title = $extra['thread_title'] ?? '';
|
|
$subject = "[{$blog_name}] {$actor_name} hat dich erwähnt";
|
|
$body = self::email_template(
|
|
$to_user->display_name,
|
|
"<strong>{$actor_name}</strong> hat dich in einem Beitrag erwähnt: <em>" . esc_html($thread_title) . "</em>",
|
|
$forum_url . '?forum_thread=' . (int)$thread_id,
|
|
'Beitrag ansehen',
|
|
$blog_name
|
|
);
|
|
break;
|
|
|
|
case 'message':
|
|
$subject = "[{$blog_name}] Neue Privatnachricht von {$actor_name}";
|
|
$body = self::email_template(
|
|
$to_user->display_name,
|
|
"<strong>{$actor_name}</strong> hat dir eine Privatnachricht gesendet.",
|
|
$forum_url . '?forum_dm=1',
|
|
'Nachricht lesen',
|
|
$blog_name
|
|
);
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
wp_mail( $to_user->email, $subject, $body, $headers );
|
|
}
|
|
|
|
private static function email_template( $name, $text, $url, $btn_label, $blog_name ) {
|
|
return "<!DOCTYPE html><html><body style='font-family:sans-serif;background:#0f1117;margin:0;padding:2rem'>
|
|
<div style='max-width:520px;margin:0 auto;background:#1e2330;border-radius:12px;overflow:hidden'>
|
|
<div style='background:linear-gradient(135deg,#0d1117,#1a2540);padding:1.5rem 2rem;border-bottom:1px solid rgba(0,180,216,.2)'>
|
|
<span style='color:#00b4d8;font-weight:700;font-size:1.1rem'>💬 {$blog_name}</span>
|
|
</div>
|
|
<div style='padding:2rem'>
|
|
<p style='color:#e8eaf0;font-size:1rem;margin:0 0 1rem'>Hallo <strong style='color:#fff'>{$name}</strong>,</p>
|
|
<p style='color:#b0b8cc;font-size:.95rem;margin:0 0 1.5rem'>{$text}</p>
|
|
<a href='{$url}' style='display:inline-block;background:#00b4d8;color:#fff;padding:.75rem 1.5rem;border-radius:8px;text-decoration:none;font-weight:700'>{$btn_label}</a>
|
|
</div>
|
|
<div style='padding:1rem 2rem;border-top:1px solid rgba(255,255,255,.06)'>
|
|
<p style='color:#6b7a99;font-size:.78rem;margin:0'>Du erhältst diese E-Mail weil du im {$blog_name} Forum registriert bist.<br>
|
|
<a href='{$url}' style='color:#00b4d8'>Forum öffnen</a></p>
|
|
</div>
|
|
</div></body></html>";
|
|
}
|
|
|
|
// ── Passwort vergessen ────────────────────────────────────────────────────
|
|
|
|
public static function handle_forgot_password() {
|
|
// Kein Nonce nötig — nur E-Mail wird verarbeitet
|
|
$email = sanitize_email( $_POST['email'] ?? '' );
|
|
if ( ! is_email($email) ) wp_send_json_error(['message'=>'Ungültige E-Mail-Adresse.']);
|
|
|
|
// ── Rate-Limiting: max. 1 Reset-Mail pro E-Mail-Adresse alle 15 Minuten ──
|
|
// Verhindert, dass ein Angreifer tausende Reset-Mails pro Sekunde
|
|
// für beliebige Adressen triggert und den Mail-Server überlastet.
|
|
$rate_key = 'wbf_pwreset_' . md5( strtolower( $email ) );
|
|
if ( get_transient( $rate_key ) !== false ) {
|
|
// Immer Erfolg melden — kein Leak ob Rate-Limit oder kein Account
|
|
wp_send_json_success(['message'=>'Falls diese E-Mail registriert ist, wurde eine E-Mail gesendet.']);
|
|
}
|
|
// Cooldown setzen — 15 Minuten
|
|
set_transient( $rate_key, 1, 15 * MINUTE_IN_SECONDS );
|
|
|
|
$user = WBF_DB::get_user_by('email', $email);
|
|
// Immer Erfolg melden (kein User-Enumeration)
|
|
if ( ! $user ) {
|
|
wp_send_json_success(['message'=>'Falls diese E-Mail registriert ist, wurde eine E-Mail gesendet.']);
|
|
}
|
|
|
|
$token = WBF_DB::create_reset_token($user->id);
|
|
$forum_url = wbf_get_forum_url();
|
|
$reset_url = $forum_url . '?wbf_reset_token=' . urlencode($token);
|
|
$blog_name = get_bloginfo('name');
|
|
|
|
$headers = ['Content-Type: text/html; charset=UTF-8', 'From: ' . $blog_name . ' <' . get_option('admin_email') . '>'];
|
|
$body = self::email_template(
|
|
$user->display_name,
|
|
'Du hast ein neues Passwort angefordert. Klicke den Button um dein Passwort zurückzusetzen. Der Link ist <strong>1 Stunde</strong> gültig.',
|
|
$reset_url,
|
|
'Passwort zurücksetzen',
|
|
$blog_name
|
|
);
|
|
wp_mail( $user->email, "[{$blog_name}] Passwort zurücksetzen", $body, $headers );
|
|
|
|
wp_send_json_success(['message'=>'E-Mail gesendet! Bitte prüfe deinen Posteingang.']);
|
|
}
|
|
|
|
public static function handle_reset_password() {
|
|
// Kein self::verify() hier — Gäste haben keine Forum-Session.
|
|
// Das Reset-Token selbst authentifiziert die Anfrage.
|
|
// Wir prüfen trotzdem den WP-Nonce als CSRF-Schutz; dieser wird
|
|
// von wp_localize_script für alle Besucher (auch Gäste) generiert.
|
|
if ( ! check_ajax_referer( 'wbf_nonce', 'nonce', false ) ) {
|
|
wp_send_json_error(['message' => 'Sicherheitsfehler.']);
|
|
}
|
|
$token = sanitize_text_field( $_POST['token'] ?? '' );
|
|
$password = $_POST['password'] ?? '';
|
|
$password2= $_POST['password2'] ?? '';
|
|
|
|
if ( strlen($password) < 6 ) wp_send_json_error(['message'=>'Passwort mindestens 6 Zeichen.']);
|
|
if ( $password !== $password2 ) wp_send_json_error(['message'=>'Passwörter stimmen nicht überein.']);
|
|
|
|
$ok = WBF_DB::use_reset_token($token, $password);
|
|
if ( ! $ok ) wp_send_json_error(['message'=>'Link ungültig oder abgelaufen.']);
|
|
|
|
wp_send_json_success(['message'=>'Passwort geändert! Du kannst dich jetzt einloggen.']);
|
|
}
|
|
|
|
// ── Ältere Nachrichten laden ──────────────────────────────────────────────
|
|
|
|
public static function handle_load_more_messages() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
|
|
$partner_id = (int)($_POST['partner_id'] ?? 0);
|
|
$offset = (int)($_POST['offset'] ?? 0);
|
|
$per_page = 30;
|
|
|
|
if (!$partner_id) wp_send_json_error(['message'=>'Ungültig.']);
|
|
|
|
$total = WBF_DB::count_conversation($user->id, $partner_id);
|
|
$msgs = WBF_DB::get_conversation($user->id, $partner_id, $per_page, $offset);
|
|
|
|
wp_send_json_success([
|
|
'messages' => $msgs,
|
|
'my_id' => $user->id,
|
|
'total' => $total,
|
|
'offset' => $offset,
|
|
'has_more' => ($offset + $per_page) < $total,
|
|
]);
|
|
}
|
|
|
|
|
|
|
|
// ── Einladungen ───────────────────────────────────────────────────────────
|
|
|
|
public static function handle_create_invite() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user || ! WBF_Roles::can($user, 'manage_users') ) {
|
|
wp_send_json_error(['message' => 'Keine Berechtigung.']);
|
|
}
|
|
$max_uses = max(1, (int)($_POST['max_uses'] ?? 1));
|
|
$note = sanitize_text_field($_POST['note'] ?? '');
|
|
$expires = sanitize_text_field($_POST['expires'] ?? '');
|
|
$expires_at = null;
|
|
if ($expires) {
|
|
$ts = strtotime($expires);
|
|
if ($ts > time()) {
|
|
$expires_at = date('Y-m-d H:i:s', $ts);
|
|
}
|
|
}
|
|
$code = WBF_DB::create_invite($user->id, $max_uses, $note, $expires_at);
|
|
$url = wbf_get_forum_url() . '?wbf_invite=' . $code;
|
|
wp_send_json_success(['code' => $code, 'url' => $url]);
|
|
}
|
|
|
|
public static function handle_delete_invite() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user || ! WBF_Roles::can($user, 'manage_users') ) {
|
|
wp_send_json_error(['message' => 'Keine Berechtigung.']);
|
|
}
|
|
$id = (int)($_POST['invite_id'] ?? 0);
|
|
if ($id) WBF_DB::delete_invite($id);
|
|
wp_send_json_success();
|
|
}
|
|
|
|
|
|
|
|
// ── Thread-Abonnement ─────────────────────────────────────────────────────
|
|
|
|
public static function handle_toggle_subscribe() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
$thread_id = (int)($_POST['thread_id'] ?? 0);
|
|
if (!$thread_id) wp_send_json_error(['message'=>'Ungültig.']);
|
|
|
|
if (WBF_DB::is_subscribed($user->id, $thread_id)) {
|
|
WBF_DB::unsubscribe($user->id, $thread_id);
|
|
wp_send_json_success(['subscribed'=>false,'msg'=>'Abonnement entfernt.']);
|
|
} else {
|
|
WBF_DB::subscribe($user->id, $thread_id);
|
|
wp_send_json_success(['subscribed'=>true,'msg'=>'Thread abonniert! Du erhältst E-Mails bei neuen Antworten.']);
|
|
}
|
|
}
|
|
|
|
// ── Wiederherstellen (Soft-Delete) ────────────────────────────────────────
|
|
|
|
public static function handle_restore_content() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user || !WBF_Roles::can($user,'delete_thread')) wp_send_json_error(['message'=>'Keine Berechtigung.']);
|
|
$type = sanitize_key($_POST['content_type'] ?? '');
|
|
$id = (int)($_POST['content_id'] ?? 0);
|
|
if ($type === 'thread') {
|
|
WBF_DB::restore_thread($id);
|
|
} elseif ($type === 'post') {
|
|
WBF_DB::restore_post($id);
|
|
} else {
|
|
wp_send_json_error(['message'=>'Ungültig.']);
|
|
}
|
|
wp_send_json_success(['message'=>'Wiederhergestellt.']);
|
|
}
|
|
|
|
// ── Profil-Sichtbarkeit umschalten ────────────────────────────────────────
|
|
|
|
public static function handle_toggle_profile_visibility() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
// Sicherstellen dass Spalte existiert (Schutz für bestehende Installs)
|
|
global $wpdb;
|
|
$cols = $wpdb->get_col( "DESCRIBE {$wpdb->prefix}forum_users" );
|
|
if ( ! in_array( 'profile_public', $cols ) ) {
|
|
$wpdb->query( "ALTER TABLE {$wpdb->prefix}forum_users ADD COLUMN profile_public TINYINT(1) NOT NULL DEFAULT 1" );
|
|
}
|
|
$current = (int)($user->profile_public ?? 1);
|
|
$new = $current ? 0 : 1;
|
|
WBF_DB::update_user($user->id, ['profile_public'=>$new]);
|
|
wp_send_json_success(['public'=>$new,'msg'=> $new ? 'Profil ist jetzt öffentlich.' : 'Profil ist jetzt privat.']);
|
|
}
|
|
|
|
// ── DSGVO: Konto löschen ─────────────────────────────────────────────────
|
|
|
|
public static function handle_delete_account() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error( [ 'message' => 'Nicht eingeloggt.' ] );
|
|
|
|
// Superadmin darf sich nicht selbst löschen
|
|
if ( $user->role === 'superadmin' ) {
|
|
wp_send_json_error( [ 'message' => 'Der Superadmin-Account kann nicht gelöscht werden.' ] );
|
|
}
|
|
|
|
// Passwort-Bestätigung prüfen
|
|
$password = $_POST['password'] ?? '';
|
|
if ( empty( $password ) ) {
|
|
wp_send_json_error( [ 'message' => 'Bitte Passwort zur Bestätigung eingeben.' ] );
|
|
}
|
|
if ( ! password_verify( $password, $user->password ) ) {
|
|
wp_send_json_error( [ 'message' => 'Falsches Passwort.' ] );
|
|
}
|
|
|
|
// Bestätigungs-Checkbox
|
|
if ( empty( $_POST['confirm'] ) ) {
|
|
wp_send_json_error( [ 'message' => 'Bitte Löschung ausdrücklich bestätigen.' ] );
|
|
}
|
|
|
|
// Ausloggen bevor gelöscht wird
|
|
WBF_Auth::logout();
|
|
|
|
// DSGVO-Löschung durchführen
|
|
$ok = WBF_DB::delete_user_gdpr( $user->id );
|
|
// Custom Profile Meta ebenfalls löschen
|
|
WBF_DB::delete_user_meta_all( $user->id );
|
|
if ( ! $ok ) {
|
|
wp_send_json_error( [ 'message' => 'Fehler bei der Kontolöschung. Bitte Admin kontaktieren.' ] );
|
|
}
|
|
|
|
// Admin benachrichtigen
|
|
$blog_name = get_bloginfo( 'name' );
|
|
$admin_email = get_option( 'admin_email' );
|
|
wp_mail(
|
|
$admin_email,
|
|
"[{$blog_name}] DSGVO: Konto gelöscht",
|
|
"Nutzer #{$user->id} ({$user->username}) hat sein Konto gemäß DSGVO Art. 17 gelöscht.\n\n"
|
|
. "Zeitpunkt: " . date('d.m.Y H:i:s') . "\n"
|
|
. "Alle personenbezogenen Daten wurden anonymisiert.",
|
|
[ 'Content-Type: text/plain; charset=UTF-8' ]
|
|
);
|
|
|
|
wp_send_json_success( [
|
|
'message' => 'Dein Konto wurde vollständig gelöscht. Alle personenbezogenen Daten wurden entfernt.',
|
|
'redirect' => wbf_get_forum_url(),
|
|
] );
|
|
}
|
|
|
|
// ── Umfrage: Erstellen (aus Thread-View) ──────────────────────────────────
|
|
|
|
public static function handle_create_poll() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
|
|
$thread_id = (int)( $_POST['thread_id'] ?? 0 );
|
|
if ( ! $thread_id ) wp_send_json_error(['message' => 'Ungültiger Thread.']);
|
|
|
|
$thread = WBF_DB::get_thread( $thread_id );
|
|
if ( ! $thread ) wp_send_json_error(['message' => 'Thread nicht gefunden.']);
|
|
|
|
// Nur der Thread-Ersteller darf eine Umfrage hinzufügen
|
|
if ( (int)$thread->user_id !== (int)$user->id && $user->role !== 'superadmin' ) {
|
|
wp_send_json_error(['message' => 'Keine Berechtigung.']);
|
|
}
|
|
|
|
// Bereits eine Umfrage vorhanden?
|
|
if ( WBF_DB::get_poll( $thread_id ) ) {
|
|
wp_send_json_error(['message' => 'Dieser Thread hat bereits eine Umfrage.']);
|
|
}
|
|
|
|
$question = sanitize_text_field( $_POST['poll_question'] ?? '' );
|
|
$opts_raw = $_POST['poll_options'] ?? [];
|
|
$multi = ! empty($_POST['poll_multi']);
|
|
$ends_raw = sanitize_text_field( $_POST['poll_ends_at'] ?? '' );
|
|
|
|
if ( ! $question ) wp_send_json_error(['message' => 'Bitte eine Frage eingeben.']);
|
|
|
|
$options = array_values( array_filter( array_map( 'sanitize_text_field', (array)$opts_raw ) ) );
|
|
if ( count($options) < 2 ) wp_send_json_error(['message' => 'Mindestens 2 Antwortmöglichkeiten erforderlich.']);
|
|
if ( count($options) > 10 ) wp_send_json_error(['message' => 'Maximal 10 Antwortmöglichkeiten erlaubt.']);
|
|
|
|
$ends_at = null;
|
|
if ( $ends_raw ) {
|
|
$ts = strtotime( $ends_raw );
|
|
if ( $ts && $ts > time() ) $ends_at = date('Y-m-d H:i:s', $ts);
|
|
}
|
|
|
|
WBF_DB::create_poll( $thread_id, $question, $options, $multi, $ends_at );
|
|
wp_send_json_success(['message' => 'Umfrage erstellt! Seite wird neu geladen…']);
|
|
}
|
|
|
|
// ── Umfrage: Abstimmen ────────────────────────────────────────────────────
|
|
|
|
public static function handle_vote_poll() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Bitte einloggen um abzustimmen.']);
|
|
|
|
$poll_id = (int)( $_POST['poll_id'] ?? 0 );
|
|
$option_idxs = array_map( 'intval', (array)( $_POST['options'] ?? [] ) );
|
|
|
|
if ( ! $poll_id || empty($option_idxs) ) {
|
|
wp_send_json_error(['message' => 'Ungültige Abstimmung.']);
|
|
}
|
|
|
|
$ok = WBF_DB::vote_poll( $poll_id, $user->id, $option_idxs );
|
|
if ( ! $ok ) {
|
|
wp_send_json_error(['message' => 'Bereits abgestimmt oder Umfrage beendet.']);
|
|
}
|
|
|
|
$results = WBF_DB::get_poll_results( $poll_id );
|
|
$my_votes = WBF_DB::get_user_votes( $poll_id, $user->id );
|
|
wp_send_json_success([
|
|
'results' => $results,
|
|
'my_votes' => $my_votes,
|
|
'total' => array_sum( $results ),
|
|
]);
|
|
}
|
|
|
|
// ── Lesezeichen ───────────────────────────────────────────────────────────
|
|
|
|
public static function handle_toggle_bookmark() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
$thread_id = (int)($_POST['thread_id'] ?? 0);
|
|
if (!$thread_id) wp_send_json_error(['message'=>'Ungültiger Thread.']);
|
|
$added = WBF_DB::toggle_bookmark( $user->id, $thread_id );
|
|
wp_send_json_success(['bookmarked' => $added]);
|
|
}
|
|
|
|
// ── Thread-Präfix setzen ──────────────────────────────────────────────────
|
|
|
|
public static function handle_set_thread_prefix() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
|
$thread_id = (int)($_POST['thread_id'] ?? 0);
|
|
$prefix_id = (int)($_POST['prefix_id'] ?? 0) ?: null;
|
|
if (!$thread_id) wp_send_json_error(['message'=>'Ungültiger Thread.']);
|
|
$thread = WBF_DB::get_thread($thread_id);
|
|
if (!$thread) wp_send_json_error(['message'=>'Thread nicht gefunden.']);
|
|
// Nur Thread-Ersteller oder Mods
|
|
if ( (int)$thread->user_id !== (int)$user->id && !WBF_DB::can($user,'pin_thread') ) {
|
|
wp_send_json_error(['message'=>'Keine Berechtigung.']);
|
|
}
|
|
global $wpdb;
|
|
$wpdb->update( "{$wpdb->prefix}forum_threads", ['prefix_id'=>$prefix_id], ['id'=>$thread_id] );
|
|
$prefix = $prefix_id ? WBF_DB::get_prefix($prefix_id) : null;
|
|
wp_send_json_success(['prefix' => $prefix]);
|
|
}
|
|
|
|
// ── E-Mail-Adresse ändern ─────────────────────────────────────────────────
|
|
|
|
public static function handle_change_email() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
|
|
$new_email = sanitize_email( $_POST['new_email'] ?? '' );
|
|
$password = $_POST['password'] ?? '';
|
|
|
|
if ( ! is_email($new_email) ) {
|
|
wp_send_json_error(['message' => 'Ungültige E-Mail-Adresse.']);
|
|
}
|
|
if ( empty($password) ) {
|
|
wp_send_json_error(['message' => 'Bitte aktuelles Passwort zur Bestätigung eingeben.']);
|
|
}
|
|
if ( ! password_verify($password, $user->password) ) {
|
|
wp_send_json_error(['message' => 'Falsches Passwort.']);
|
|
}
|
|
if ( strtolower($new_email) === strtolower($user->email) ) {
|
|
wp_send_json_error(['message' => 'Das ist bereits deine aktuelle E-Mail-Adresse.']);
|
|
}
|
|
|
|
// Prüfen ob E-Mail bereits vergeben
|
|
$existing = WBF_DB::get_user_by('email', $new_email);
|
|
if ( $existing && (int)$existing->id !== (int)$user->id ) {
|
|
wp_send_json_error(['message' => 'Diese E-Mail-Adresse ist bereits registriert.']);
|
|
}
|
|
|
|
WBF_DB::update_user($user->id, ['email' => $new_email]);
|
|
wp_send_json_success(['message' => 'E-Mail-Adresse erfolgreich geändert.']);
|
|
}
|
|
|
|
// ── Benachrichtigungs-Einstellungen speichern ─────────────────────────────
|
|
|
|
public static function handle_save_notification_prefs() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
|
|
$allowed = ['notify_reply', 'notify_mention', 'notify_message'];
|
|
foreach ( $allowed as $key ) {
|
|
// 1 wenn Checkbox aktiviert, 0 wenn deaktiviert
|
|
$val = isset($_POST[$key]) && $_POST[$key] === '1' ? '1' : '0';
|
|
WBF_DB::set_user_meta($user->id, $key, $val);
|
|
}
|
|
|
|
wp_send_json_success(['message' => 'Benachrichtigungs-Einstellungen gespeichert.']);
|
|
}
|
|
|
|
// ── User ignorieren / Ignorierung aufheben ────────────────────────────────
|
|
|
|
public static function handle_toggle_ignore() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error( ['message' => 'Nicht eingeloggt.'] );
|
|
|
|
$ignored_id = (int)( $_POST['ignored_id'] ?? 0 );
|
|
if ( ! $ignored_id ) wp_send_json_error( ['message' => 'Ungültiger Nutzer.'] );
|
|
if ( $ignored_id === (int)$user->id ) {
|
|
wp_send_json_error( ['message' => 'Du kannst dich nicht selbst ignorieren.'] );
|
|
}
|
|
|
|
$target = WBF_DB::get_user( $ignored_id );
|
|
if ( ! $target ) wp_send_json_error( ['message' => 'Nutzer nicht gefunden.'] );
|
|
|
|
// Prüfen ob diese Rolle geblockt werden darf (konfigurierbar in den Einstellungen)
|
|
if ( ! wbf_can_be_ignored( $target ) ) {
|
|
$role_label = WBF_Roles::get($target->role)['label'] ?? $target->role;
|
|
wp_send_json_error( ['message' => 'Nutzer mit der Rolle "' . $role_label . '" können nicht ignoriert werden.'] );
|
|
}
|
|
|
|
$now_ignored = WBF_DB::toggle_ignore( $user->id, $ignored_id );
|
|
|
|
wp_send_json_success( [
|
|
'ignored' => $now_ignored,
|
|
'ignored_id' => $ignored_id,
|
|
'display_name' => $target->display_name,
|
|
'message' => $now_ignored
|
|
? esc_html( $target->display_name ) . ' wird jetzt ignoriert.'
|
|
: 'Ignorierung von ' . esc_html( $target->display_name ) . ' aufgehoben.',
|
|
] );
|
|
}
|
|
|
|
// ── Discord: Verifikations-Code per Bot-DM senden ─────────────────────────
|
|
|
|
public static function handle_discord_send_code() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
|
|
$s = wbf_get_settings();
|
|
$token = trim($s['discord_bot_token'] ?? '');
|
|
$guild = trim($s['discord_guild_id'] ?? '');
|
|
|
|
if ( ! $token ) {
|
|
wp_send_json_error(['message' => 'Discord-Bot ist noch nicht konfiguriert. Bitte wende dich an einen Admin.']);
|
|
}
|
|
|
|
$username_input = sanitize_text_field($_POST['discord_username'] ?? '');
|
|
if ( ! $username_input ) {
|
|
wp_send_json_error(['message' => 'Bitte Discord-Benutzername eingeben.']);
|
|
}
|
|
|
|
// Nutzer auf dem Guild suchen (nach Username oder per Search)
|
|
$discord_user_id = self::discord_find_user_id($username_input, $token, $guild);
|
|
if ( ! $discord_user_id ) {
|
|
wp_send_json_error(['message' => 'Discord-Nutzer nicht auf dem Server gefunden. Stelle sicher, dass du Mitglied des Servers bist.']);
|
|
}
|
|
|
|
// Verifikations-Code generieren (6-stellig)
|
|
$code = strtoupper(substr(md5(uniqid(mt_rand(), true)), 0, 6));
|
|
$expires = time() + 600; // 10 Minuten
|
|
|
|
// Code + Discord-User-ID temporär speichern
|
|
WBF_DB::set_user_meta($user->id, 'discord_verify_code', $code);
|
|
WBF_DB::set_user_meta($user->id, 'discord_verify_expires', (string)$expires);
|
|
WBF_DB::set_user_meta($user->id, 'discord_verify_pending_id', $discord_user_id);
|
|
|
|
// DM senden
|
|
$sent = self::discord_send_dm($discord_user_id, $token,
|
|
"🔐 **Dein Verifikationscode für " . get_bloginfo('name') . ":**\n\n" .
|
|
"```" . $code . "```\n" .
|
|
"Gib diesen Code im Forum ein. Er ist **10 Minuten** gültig.\n" .
|
|
"_Falls du diese Nachricht nicht erwartet hast, ignoriere sie einfach._"
|
|
);
|
|
|
|
if ( ! $sent ) {
|
|
wp_send_json_error(['message' => 'DM konnte nicht gesendet werden. Stelle sicher, dass du DMs von Server-Mitgliedern zulässt.']);
|
|
}
|
|
|
|
wp_send_json_success(['message' => '✅ Code gesendet! Prüfe deine Discord-DMs und gib den 6-stelligen Code ein.', 'step' => 'enter_code']);
|
|
}
|
|
|
|
// ── Discord: Code überprüfen + Verbindung herstellen ─────────────────────
|
|
|
|
public static function handle_discord_verify_code() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
|
|
$code_input = strtoupper(sanitize_text_field($_POST['verify_code'] ?? ''));
|
|
$meta = WBF_DB::get_user_meta($user->id);
|
|
|
|
$stored_code = strtoupper($meta['discord_verify_code'] ?? '');
|
|
$expires = (int)($meta['discord_verify_expires'] ?? 0);
|
|
$discord_uid = $meta['discord_verify_pending_id'] ?? '';
|
|
|
|
if ( ! $stored_code || ! $discord_uid ) {
|
|
wp_send_json_error(['message' => 'Kein offener Verifizierungs-Vorgang. Bitte erneut starten.']);
|
|
}
|
|
if ( time() > $expires ) {
|
|
wp_send_json_error(['message' => 'Code abgelaufen. Bitte erneut einen Code anfordern.']);
|
|
}
|
|
if ( ! hash_equals($stored_code, $code_input) ) {
|
|
wp_send_json_error(['message' => 'Falscher Code. Bitte erneut versuchen.']);
|
|
}
|
|
|
|
// Discord-Username abrufen (für Anzeige)
|
|
$s = wbf_get_settings();
|
|
$token = trim($s['discord_bot_token'] ?? '');
|
|
$display_name = $discord_uid;
|
|
if ( $token ) {
|
|
$res = wp_remote_get("https://discord.com/api/v10/users/{$discord_uid}", [
|
|
'timeout' => 5,
|
|
'headers' => ['Authorization' => 'Bot ' . $token],
|
|
]);
|
|
if ( ! is_wp_error($res) && wp_remote_retrieve_response_code($res) === 200 ) {
|
|
$d = json_decode(wp_remote_retrieve_body($res), true);
|
|
$display_name = $d['global_name'] ?? $d['username'] ?? $discord_uid;
|
|
}
|
|
}
|
|
|
|
// Speichern
|
|
WBF_DB::set_user_meta($user->id, 'discord_user_id', $discord_uid);
|
|
WBF_DB::set_user_meta($user->id, 'discord_username', $display_name);
|
|
// Temp-Daten löschen
|
|
WBF_DB::set_user_meta($user->id, 'discord_verify_code', '');
|
|
WBF_DB::set_user_meta($user->id, 'discord_verify_expires', '');
|
|
WBF_DB::set_user_meta($user->id, 'discord_verify_pending_id', '');
|
|
|
|
// Rollen-Sync direkt nach Verifikation
|
|
$guild = trim($s['discord_guild_id'] ?? '');
|
|
$role_map = json_decode($s['discord_role_map'] ?? '{}', true) ?: [];
|
|
if ( ($s['discord_role_sync'] ?? '0') === '1' && $token && $guild && $role_map ) {
|
|
wbf_sync_discord_role_for_user($user->id, $discord_uid, $token, $guild, $role_map);
|
|
}
|
|
|
|
wp_send_json_success([
|
|
'message' => '🎉 Discord erfolgreich verknüpft!',
|
|
'connected' => true,
|
|
'display_name' => esc_html($display_name),
|
|
]);
|
|
}
|
|
|
|
// ── Discord: Verbindung trennen ───────────────────────────────────────────
|
|
|
|
public static function handle_save_discord() {
|
|
self::verify();
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
|
|
|
|
$action = sanitize_key( $_POST['sub_action'] ?? 'save' );
|
|
|
|
if ( $action === 'disconnect' ) {
|
|
WBF_DB::set_user_meta($user->id, 'discord_username', '');
|
|
WBF_DB::set_user_meta($user->id, 'discord_user_id', '');
|
|
wp_send_json_success(['message' => 'Discord-Verbindung getrennt.', 'connected' => false]);
|
|
}
|
|
|
|
wp_send_json_error(['message' => 'Unbekannte Aktion.']);
|
|
}
|
|
|
|
// ── Discord Hilfsmethoden ─────────────────────────────────────────────────
|
|
|
|
private static function discord_find_user_id($username_input, $token, $guild) {
|
|
if ( ! $guild ) return null;
|
|
// Guild-Member-Search (max. 1 Treffer)
|
|
$search = rawurlencode($username_input);
|
|
$res = wp_remote_get("https://discord.com/api/v10/guilds/{$guild}/members/search?query={$search}&limit=5", [
|
|
'timeout' => 6,
|
|
'headers' => ['Authorization' => 'Bot ' . $token],
|
|
]);
|
|
if ( is_wp_error($res) || wp_remote_retrieve_response_code($res) !== 200 ) return null;
|
|
$members = json_decode(wp_remote_retrieve_body($res), true);
|
|
if ( empty($members) ) return null;
|
|
// Exakten Treffer bevorzugen
|
|
$input_lower = strtolower($username_input);
|
|
foreach ( $members as $m ) {
|
|
$uname = strtolower($m['user']['username'] ?? '');
|
|
$global = strtolower($m['user']['global_name'] ?? '');
|
|
if ( $uname === $input_lower || $global === $input_lower ) {
|
|
return $m['user']['id'];
|
|
}
|
|
}
|
|
// Erster Treffer als Fallback
|
|
return $members[0]['user']['id'] ?? null;
|
|
}
|
|
|
|
private static function discord_send_dm($user_id, $token, $message) {
|
|
// DM-Channel erstellen
|
|
$ch_res = wp_remote_post('https://discord.com/api/v10/users/@me/channels', [
|
|
'timeout' => 6,
|
|
'headers' => ['Authorization' => 'Bot ' . $token, 'Content-Type' => 'application/json'],
|
|
'body' => json_encode(['recipient_id' => $user_id]),
|
|
]);
|
|
if ( is_wp_error($ch_res) || wp_remote_retrieve_response_code($ch_res) !== 200 ) return false;
|
|
$channel = json_decode(wp_remote_retrieve_body($ch_res), true);
|
|
$ch_id = $channel['id'] ?? '';
|
|
if ( ! $ch_id ) return false;
|
|
|
|
// Nachricht senden
|
|
$msg_res = wp_remote_post("https://discord.com/api/v10/channels/{$ch_id}/messages", [
|
|
'timeout' => 6,
|
|
'headers' => ['Authorization' => 'Bot ' . $token, 'Content-Type' => 'application/json'],
|
|
'body' => json_encode(['content' => $message]),
|
|
]);
|
|
return ( ! is_wp_error($msg_res) && wp_remote_retrieve_response_code($msg_res) === 200 );
|
|
}
|
|
|
|
}
|
|
|
|
add_action( 'init', [ 'WBF_Ajax', 'init' ] ); |