Update from Git Manager GUI
This commit is contained in:
833
includes/class-forum-ajax.php
Normal file
833
includes/class-forum-ajax.php
Normal file
@@ -0,0 +1,833 @@
|
|||||||
|
<?php
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||||
|
|
||||||
|
class WBF_Ajax {
|
||||||
|
|
||||||
|
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_post_image',
|
||||||
|
'wbf_forgot_password', 'wbf_reset_password', 'wbf_load_more_messages',
|
||||||
|
'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',
|
||||||
|
];
|
||||||
|
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() {
|
||||||
|
// 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() {
|
||||||
|
// Register braucht keinen Nonce
|
||||||
|
$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'];
|
||||||
|
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.']);
|
||||||
|
|
||||||
|
$title = sanitize_text_field($_POST['title'] ?? '');
|
||||||
|
$content = WBF_BBCode::sanitize( $_POST['content'] ?? '' );
|
||||||
|
$category_id = (int)($_POST['category_id'] ?? 0);
|
||||||
|
|
||||||
|
if (strlen($title) < 5) wp_send_json_error(['message'=>'Titel zu kurz (min. 5 Zeichen).']);
|
||||||
|
if (strlen($content) < 10) wp_send_json_error(['message'=>'Inhalt zu kurz (min. 10 Zeichen).']);
|
||||||
|
if (!$category_id) wp_send_json_error(['message'=>'Keine Kategorie gewählt.']);
|
||||||
|
|
||||||
|
$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' => $title,
|
||||||
|
'slug' => sanitize_title($title) . '-' . time(),
|
||||||
|
'content' => $content,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Tags speichern
|
||||||
|
$raw_tags = sanitize_text_field( $_POST['tags'] ?? '' );
|
||||||
|
if ( $raw_tags ) {
|
||||||
|
WBF_DB::sync_thread_tags( $id, $raw_tags );
|
||||||
|
}
|
||||||
|
|
||||||
|
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.']);
|
||||||
|
|
||||||
|
$thread_id = (int)($_POST['thread_id'] ?? 0);
|
||||||
|
$content = WBF_BBCode::sanitize( $_POST['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);
|
||||||
|
// E-Mail
|
||||||
|
$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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
// @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::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::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);
|
||||||
|
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.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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,
|
||||||
|
"<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.']);
|
||||||
|
|
||||||
|
$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() {
|
||||||
|
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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
WBF_Ajax::init();
|
||||||
103
includes/class-forum-auth.php
Normal file
103
includes/class-forum-auth.php
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||||
|
|
||||||
|
class WBF_Auth {
|
||||||
|
|
||||||
|
const SESSION_KEY = 'wbf_forum_user';
|
||||||
|
|
||||||
|
public static function init() {
|
||||||
|
if ( ! session_id() ) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
// Auto-login via Remember-Me cookie if not already logged in
|
||||||
|
if ( empty( $_SESSION[ self::SESSION_KEY ] ) && isset( $_COOKIE['wbf_remember'] ) ) {
|
||||||
|
$row = WBF_DB::verify_remember_token( $_COOKIE['wbf_remember'] );
|
||||||
|
if ( $row ) {
|
||||||
|
$user = WBF_DB::get_user( (int)$row->user_id );
|
||||||
|
if ( $user && WBF_Roles::level($user->role) >= 0 ) {
|
||||||
|
$_SESSION[ self::SESSION_KEY ] = $user->id;
|
||||||
|
WBF_DB::touch_last_active( $user->id );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function is_forum_logged_in() {
|
||||||
|
self::init();
|
||||||
|
return ! empty( $_SESSION[ self::SESSION_KEY ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get_current_user() {
|
||||||
|
self::init();
|
||||||
|
if ( empty( $_SESSION[ self::SESSION_KEY ] ) ) return null;
|
||||||
|
return WBF_DB::get_user( (int) $_SESSION[ self::SESSION_KEY ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function login( $username_or_email, $password ) {
|
||||||
|
self::init();
|
||||||
|
$user = WBF_DB::get_user_by( 'username', $username_or_email );
|
||||||
|
if ( ! $user ) {
|
||||||
|
$user = WBF_DB::get_user_by( 'email', $username_or_email );
|
||||||
|
}
|
||||||
|
if ( ! $user ) return array( 'success' => false, 'message' => 'Benutzer nicht gefunden.' );
|
||||||
|
if ( ! password_verify( $password, $user->password ) ) {
|
||||||
|
return array( 'success' => false, 'message' => 'Falsches Passwort.' );
|
||||||
|
}
|
||||||
|
if ( WBF_Roles::level($user->role) < 0 ) {
|
||||||
|
$reason = !empty($user->ban_reason) ? $user->ban_reason : 'Dein Konto wurde gesperrt.';
|
||||||
|
return array( 'success' => false, 'banned' => true, 'message' => $reason );
|
||||||
|
}
|
||||||
|
$_SESSION[ self::SESSION_KEY ] = $user->id;
|
||||||
|
WBF_DB::touch_last_active( $user->id );
|
||||||
|
return array( 'success' => true, 'user' => $user );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function register( $username, $email, $password, $display_name ) {
|
||||||
|
self::init();
|
||||||
|
$username = sanitize_user( $username );
|
||||||
|
$email = sanitize_email( $email );
|
||||||
|
$display_name = sanitize_text_field( $display_name );
|
||||||
|
|
||||||
|
if ( strlen($username) < 3 ) return array('success'=>false,'message'=>'Benutzername mindestens 3 Zeichen.');
|
||||||
|
if ( ! is_email($email) ) return array('success'=>false,'message'=>'Ungültige E-Mail-Adresse.');
|
||||||
|
if ( strlen($password) < 6 ) return array('success'=>false,'message'=>'Passwort mindestens 6 Zeichen.');
|
||||||
|
if ( empty($display_name) ) return array('success'=>false,'message'=>'Anzeigename darf nicht leer sein.');
|
||||||
|
|
||||||
|
if ( WBF_DB::get_user_by('username', $username) ) return array('success'=>false,'message'=>'Benutzername bereits vergeben.');
|
||||||
|
if ( WBF_DB::get_user_by('email', $email) ) return array('success'=>false,'message'=>'E-Mail bereits registriert.');
|
||||||
|
|
||||||
|
$avatar = 'https://www.gravatar.com/avatar/' . md5( strtolower($email) ) . '?d=identicon&s=80';
|
||||||
|
|
||||||
|
$id = WBF_DB::create_user( array(
|
||||||
|
'username' => $username,
|
||||||
|
'email' => $email,
|
||||||
|
'password' => password_hash( $password, PASSWORD_DEFAULT ),
|
||||||
|
'display_name' => $display_name,
|
||||||
|
'avatar_url' => $avatar,
|
||||||
|
));
|
||||||
|
|
||||||
|
$_SESSION[ self::SESSION_KEY ] = $id;
|
||||||
|
return array('success'=>true,'user'=>WBF_DB::get_user($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function logout() {
|
||||||
|
self::init();
|
||||||
|
$user_id = $_SESSION[ self::SESSION_KEY ] ?? 0;
|
||||||
|
unset( $_SESSION[ self::SESSION_KEY ] );
|
||||||
|
if ( $user_id ) {
|
||||||
|
WBF_DB::delete_remember_token( (int)$user_id );
|
||||||
|
}
|
||||||
|
// Remove cookie
|
||||||
|
if ( isset($_COOKIE['wbf_remember']) ) {
|
||||||
|
setcookie( 'wbf_remember', '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remember-Me Token setzen und Cookie senden */
|
||||||
|
public static function set_remember_cookie( $user_id ) {
|
||||||
|
$token = WBF_DB::create_remember_token( (int)$user_id );
|
||||||
|
setcookie( 'wbf_remember', $token, time() + 30 * DAY_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_action('init', array('WBF_Auth','init'), 1);
|
||||||
249
includes/class-forum-bbcode.php
Normal file
249
includes/class-forum-bbcode.php
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
<?php
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WBF_BBCode — Sicherer BBCode → HTML Parser
|
||||||
|
*
|
||||||
|
* Unterstützte Tags:
|
||||||
|
* [b], [i], [u], [s], [h2], [h3]
|
||||||
|
* [code], [icode]
|
||||||
|
* [quote], [quote=Name]
|
||||||
|
* [spoiler], [spoiler=Titel]
|
||||||
|
* [color=...], [size=small|large|xlarge]
|
||||||
|
* [url=...], [url]
|
||||||
|
* [img]
|
||||||
|
* [list], [list=1], [*]
|
||||||
|
* [center], [right]
|
||||||
|
* [hr]
|
||||||
|
*/
|
||||||
|
class WBF_BBCode {
|
||||||
|
|
||||||
|
// Erlaubte Farben (Hex oder benannt, whitelist zur XSS-Prävention)
|
||||||
|
private static $allowed_colors = [
|
||||||
|
'red','blue','green','orange','yellow','purple','pink',
|
||||||
|
'cyan','white','gray','grey','black','gold','silver','lime','teal','navy',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hauptmethode: BBCode → sicheres HTML
|
||||||
|
* Immer über diese Methode rendern!
|
||||||
|
*/
|
||||||
|
public static function render( $content ) {
|
||||||
|
if ( empty( $content ) ) return '';
|
||||||
|
|
||||||
|
// 1. HTML-Entities escapen (verhindert XSS aus rohem Input)
|
||||||
|
$out = htmlspecialchars( $content, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' );
|
||||||
|
|
||||||
|
// 2. Zeilenumbrüche vormerken (nach Tag-Parsing ersetzen)
|
||||||
|
$out = str_replace( "\r\n", "\n", $out );
|
||||||
|
|
||||||
|
// 3. [code]-Blöcke VOR allem anderen rausziehen & schützen
|
||||||
|
$placeholders = [];
|
||||||
|
$out = preg_replace_callback(
|
||||||
|
'/\[code\](.*?)\[\/code\]/is',
|
||||||
|
function ( $m ) use ( &$placeholders ) {
|
||||||
|
$key = '%%CODE_' . count($placeholders) . '%%';
|
||||||
|
$placeholders[$key] = '<pre class="wbf-bb-code"><code>'
|
||||||
|
. $m[1] // bereits html-escaped durch htmlspecialchars oben
|
||||||
|
. '</code></pre>';
|
||||||
|
return $key;
|
||||||
|
},
|
||||||
|
$out
|
||||||
|
);
|
||||||
|
|
||||||
|
// Inline-Code
|
||||||
|
$out = preg_replace_callback(
|
||||||
|
'/\[icode\](.*?)\[\/icode\]/is',
|
||||||
|
function ( $m ) use ( &$placeholders ) {
|
||||||
|
$key = '%%ICODE_' . count($placeholders) . '%%';
|
||||||
|
$placeholders[$key] = '<code class="wbf-bb-icode">' . $m[1] . '</code>';
|
||||||
|
return $key;
|
||||||
|
},
|
||||||
|
$out
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Alle anderen Tags parsen
|
||||||
|
$out = self::parse( $out );
|
||||||
|
|
||||||
|
// 5. Zeilenumbrüche zu <br> (nur außerhalb von Block-Tags)
|
||||||
|
$out = self::nl_to_br( $out );
|
||||||
|
|
||||||
|
// 6. Code-Blöcke wieder einsetzen
|
||||||
|
foreach ( $placeholders as $key => $html ) {
|
||||||
|
$out = str_replace( $key, $html, $out );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize bei Speicherung: nur HTML streifen, BBCode-Tags bleiben
|
||||||
|
*/
|
||||||
|
public static function sanitize( $raw ) {
|
||||||
|
// Alle echten HTML-Tags entfernen, BBCode-Tags [xxx] bleiben erhalten
|
||||||
|
return strip_tags( $raw );
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Interner Parser ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static function parse( $s ) {
|
||||||
|
|
||||||
|
// [b] [i] [u] [s]
|
||||||
|
$s = preg_replace( '/\[b\](.*?)\[\/b\]/is', '<strong>$1</strong>', $s );
|
||||||
|
$s = preg_replace( '/\[i\](.*?)\[\/i\]/is', '<em>$1</em>', $s );
|
||||||
|
$s = preg_replace( '/\[u\](.*?)\[\/u\]/is', '<u>$1</u>', $s );
|
||||||
|
$s = preg_replace( '/\[s\](.*?)\[\/s\]/is', '<s>$1</s>', $s );
|
||||||
|
|
||||||
|
// [h2] [h3]
|
||||||
|
$s = preg_replace( '/\[h2\](.*?)\[\/h2\]/is', '<h2 class="wbf-bb-h2">$1</h2>', $s );
|
||||||
|
$s = preg_replace( '/\[h3\](.*?)\[\/h3\]/is', '<h3 class="wbf-bb-h3">$1</h3>', $s );
|
||||||
|
|
||||||
|
// [center] [right]
|
||||||
|
$s = preg_replace( '/\[center\](.*?)\[\/center\]/is', '<div class="wbf-bb-center">$1</div>', $s );
|
||||||
|
$s = preg_replace( '/\[right\](.*?)\[\/right\]/is', '<div class="wbf-bb-right">$1</div>', $s );
|
||||||
|
|
||||||
|
// [hr]
|
||||||
|
$s = str_replace( '[hr]', '<hr class="wbf-bb-hr">', $s );
|
||||||
|
|
||||||
|
// [color=...]
|
||||||
|
$s = preg_replace_callback(
|
||||||
|
'/\[color=([a-zA-Z0-9#]{1,20})\](.*?)\[\/color\]/is',
|
||||||
|
function ( $m ) {
|
||||||
|
$color = $m[1];
|
||||||
|
// Hex-Farben direkt erlauben, benannte aus Whitelist
|
||||||
|
if ( preg_match( '/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $color ) ) {
|
||||||
|
$safe = esc_attr( $color );
|
||||||
|
} elseif ( in_array( strtolower($color), self::$allowed_colors ) ) {
|
||||||
|
$safe = esc_attr( strtolower($color) );
|
||||||
|
} else {
|
||||||
|
return $m[2]; // Unbekannte Farbe → nur Text
|
||||||
|
}
|
||||||
|
return '<span style="color:' . $safe . '">' . $m[2] . '</span>';
|
||||||
|
},
|
||||||
|
$s
|
||||||
|
);
|
||||||
|
|
||||||
|
// [size=small|large|xlarge]
|
||||||
|
$s = preg_replace_callback(
|
||||||
|
'/\[size=(small|large|xlarge)\](.*?)\[\/size\]/is',
|
||||||
|
function ( $m ) {
|
||||||
|
$map = [ 'small' => '.8em', 'large' => '1.2em', 'xlarge' => '1.5em' ];
|
||||||
|
return '<span style="font-size:' . $map[$m[1]] . '">' . $m[2] . '</span>';
|
||||||
|
},
|
||||||
|
$s
|
||||||
|
);
|
||||||
|
|
||||||
|
// [url=...] und [url]...[/url]
|
||||||
|
$s = preg_replace_callback(
|
||||||
|
'/\[url=([^\]]{1,500})\](.*?)\[\/url\]/is',
|
||||||
|
function ( $m ) {
|
||||||
|
$href = esc_url( $m[1] );
|
||||||
|
if ( ! $href ) return $m[2];
|
||||||
|
return '<a href="' . $href . '" target="_blank" rel="noopener noreferrer" class="wbf-bb-link">' . $m[2] . '</a>';
|
||||||
|
},
|
||||||
|
$s
|
||||||
|
);
|
||||||
|
$s = preg_replace_callback(
|
||||||
|
'/\[url\](https?:\/\/[^\[]{1,500})\[\/url\]/is',
|
||||||
|
function ( $m ) {
|
||||||
|
$href = esc_url( $m[1] );
|
||||||
|
if ( ! $href ) return $m[1];
|
||||||
|
return '<a href="' . $href . '" target="_blank" rel="noopener noreferrer" class="wbf-bb-link">' . $href . '</a>';
|
||||||
|
},
|
||||||
|
$s
|
||||||
|
);
|
||||||
|
|
||||||
|
// [img]
|
||||||
|
$s = preg_replace_callback(
|
||||||
|
'/\[img\](https?:\/\/[^\[]{1,1000})\[\/img\]/is',
|
||||||
|
function ( $m ) {
|
||||||
|
$src = esc_url( $m[1] );
|
||||||
|
if ( ! $src ) return '';
|
||||||
|
return '<img src="' . $src . '" class="wbf-bb-img" alt="" loading="lazy">';
|
||||||
|
},
|
||||||
|
$s
|
||||||
|
);
|
||||||
|
|
||||||
|
// [quote] und [quote=Name]
|
||||||
|
$s = preg_replace_callback(
|
||||||
|
'/\[quote=([^\]]{1,80})\](.*?)\[\/quote\]/is',
|
||||||
|
function ( $m ) {
|
||||||
|
$author = '<strong>' . htmlspecialchars( $m[1], ENT_QUOTES ) . ' schrieb:</strong>';
|
||||||
|
return '<blockquote class="wbf-bb-quote"><span class="wbf-bb-quote__author">'
|
||||||
|
. $author . '</span>' . $m[2] . '</blockquote>';
|
||||||
|
},
|
||||||
|
$s
|
||||||
|
);
|
||||||
|
$s = preg_replace(
|
||||||
|
'/\[quote\](.*?)\[\/quote\]/is',
|
||||||
|
'<blockquote class="wbf-bb-quote">$1</blockquote>',
|
||||||
|
$s
|
||||||
|
);
|
||||||
|
|
||||||
|
// [spoiler] und [spoiler=Titel]
|
||||||
|
static $spoiler_id = 0;
|
||||||
|
$s = preg_replace_callback(
|
||||||
|
'/\[spoiler=([^\]]{0,80})\](.*?)\[\/spoiler\]/is',
|
||||||
|
function ( $m ) use ( &$spoiler_id ) {
|
||||||
|
$spoiler_id++;
|
||||||
|
$title = htmlspecialchars( $m[1] ?: 'Spoiler', ENT_QUOTES );
|
||||||
|
return '<div class="wbf-bb-spoiler" id="wbf-spoiler-' . $spoiler_id . '">'
|
||||||
|
. '<button type="button" class="wbf-bb-spoiler__btn" onclick="var c=this.nextElementSibling;c.style.display=c.style.display===\'none\'?\'block\':\'none\'">'
|
||||||
|
. '<i class="fas fa-eye-slash"></i> ' . $title
|
||||||
|
. '</button>'
|
||||||
|
. '<div class="wbf-bb-spoiler__body" style="display:none">' . $m[2] . '</div>'
|
||||||
|
. '</div>';
|
||||||
|
},
|
||||||
|
$s
|
||||||
|
);
|
||||||
|
$s = preg_replace_callback(
|
||||||
|
'/\[spoiler\](.*?)\[\/spoiler\]/is',
|
||||||
|
function ( $m ) use ( &$spoiler_id ) {
|
||||||
|
$spoiler_id++;
|
||||||
|
return '<div class="wbf-bb-spoiler" id="wbf-spoiler-' . $spoiler_id . '">'
|
||||||
|
. '<button type="button" class="wbf-bb-spoiler__btn" onclick="var c=this.nextElementSibling;c.style.display=c.style.display===\'none\'?\'block\':\'none\'">'
|
||||||
|
. '<i class="fas fa-eye-slash"></i> Spoiler'
|
||||||
|
. '</button>'
|
||||||
|
. '<div class="wbf-bb-spoiler__body" style="display:none">' . $m[1] . '</div>'
|
||||||
|
. '</div>';
|
||||||
|
},
|
||||||
|
$s
|
||||||
|
);
|
||||||
|
|
||||||
|
// [list] [list=1] [*]
|
||||||
|
$s = preg_replace_callback(
|
||||||
|
'/\[list(=1)?\](.*?)\[\/list\]/is',
|
||||||
|
function ( $m ) {
|
||||||
|
$tag = $m[1] ? 'ol' : 'ul';
|
||||||
|
$items = preg_replace( '/\[\*\]\s*/s', '<li>', $m[2] );
|
||||||
|
// Auto-close li tags
|
||||||
|
$items = preg_replace( '/(<li>)(.*?)(?=<li>|$)/s', '$1$2</li>', $items );
|
||||||
|
return '<' . $tag . ' class="wbf-bb-list">' . trim($items) . '</' . $tag . '>';
|
||||||
|
},
|
||||||
|
$s
|
||||||
|
);
|
||||||
|
|
||||||
|
// @Erwähnungen → klickbare Profil-Links
|
||||||
|
$s = preg_replace_callback(
|
||||||
|
'/@([a-zA-Z0-9_]{3,60})\b/',
|
||||||
|
function( $m ) {
|
||||||
|
global $wpdb;
|
||||||
|
$user = $wpdb->get_row( $wpdb->prepare(
|
||||||
|
"SELECT id, username FROM {$wpdb->prefix}forum_users WHERE username=%s", $m[1]
|
||||||
|
) );
|
||||||
|
if ( ! $user ) return esc_html($m[0]);
|
||||||
|
return '<a href="?forum_profile=' . (int)$user->id . '" class="wbf-mention">@' . esc_html($user->username) . '</a>';
|
||||||
|
},
|
||||||
|
$s
|
||||||
|
);
|
||||||
|
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zeilenumbrüche → <br>, aber nicht innerhalb von Block-Elementen
|
||||||
|
private static function nl_to_br( $s ) {
|
||||||
|
// Einfaches nl2br — ausreichend da Block-Tags (<h2>, <ul>, etc.)
|
||||||
|
// bereits eigene Zeilenumbrüche erzeugen
|
||||||
|
return nl2br( $s );
|
||||||
|
}
|
||||||
|
}
|
||||||
1137
includes/class-forum-db.php
Normal file
1137
includes/class-forum-db.php
Normal file
File diff suppressed because it is too large
Load Diff
130
includes/class-forum-levels.php
Normal file
130
includes/class-forum-levels.php
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WBF_Levels — Post-Count-basiertes Level-System
|
||||||
|
* Levels werden in wp_options gespeichert und können im Admin verwaltet werden.
|
||||||
|
* Das System kann global aktiviert/deaktiviert werden.
|
||||||
|
*/
|
||||||
|
class WBF_Levels {
|
||||||
|
|
||||||
|
const OPTION_KEY = 'wbf_level_config';
|
||||||
|
const ENABLED_KEY = 'wbf_levels_enabled';
|
||||||
|
|
||||||
|
// ── An/Aus ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public static function is_enabled() {
|
||||||
|
return (bool) get_option( self::ENABLED_KEY, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function set_enabled( $bool ) {
|
||||||
|
update_option( self::ENABLED_KEY, (bool) $bool );
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Standard-Level beim ersten Aktivieren ────────────────────
|
||||||
|
|
||||||
|
private static function default_levels() {
|
||||||
|
return [
|
||||||
|
[ 'min' => 0, 'label' => 'Neuling', 'icon' => 'fas fa-seedling', 'color' => '#94a3b8' ],
|
||||||
|
[ 'min' => 10, 'label' => 'Schreiberling', 'icon' => 'fas fa-feather', 'color' => '#60a5fa' ],
|
||||||
|
[ 'min' => 50, 'label' => 'Erfahrener', 'icon' => 'fas fa-fire', 'color' => '#f97316' ],
|
||||||
|
[ 'min' => 150, 'label' => 'Veteran', 'icon' => 'fas fa-shield-halved', 'color' => '#a78bfa' ],
|
||||||
|
[ 'min' => 500, 'label' => 'Legende', 'icon' => 'fas fa-crown', 'color' => '#fbbf24' ],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Laden / Speichern ─────────────────────────────────────────
|
||||||
|
|
||||||
|
public static function get_all() {
|
||||||
|
$saved = get_option( self::OPTION_KEY, null );
|
||||||
|
if ( $saved === null ) {
|
||||||
|
$defaults = self::default_levels();
|
||||||
|
update_option( self::OPTION_KEY, $defaults );
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
$levels = (array) $saved;
|
||||||
|
usort( $levels, fn($a,$b) => (int)$a['min'] <=> (int)$b['min'] );
|
||||||
|
return $levels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function save( $levels ) {
|
||||||
|
usort( $levels, fn($a,$b) => (int)$a['min'] <=> (int)$b['min'] );
|
||||||
|
update_option( self::OPTION_KEY, $levels );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function reset_to_defaults() {
|
||||||
|
update_option( self::OPTION_KEY, self::default_levels() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Level für eine Beitragsanzahl ermitteln ───────────────────
|
||||||
|
|
||||||
|
public static function get_for_count( $post_count ) {
|
||||||
|
$levels = self::get_all();
|
||||||
|
// Von oben (höchster min) nach unten suchen
|
||||||
|
$sorted = array_reverse( $levels );
|
||||||
|
foreach ( $sorted as $level ) {
|
||||||
|
if ( (int) $post_count >= (int) $level['min'] ) {
|
||||||
|
return $level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $levels[0]; // Fallback: niedrigstes Level
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nächstes Level (für Fortschrittsanzeige)
|
||||||
|
public static function get_next( $post_count ) {
|
||||||
|
$levels = self::get_all(); // bereits aufsteigend sortiert
|
||||||
|
foreach ( $levels as $level ) {
|
||||||
|
if ( (int) $level['min'] > (int) $post_count ) {
|
||||||
|
return $level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null; // Maxlevel erreicht
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fortschritt in Prozent zum nächsten Level (0–100)
|
||||||
|
public static function progress( $post_count ) {
|
||||||
|
$current = self::get_for_count( $post_count );
|
||||||
|
$next = self::get_next( $post_count );
|
||||||
|
if ( ! $next ) return 100;
|
||||||
|
$range = (int) $next['min'] - (int) $current['min'];
|
||||||
|
if ( $range <= 0 ) return 100;
|
||||||
|
$done = (int) $post_count - (int) $current['min'];
|
||||||
|
return min( 100, (int) round( $done / $range * 100 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Badge HTML ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public static function badge( $post_count ) {
|
||||||
|
if ( ! self::is_enabled() ) return '';
|
||||||
|
$level = self::get_for_count( $post_count );
|
||||||
|
$label = esc_html( $level['label'] );
|
||||||
|
$icon = esc_attr( $level['icon'] );
|
||||||
|
$color = esc_attr( $level['color'] );
|
||||||
|
return "<span class=\"wbf-level-badge\" style=\"--lc:{$color}\">"
|
||||||
|
. "<i class=\"{$icon}\"></i> {$label}</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Fortschrittsbalken HTML (für Profil-Sidebar) ──────────────
|
||||||
|
|
||||||
|
public static function progress_bar( $post_count ) {
|
||||||
|
if ( ! self::is_enabled() ) return '';
|
||||||
|
$current = self::get_for_count( $post_count );
|
||||||
|
$next = self::get_next( $post_count );
|
||||||
|
$pct = self::progress( $post_count );
|
||||||
|
$color = esc_attr( $current['color'] );
|
||||||
|
$cur_lbl = esc_html( $current['label'] );
|
||||||
|
$next_lbl = $next ? esc_html( $next['label'] ) : $cur_lbl;
|
||||||
|
$posts_to = $next ? ( (int)$next['min'] - (int)$post_count ) . ' Beiträge bis ' . $next_lbl : 'Maximales Level erreicht';
|
||||||
|
|
||||||
|
return "
|
||||||
|
<div class=\"wbf-level-progress\">
|
||||||
|
<div class=\"wbf-level-progress__labels\">
|
||||||
|
<span style=\"color:{$color}\">" . self::badge( $post_count ) . "</span>
|
||||||
|
<span class=\"wbf-level-progress__hint\">{$posts_to}</span>
|
||||||
|
</div>
|
||||||
|
<div class=\"wbf-level-progress__bar\">
|
||||||
|
<div class=\"wbf-level-progress__fill\" style=\"width:{$pct}%;background:{$color}\"></div>
|
||||||
|
</div>
|
||||||
|
</div>";
|
||||||
|
}
|
||||||
|
}
|
||||||
211
includes/class-forum-roles.php
Normal file
211
includes/class-forum-roles.php
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
<?php
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WBF_Roles — Dynamisches Rollensystem
|
||||||
|
* Rollen werden in wp_options gespeichert und können im Admin verwaltet werden.
|
||||||
|
* Der Superadmin ist immer an den WP-Administrator gebunden und kann nicht geändert werden.
|
||||||
|
*/
|
||||||
|
class WBF_Roles {
|
||||||
|
|
||||||
|
const OPTION_KEY = 'wbf_custom_roles';
|
||||||
|
const SUPERADMIN = 'superadmin';
|
||||||
|
|
||||||
|
/** Standard-Rollen beim ersten Aktivieren */
|
||||||
|
private static function default_roles() {
|
||||||
|
return [
|
||||||
|
'superadmin' => [
|
||||||
|
'label' => 'Superadmin',
|
||||||
|
'level' => 100,
|
||||||
|
'color' => '#e11d48',
|
||||||
|
'bg_color' => 'rgba(225,29,72,.15)',
|
||||||
|
'icon' => 'fas fa-crown',
|
||||||
|
'permissions' => ['all'],
|
||||||
|
'locked' => true, // unveränderlich
|
||||||
|
'description' => 'Vollständige Kontrolle — immer an den WordPress-Admin gebunden.',
|
||||||
|
],
|
||||||
|
'admin' => [
|
||||||
|
'label' => 'Admin',
|
||||||
|
'level' => 80,
|
||||||
|
'color' => '#f87171',
|
||||||
|
'bg_color' => 'rgba(248,113,113,.13)',
|
||||||
|
'icon' => 'fas fa-shield-halved',
|
||||||
|
'permissions' => ['post','create_thread','like','pin_thread','close_thread','delete_post','delete_thread','manage_users','manage_cats','post_announcement'],
|
||||||
|
'locked' => false,
|
||||||
|
'description' => 'Volle Moderations- und Verwaltungsrechte.',
|
||||||
|
],
|
||||||
|
'moderator' => [
|
||||||
|
'label' => 'Moderator',
|
||||||
|
'level' => 50,
|
||||||
|
'color' => '#fbbf24',
|
||||||
|
'bg_color' => 'rgba(251,191,36,.12)',
|
||||||
|
'icon' => 'fas fa-shield',
|
||||||
|
'permissions' => ['post','create_thread','like','pin_thread','close_thread','delete_post','delete_thread','post_announcement'],
|
||||||
|
'locked' => false,
|
||||||
|
'description' => 'Kann Threads & Posts moderieren.',
|
||||||
|
],
|
||||||
|
'vip' => [
|
||||||
|
'label' => 'VIP',
|
||||||
|
'level' => 20,
|
||||||
|
'color' => '#38bdf8',
|
||||||
|
'bg_color' => 'rgba(56,189,248,.12)',
|
||||||
|
'icon' => 'fas fa-star',
|
||||||
|
'permissions' => ['post','create_thread','like'],
|
||||||
|
'locked' => false,
|
||||||
|
'description' => 'VIP-Mitglied mit besonderem Badge.',
|
||||||
|
],
|
||||||
|
'member' => [
|
||||||
|
'label' => 'Member',
|
||||||
|
'level' => 10,
|
||||||
|
'color' => '#94a3b8',
|
||||||
|
'bg_color' => 'rgba(148,163,184,.1)',
|
||||||
|
'icon' => 'fas fa-user',
|
||||||
|
'permissions' => ['post','create_thread','like'],
|
||||||
|
'locked' => false,
|
||||||
|
'description' => 'Standard-Mitglied.',
|
||||||
|
],
|
||||||
|
'banned' => [
|
||||||
|
'label' => 'Gesperrt',
|
||||||
|
'level' => -1,
|
||||||
|
'color' => '#475569',
|
||||||
|
'bg_color' => 'rgba(71,85,105,.2)',
|
||||||
|
'icon' => 'fas fa-ban',
|
||||||
|
'permissions' => [],
|
||||||
|
'locked' => false,
|
||||||
|
'description' => 'Kein Forum-Zugang.',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Alle Rollen laden (aus DB oder Defaults) */
|
||||||
|
public static function get_all() {
|
||||||
|
$saved = get_option(self::OPTION_KEY, null);
|
||||||
|
if ( $saved === null ) {
|
||||||
|
$defaults = self::default_roles();
|
||||||
|
update_option(self::OPTION_KEY, $defaults);
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
// Superadmin immer aus defaults übernehmen (kann nicht manipuliert werden)
|
||||||
|
$saved[self::SUPERADMIN] = self::default_roles()[self::SUPERADMIN];
|
||||||
|
return $saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Einzelne Rolle */
|
||||||
|
public static function get( $key ) {
|
||||||
|
$all = self::get_all();
|
||||||
|
return $all[$key] ?? $all['member'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Alle verfügbaren Rollen als key=>label Array */
|
||||||
|
public static function labels() {
|
||||||
|
$out = [];
|
||||||
|
foreach ( self::get_sorted() as $key => $role ) {
|
||||||
|
$out[$key] = $role['label'];
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Nach Level sortiert (höchstes zuerst) */
|
||||||
|
public static function get_sorted() {
|
||||||
|
$all = self::get_all();
|
||||||
|
uasort($all, fn($a,$b) => $b['level'] <=> $a['level']);
|
||||||
|
return $all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rolle speichern / erstellen */
|
||||||
|
public static function save( $key, $data ) {
|
||||||
|
if ( $key === self::SUPERADMIN ) return false; // nie überschreiben
|
||||||
|
$all = self::get_all();
|
||||||
|
$all[$key] = $data;
|
||||||
|
update_option(self::OPTION_KEY, $all);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rolle löschen */
|
||||||
|
public static function delete( $key ) {
|
||||||
|
if ( $key === self::SUPERADMIN ) return false;
|
||||||
|
if ( $key === 'member' ) return false; // member darf nicht gelöscht werden
|
||||||
|
$all = self::get_all();
|
||||||
|
unset($all[$key]);
|
||||||
|
update_option(self::OPTION_KEY, $all);
|
||||||
|
// Alle Nutzer dieser Rolle zu 'member' degradieren
|
||||||
|
global $wpdb;
|
||||||
|
$wpdb->update("{$wpdb->prefix}forum_users", ['role'=>'member'], ['role'=>$key]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Level einer Rolle */
|
||||||
|
public static function level( $key ) {
|
||||||
|
return (int)( self::get($key)['level'] ?? 10 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Hat Rolle eine bestimmte Permission? */
|
||||||
|
public static function has_permission( $role_key, $permission ) {
|
||||||
|
$role = self::get($role_key);
|
||||||
|
$perms = $role['permissions'] ?? [];
|
||||||
|
return in_array('all', $perms) || in_array($permission, $perms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Darf User eine Aktion ausführen? */
|
||||||
|
public static function can( $user, $action ) {
|
||||||
|
if ( ! $user ) return false;
|
||||||
|
// Superadmin — immer alles erlaubt
|
||||||
|
if ( $user->role === self::SUPERADMIN ) return true;
|
||||||
|
if ( self::level($user->role) < 0 ) return false; // banned
|
||||||
|
return self::has_permission($user->role, $action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Darf User in Kategorie posten? */
|
||||||
|
public static function can_post_in( $user, $cat ) {
|
||||||
|
if ( ! $user ) return false;
|
||||||
|
if ( $user->role === self::SUPERADMIN ) return true;
|
||||||
|
$min = $cat->min_role ?? 'member';
|
||||||
|
return self::level($user->role) >= self::level($min);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Badge HTML */
|
||||||
|
public static function badge( $role_key ) {
|
||||||
|
$role = self::get($role_key);
|
||||||
|
$label = esc_html($role['label']);
|
||||||
|
$color = esc_attr($role['color']);
|
||||||
|
$bg = esc_attr($role['bg_color']);
|
||||||
|
$icon = esc_attr($role['icon'] ?? 'fas fa-user');
|
||||||
|
$border = esc_attr($role['color']);
|
||||||
|
return "<span class=\"wbf-role-badge\" style=\"color:{$color};background:{$bg};border-color:{$border}\">
|
||||||
|
<i class=\"{$icon}\"></i> {$label}
|
||||||
|
</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Alle erlaubten Permissions (für Checkboxen im Admin) */
|
||||||
|
public static function all_permissions() {
|
||||||
|
return [
|
||||||
|
'post' => 'Beiträge schreiben',
|
||||||
|
'create_thread' => 'Threads erstellen',
|
||||||
|
'like' => 'Beiträge liken',
|
||||||
|
'pin_thread' => 'Threads pinnen',
|
||||||
|
'close_thread' => 'Threads schließen',
|
||||||
|
'delete_post' => 'Posts löschen',
|
||||||
|
'delete_thread' => 'Threads löschen',
|
||||||
|
'manage_users' => 'Nutzer verwalten',
|
||||||
|
'manage_cats' => 'Kategorien verwalten',
|
||||||
|
'post_announcement' => 'Ankündigungen posten',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ist der aktuelle WP-User der Seiteninhaber (Superadmin)? */
|
||||||
|
public static function is_wp_superadmin() {
|
||||||
|
return current_user_can('administrator') || (is_multisite() && is_super_admin());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Superadmin-Status erzwingen: Forum-User des WP-Admins immer auf superadmin setzen */
|
||||||
|
public static function sync_superadmin() {
|
||||||
|
if ( ! is_user_logged_in() ) return;
|
||||||
|
if ( ! self::is_wp_superadmin() ) return;
|
||||||
|
|
||||||
|
$wp_user = wp_get_current_user();
|
||||||
|
$forum_user = WBF_DB::get_user_by('email', $wp_user->user_email);
|
||||||
|
if ( $forum_user && $forum_user->role !== self::SUPERADMIN ) {
|
||||||
|
WBF_DB::update_user($forum_user->id, ['role' => self::SUPERADMIN]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1537
includes/class-forum-shortcodes.php
Normal file
1537
includes/class-forum-shortcodes.php
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user