'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() { // Login braucht keinen Nonce — Credentials sind die Authentifizierung $result = WBF_Auth::login( sanitize_text_field($_POST['username'] ?? ''), $_POST['password'] ?? '' ); if ($result['success']) { $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 { wp_send_json_error($result); } } public static function handle_register() { // 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']) { $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); foreach ($subscribers as $sub) { if ((int)$sub->id === (int)$user->id) continue; // nicht sich selbst if (in_array($sub->id, array_column($notif_users, 'id') ?: [])) 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.']); $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 : ''; } 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']; $mime = $_FILES['avatar']['type'] ?? ''; if (!in_array($mime, $allowed_types)) wp_send_json_error(['message'=>'Nur JPG, PNG, GIF und WebP erlaubt.']); if ($_FILES['avatar']['size'] > 2 * 1024 * 1024) wp_send_json_error(['message'=>'Maximale Dateigröße: 2 MB.']); 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]); } // ── 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']; $mime = $_FILES['image']['type'] ?? ''; if ( ! in_array($mime, $allowed_types) ) { wp_send_json_error(['message' => 'Nur JPG, PNG, GIF und WebP erlaubt.']); } // Max 5 MB if ( $_FILES['image']['size'] > 5 * 1024 * 1024 ) { wp_send_json_error(['message' => 'Maximale Dateigröße: 5 MB.']); } 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.']); $results = WBF_DB::search( $query, 40 ); 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.']); 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.']); $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() { $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) . '%'; $users = $wpdb->get_results($wpdb->prepare( "SELECT id, username, display_name, avatar_url, role FROM {$wpdb->prefix}forum_users WHERE username LIKE %s OR display_name LIKE %s 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; $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, "{$actor_name} hat in deinem Thread " . esc_html($thread_title) . " 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, "{$actor_name} hat dich in einem Beitrag erwähnt: " . esc_html($thread_title) . "", $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, "{$actor_name} 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 "
"; } // ── 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.']); $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 1 Stunde 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() { self::verify(); $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.']); $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]); } } add_action( 'init', [ 'WBF_Ajax', 'init' ] );