Update from Git Manager GUI
This commit is contained in:
@@ -9,8 +9,15 @@ class WBF_Ajax {
|
||||
'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_create_invite', 'wbf_delete_invite',
|
||||
'wbf_toggle_subscribe', 'wbf_restore_content', 'wbf_toggle_profile_visibility',
|
||||
'wbf_mod_action', 'wbf_report_post', 'wbf_edit_post', 'wbf_edit_thread', 'wbf_search', 'wbf_get_notifications', 'wbf_mark_notifications_read', 'wbf_move_thread', 'wbf_tag_suggest',
|
||||
'wbf_set_reaction', 'wbf_send_message', 'wbf_get_inbox', 'wbf_get_conversation', 'wbf_mark_messages_read', 'wbf_get_online_users', 'wbf_user_suggest', 'wbf_delete_message', 'wbf_get_new_messages',
|
||||
'wbf_delete_account',
|
||||
'wbf_vote_poll',
|
||||
'wbf_create_poll',
|
||||
'wbf_toggle_bookmark',
|
||||
'wbf_set_thread_prefix',
|
||||
];
|
||||
foreach ($actions as $action) {
|
||||
add_action('wp_ajax_nopriv_' . $action, [__CLASS__, str_replace('wbf_','handle_',$action)]);
|
||||
@@ -50,7 +57,37 @@ class WBF_Ajax {
|
||||
}
|
||||
|
||||
public static function handle_register() {
|
||||
// Register braucht keinen Nonce
|
||||
// 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'] ?? ''),
|
||||
@@ -59,6 +96,12 @@ class WBF_Ajax {
|
||||
);
|
||||
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);
|
||||
@@ -79,23 +122,40 @@ class WBF_Ajax {
|
||||
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 (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.']);
|
||||
|
||||
// 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' => $title,
|
||||
'title' => WBF_DB::apply_word_filter($title),
|
||||
'slug' => sanitize_title($title) . '-' . time(),
|
||||
'content' => $content,
|
||||
'content' => WBF_DB::apply_word_filter($content),
|
||||
'prefix_id' => $prefix_id,
|
||||
]);
|
||||
|
||||
// Tags speichern
|
||||
@@ -104,6 +164,23 @@ class WBF_Ajax {
|
||||
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!']);
|
||||
}
|
||||
|
||||
@@ -115,8 +192,15 @@ class WBF_Ajax {
|
||||
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.']);
|
||||
@@ -131,13 +215,24 @@ class WBF_Ajax {
|
||||
$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,
|
||||
]);
|
||||
}
|
||||
// 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) {
|
||||
@@ -198,13 +293,13 @@ class WBF_Ajax {
|
||||
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);
|
||||
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::delete_post($object_id);
|
||||
WBF_DB::soft_delete_post($object_id);
|
||||
wp_send_json_success(['action'=>'post_deleted']);
|
||||
break;
|
||||
|
||||
@@ -278,6 +373,34 @@ class WBF_Ajax {
|
||||
}
|
||||
|
||||
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!']);
|
||||
}
|
||||
|
||||
@@ -394,6 +517,17 @@ class WBF_Ajax {
|
||||
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')],
|
||||
@@ -828,6 +962,251 @@ class WBF_Ajax {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ── 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]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WBF_Ajax::init();
|
||||
add_action( 'init', [ 'WBF_Ajax', 'init' ] );
|
||||
@@ -44,7 +44,27 @@ class WBF_Auth {
|
||||
return array( 'success' => false, 'message' => 'Falsches Passwort.' );
|
||||
}
|
||||
if ( WBF_Roles::level($user->role) < 0 ) {
|
||||
// Zeitlich begrenzte Sperre prüfen — automatisch aufheben wenn abgelaufen
|
||||
if ( ! empty($user->ban_until) && strtotime($user->ban_until) <= time() ) {
|
||||
$restore = ! empty($user->pre_ban_role) ? $user->pre_ban_role : 'member';
|
||||
WBF_DB::update_user( $user->id, [
|
||||
'role' => $restore,
|
||||
'ban_reason' => '',
|
||||
'ban_until' => null,
|
||||
'pre_ban_role' => '',
|
||||
]);
|
||||
// Frisch laden und einloggen
|
||||
$user = WBF_DB::get_user( $user->id );
|
||||
$_SESSION[ self::SESSION_KEY ] = $user->id;
|
||||
WBF_DB::touch_last_active( $user->id );
|
||||
return array( 'success' => true, 'user' => $user );
|
||||
}
|
||||
$reason = !empty($user->ban_reason) ? $user->ban_reason : 'Dein Konto wurde gesperrt.';
|
||||
// Zeitstempel anhängen wenn temporäre Sperre
|
||||
if ( ! empty($user->ban_until) ) {
|
||||
$until_fmt = date_i18n( 'd.m.Y \u\m H:i \U\h\r', strtotime($user->ban_until) );
|
||||
$reason .= ' (Gesperrt bis: ' . $until_fmt . ')';
|
||||
}
|
||||
return array( 'success' => false, 'banned' => true, 'message' => $reason );
|
||||
}
|
||||
$_SESSION[ self::SESSION_KEY ] = $user->id;
|
||||
|
||||
@@ -96,10 +96,10 @@ class WBF_DB {
|
||||
$sql_tags = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}forum_tags (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
name VARCHAR(60) NOT NULL,
|
||||
slug VARCHAR(60) NOT NULL UNIQUE,
|
||||
slug VARCHAR(60) NOT NULL,
|
||||
use_count INT DEFAULT 0,
|
||||
PRIMARY KEY (id),
|
||||
KEY slug (slug)
|
||||
UNIQUE KEY slug (slug)
|
||||
) $charset;";
|
||||
|
||||
$sql_thread_tags = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}forum_thread_tags (
|
||||
@@ -163,8 +163,28 @@ class WBF_DB {
|
||||
self::maybe_add_column("{$wpdb->prefix}forum_users", 'ban_reason', "ALTER TABLE {$wpdb->prefix}forum_users ADD COLUMN ban_reason TEXT DEFAULT '' AFTER role");
|
||||
self::maybe_add_column("{$wpdb->prefix}forum_categories", 'parent_id', "ALTER TABLE {$wpdb->prefix}forum_categories ADD COLUMN parent_id BIGINT UNSIGNED DEFAULT 0 AFTER id");
|
||||
self::maybe_add_column("{$wpdb->prefix}forum_categories", 'min_role', "ALTER TABLE {$wpdb->prefix}forum_categories ADD COLUMN min_role VARCHAR(20) DEFAULT 'member' AFTER post_count");
|
||||
self::maybe_add_column("{$wpdb->prefix}forum_categories", 'guest_visible', "ALTER TABLE {$wpdb->prefix}forum_categories ADD COLUMN guest_visible TINYINT(1) DEFAULT 1 AFTER min_role");
|
||||
self::maybe_add_column("{$wpdb->prefix}forum_users", 'reset_token', "ALTER TABLE {$wpdb->prefix}forum_users ADD COLUMN reset_token VARCHAR(64) DEFAULT NULL");
|
||||
self::maybe_add_column("{$wpdb->prefix}forum_users", 'reset_token_expires', "ALTER TABLE {$wpdb->prefix}forum_users ADD COLUMN reset_token_expires DATETIME DEFAULT NULL");
|
||||
// Soft-Delete
|
||||
self::maybe_add_column("{$wpdb->prefix}forum_threads", 'deleted_at', "ALTER TABLE {$wpdb->prefix}forum_threads ADD COLUMN deleted_at DATETIME DEFAULT NULL");
|
||||
self::maybe_add_column("{$wpdb->prefix}forum_posts", 'deleted_at', "ALTER TABLE {$wpdb->prefix}forum_posts ADD COLUMN deleted_at DATETIME DEFAULT NULL");
|
||||
// Profil-Sichtbarkeit
|
||||
self::maybe_add_column("{$wpdb->prefix}forum_users", 'profile_public', "ALTER TABLE {$wpdb->prefix}forum_users ADD COLUMN profile_public TINYINT(1) DEFAULT 1");
|
||||
// Zeitlich begrenzte Sperren
|
||||
self::maybe_add_column("{$wpdb->prefix}forum_users", 'ban_until', "ALTER TABLE {$wpdb->prefix}forum_users ADD COLUMN ban_until DATETIME DEFAULT NULL");
|
||||
self::maybe_add_column("{$wpdb->prefix}forum_users", 'pre_ban_role', "ALTER TABLE {$wpdb->prefix}forum_users ADD COLUMN pre_ban_role VARCHAR(20) DEFAULT 'member'");
|
||||
// Thread-Abonnements
|
||||
$sql_subscriptions = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}forum_subscriptions (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
thread_id BIGINT UNSIGNED NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY user_thread (user_id, thread_id),
|
||||
KEY thread_id (thread_id)
|
||||
) $charset;";
|
||||
dbDelta( $sql_subscriptions );
|
||||
$sql_notifications = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}forum_notifications (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
@@ -182,7 +202,87 @@ class WBF_DB {
|
||||
dbDelta( $sql_reports );
|
||||
dbDelta( $sql_notifications );
|
||||
|
||||
// Default categories
|
||||
// Einladungs-Tabelle
|
||||
$sql_invites = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}forum_invites (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code VARCHAR(64) NOT NULL UNIQUE,
|
||||
created_by BIGINT UNSIGNED NOT NULL,
|
||||
used_by BIGINT UNSIGNED DEFAULT NULL,
|
||||
max_uses SMALLINT UNSIGNED DEFAULT 1,
|
||||
use_count SMALLINT UNSIGNED DEFAULT 0,
|
||||
note VARCHAR(255) DEFAULT '',
|
||||
expires_at DATETIME DEFAULT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY code (code),
|
||||
KEY created_by (created_by)
|
||||
) $charset;";
|
||||
dbDelta( $sql_invites );
|
||||
|
||||
// Benutzerdefinierte Profilfelder — Meta-Tabelle
|
||||
$sql_user_meta = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}forum_user_meta (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
meta_key VARCHAR(60) NOT NULL,
|
||||
meta_value TEXT DEFAULT '',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY user_key (user_id, meta_key),
|
||||
KEY user_id (user_id)
|
||||
) $charset;";
|
||||
dbDelta( $sql_user_meta );
|
||||
|
||||
// Umfragen (Polls)
|
||||
$sql_polls = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}forum_polls (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
thread_id BIGINT UNSIGNED NOT NULL,
|
||||
question VARCHAR(255) NOT NULL DEFAULT '',
|
||||
options TEXT NOT NULL DEFAULT '',
|
||||
multi TINYINT(1) DEFAULT 0,
|
||||
ends_at DATETIME DEFAULT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY thread_id (thread_id)
|
||||
) $charset;";
|
||||
dbDelta( $sql_polls );
|
||||
|
||||
$sql_poll_votes = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}forum_poll_votes (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
poll_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
option_idx TINYINT UNSIGNED NOT NULL,
|
||||
voted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY poll_user_option (poll_id, user_id, option_idx),
|
||||
KEY poll_id (poll_id)
|
||||
) $charset;";
|
||||
dbDelta( $sql_poll_votes );
|
||||
|
||||
// ── Thread-Präfixe ────────────────────────────────────────────────────
|
||||
$sql_prefixes = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}forum_prefixes (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
label VARCHAR(60) NOT NULL,
|
||||
color VARCHAR(30) DEFAULT '#ffffff',
|
||||
bg_color VARCHAR(30) DEFAULT '#475569',
|
||||
sort_order INT DEFAULT 0,
|
||||
PRIMARY KEY (id)
|
||||
) $charset;";
|
||||
dbDelta( $sql_prefixes );
|
||||
|
||||
// ── Lesezeichen ───────────────────────────────────────────────────────
|
||||
$sql_bookmarks = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}forum_bookmarks (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
thread_id BIGINT UNSIGNED NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY user_thread (user_id, thread_id)
|
||||
) $charset;";
|
||||
dbDelta( $sql_bookmarks );
|
||||
|
||||
// ── prefix_id zu threads ──────────────────────────────────────────────
|
||||
self::maybe_add_column( "{$wpdb->prefix}forum_threads", 'prefix_id',
|
||||
"ALTER TABLE {$wpdb->prefix}forum_threads ADD COLUMN prefix_id BIGINT UNSIGNED DEFAULT NULL" );
|
||||
|
||||
$count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_categories");
|
||||
if ( (int)$count === 0 ) {
|
||||
$wpdb->insert("{$wpdb->prefix}forum_categories", ['parent_id'=>0,'name'=>'Allgemein', 'slug'=>'allgemein', 'description'=>'Allgemeine Diskussionen','icon'=>'fas fa-home', 'sort_order'=>1]);
|
||||
@@ -311,10 +411,12 @@ class WBF_DB {
|
||||
$offset = ($page - 1) * $per_page;
|
||||
$status_sql = $include_archived ? '' : "AND t.status != 'archived'";
|
||||
return $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT t.*, u.display_name, u.avatar_url, u.username, u.role as author_role
|
||||
"SELECT t.*, u.display_name, u.avatar_url, u.username, u.role as author_role,
|
||||
p.label as prefix_label, p.color as prefix_color, p.bg_color as prefix_bg
|
||||
FROM {$wpdb->prefix}forum_threads t
|
||||
JOIN {$wpdb->prefix}forum_users u ON u.id = t.user_id
|
||||
WHERE t.category_id = %d $status_sql
|
||||
LEFT JOIN {$wpdb->prefix}forum_prefixes p ON p.id = t.prefix_id
|
||||
WHERE t.category_id = %d AND t.deleted_at IS NULL $status_sql
|
||||
ORDER BY t.pinned DESC, t.last_reply_at DESC
|
||||
LIMIT %d OFFSET %d",
|
||||
$category_id, $per_page, $offset
|
||||
@@ -351,7 +453,7 @@ class WBF_DB {
|
||||
public static function count_threads( $category_id ) {
|
||||
global $wpdb;
|
||||
return (int)$wpdb->get_var($wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$wpdb->prefix}forum_threads WHERE category_id=%d AND status != 'archived'",
|
||||
"SELECT COUNT(*) FROM {$wpdb->prefix}forum_threads WHERE category_id=%d AND status != 'archived' AND deleted_at IS NULL",
|
||||
$category_id
|
||||
));
|
||||
}
|
||||
@@ -393,9 +495,11 @@ class WBF_DB {
|
||||
global $wpdb;
|
||||
return $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT t.*, u.display_name, u.avatar_url, u.username, u.signature,
|
||||
u.post_count as author_posts, u.registered as author_registered, u.role as author_role
|
||||
u.post_count as author_posts, u.registered as author_registered, u.role as author_role,
|
||||
p.label as prefix_label, p.color as prefix_color, p.bg_color as prefix_bg
|
||||
FROM {$wpdb->prefix}forum_threads t
|
||||
JOIN {$wpdb->prefix}forum_users u ON u.id = t.user_id
|
||||
LEFT JOIN {$wpdb->prefix}forum_prefixes p ON p.id = t.prefix_id
|
||||
WHERE t.id = %d", $id
|
||||
));
|
||||
}
|
||||
@@ -447,7 +551,7 @@ class WBF_DB {
|
||||
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.thread_id = %d
|
||||
WHERE p.thread_id = %d AND p.deleted_at IS NULL
|
||||
ORDER BY p.created_at ASC
|
||||
LIMIT %d OFFSET %d",
|
||||
$thread_id, $per_page, $offset
|
||||
@@ -885,9 +989,16 @@ class WBF_DB {
|
||||
|
||||
// ── Reaktionen ────────────────────────────────────────────────────────────
|
||||
|
||||
/** Erlaubte Reaktionen aus den Einstellungen holen */
|
||||
public static function get_allowed_reactions() {
|
||||
$saved = get_option('wbf_reactions', null);
|
||||
if ( $saved !== null && is_array($saved) && count($saved) > 0 ) return $saved;
|
||||
return ['👍','❤️','😂','😮','😢','😡']; // Defaults
|
||||
}
|
||||
|
||||
public static function set_reaction( $user_id, $object_id, $object_type, $reaction ) {
|
||||
global $wpdb;
|
||||
$allowed = ['👍','❤️','😂','😮','😢','😡'];
|
||||
$allowed = self::get_allowed_reactions();
|
||||
if ( ! in_array($reaction, $allowed, true) ) return false;
|
||||
|
||||
$existing = $wpdb->get_row( $wpdb->prepare(
|
||||
@@ -1134,4 +1245,491 @@ class WBF_DB {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ── Einladungen ───────────────────────────────────────────────────────────
|
||||
|
||||
public static function create_invite( $created_by, $max_uses = 1, $note = '', $expires_at = null ) {
|
||||
global $wpdb;
|
||||
$code = strtoupper( substr( bin2hex( random_bytes(6) ), 0, 10 ) );
|
||||
$wpdb->insert( "{$wpdb->prefix}forum_invites", [
|
||||
'code' => $code,
|
||||
'created_by' => (int) $created_by,
|
||||
'max_uses' => (int) $max_uses,
|
||||
'note' => sanitize_text_field( $note ),
|
||||
'expires_at' => $expires_at,
|
||||
] );
|
||||
return $code;
|
||||
}
|
||||
|
||||
public static function get_invite( $code ) {
|
||||
global $wpdb;
|
||||
return $wpdb->get_row( $wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->prefix}forum_invites WHERE code = %s",
|
||||
strtoupper( trim($code) )
|
||||
) );
|
||||
}
|
||||
|
||||
public static function verify_invite( $code ) {
|
||||
$inv = self::get_invite( $code );
|
||||
if ( ! $inv ) return false;
|
||||
if ( $inv->use_count >= $inv->max_uses ) return false;
|
||||
if ( $inv->expires_at && strtotime($inv->expires_at) < time() ) return false;
|
||||
return $inv;
|
||||
}
|
||||
|
||||
public static function use_invite( $code, $user_id ) {
|
||||
global $wpdb;
|
||||
$inv = self::verify_invite( $code );
|
||||
if ( ! $inv ) return false;
|
||||
$wpdb->query( $wpdb->prepare(
|
||||
"UPDATE {$wpdb->prefix}forum_invites
|
||||
SET use_count = use_count + 1, used_by = %d
|
||||
WHERE code = %s",
|
||||
(int) $user_id, strtoupper($code)
|
||||
) );
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function get_all_invites( $limit = 100 ) {
|
||||
global $wpdb;
|
||||
return $wpdb->get_results( $wpdb->prepare(
|
||||
"SELECT i.*, u.display_name AS creator_name,
|
||||
uu.display_name AS used_name
|
||||
FROM {$wpdb->prefix}forum_invites i
|
||||
LEFT JOIN {$wpdb->prefix}forum_users u ON u.id = i.created_by
|
||||
LEFT JOIN {$wpdb->prefix}forum_users uu ON uu.id = i.used_by
|
||||
ORDER BY i.created_at DESC
|
||||
LIMIT %d",
|
||||
$limit
|
||||
) );
|
||||
}
|
||||
|
||||
public static function delete_invite( $id ) {
|
||||
global $wpdb;
|
||||
$wpdb->delete( "{$wpdb->prefix}forum_invites", ['id' => (int)$id] );
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ── Thread-Abonnements ────────────────────────────────────────────────────
|
||||
|
||||
public static function subscribe( $user_id, $thread_id ) {
|
||||
global $wpdb;
|
||||
$wpdb->replace("{$wpdb->prefix}forum_subscriptions", [
|
||||
'user_id' => (int)$user_id,
|
||||
'thread_id' => (int)$thread_id,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function unsubscribe( $user_id, $thread_id ) {
|
||||
global $wpdb;
|
||||
$wpdb->delete("{$wpdb->prefix}forum_subscriptions", [
|
||||
'user_id' => (int)$user_id,
|
||||
'thread_id' => (int)$thread_id,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function is_subscribed( $user_id, $thread_id ) {
|
||||
global $wpdb;
|
||||
return (bool)$wpdb->get_var($wpdb->prepare(
|
||||
"SELECT id FROM {$wpdb->prefix}forum_subscriptions WHERE user_id=%d AND thread_id=%d",
|
||||
(int)$user_id, (int)$thread_id
|
||||
));
|
||||
}
|
||||
|
||||
public static function get_thread_subscribers( $thread_id ) {
|
||||
global $wpdb;
|
||||
return $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT u.id, u.email, u.display_name
|
||||
FROM {$wpdb->prefix}forum_subscriptions s
|
||||
JOIN {$wpdb->prefix}forum_users u ON u.id = s.user_id
|
||||
WHERE s.thread_id = %d",
|
||||
(int)$thread_id
|
||||
));
|
||||
}
|
||||
|
||||
// ── Soft-Delete ───────────────────────────────────────────────────────────
|
||||
|
||||
public static function soft_delete_thread( $thread_id ) {
|
||||
global $wpdb;
|
||||
$wpdb->update(
|
||||
"{$wpdb->prefix}forum_threads",
|
||||
['deleted_at' => current_time('mysql')],
|
||||
['id' => (int)$thread_id]
|
||||
);
|
||||
}
|
||||
|
||||
public static function soft_delete_post( $post_id ) {
|
||||
global $wpdb;
|
||||
$wpdb->update(
|
||||
"{$wpdb->prefix}forum_posts",
|
||||
['deleted_at' => current_time('mysql')],
|
||||
['id' => (int)$post_id]
|
||||
);
|
||||
}
|
||||
|
||||
public static function restore_thread( $thread_id ) {
|
||||
global $wpdb;
|
||||
$wpdb->update("{$wpdb->prefix}forum_threads", ['deleted_at'=>null], ['id'=>(int)$thread_id]);
|
||||
}
|
||||
|
||||
public static function restore_post( $post_id ) {
|
||||
global $wpdb;
|
||||
$wpdb->update("{$wpdb->prefix}forum_posts", ['deleted_at'=>null], ['id'=>(int)$post_id]);
|
||||
}
|
||||
|
||||
public static function get_deleted_content( $limit = 50 ) {
|
||||
global $wpdb;
|
||||
$threads = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT 'thread' as type, t.id, t.title as content_preview, t.deleted_at,
|
||||
u.display_name, c.name as cat_name
|
||||
FROM {$wpdb->prefix}forum_threads t
|
||||
JOIN {$wpdb->prefix}forum_users u ON u.id=t.user_id
|
||||
LEFT JOIN {$wpdb->prefix}forum_categories c ON c.id=t.category_id
|
||||
WHERE t.deleted_at IS NOT NULL
|
||||
ORDER BY t.deleted_at DESC LIMIT %d", $limit
|
||||
));
|
||||
$posts = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT 'post' as type, p.id, LEFT(p.content,80) as content_preview, p.deleted_at,
|
||||
u.display_name, t.title as cat_name
|
||||
FROM {$wpdb->prefix}forum_posts p
|
||||
JOIN {$wpdb->prefix}forum_users u ON u.id=p.user_id
|
||||
LEFT JOIN {$wpdb->prefix}forum_threads t ON t.id=p.thread_id
|
||||
WHERE p.deleted_at IS NOT NULL
|
||||
ORDER BY p.deleted_at DESC LIMIT %d", $limit
|
||||
));
|
||||
return array_merge($threads, $posts);
|
||||
}
|
||||
|
||||
// ── Nutzungs-Statistiken ──────────────────────────────────────────────────
|
||||
|
||||
public static function get_activity_stats( $days = 30 ) {
|
||||
global $wpdb;
|
||||
$since = date('Y-m-d', strtotime("-{$days} days"));
|
||||
|
||||
$posts_per_day = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT DATE(created_at) as day, COUNT(*) as count
|
||||
FROM {$wpdb->prefix}forum_posts
|
||||
WHERE created_at >= %s AND deleted_at IS NULL
|
||||
GROUP BY DATE(created_at) ORDER BY day ASC",
|
||||
$since
|
||||
));
|
||||
$threads_per_day = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT DATE(created_at) as day, COUNT(*) as count
|
||||
FROM {$wpdb->prefix}forum_threads
|
||||
WHERE created_at >= %s AND deleted_at IS NULL
|
||||
GROUP BY DATE(created_at) ORDER BY day ASC",
|
||||
$since
|
||||
));
|
||||
$registrations = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT DATE(registered) as day, COUNT(*) as count
|
||||
FROM {$wpdb->prefix}forum_users
|
||||
WHERE registered >= %s
|
||||
GROUP BY DATE(registered) ORDER BY day ASC",
|
||||
$since
|
||||
));
|
||||
$top_posters = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT u.display_name, u.role, COUNT(p.id) as post_count
|
||||
FROM {$wpdb->prefix}forum_posts p
|
||||
JOIN {$wpdb->prefix}forum_users u ON u.id=p.user_id
|
||||
WHERE p.created_at >= %s AND p.deleted_at IS NULL
|
||||
GROUP BY u.id ORDER BY post_count DESC LIMIT 10",
|
||||
$since
|
||||
));
|
||||
$active_hours = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT HOUR(created_at) as hour, COUNT(*) as count
|
||||
FROM {$wpdb->prefix}forum_posts
|
||||
WHERE created_at >= %s AND deleted_at IS NULL
|
||||
GROUP BY HOUR(created_at) ORDER BY hour ASC",
|
||||
$since
|
||||
));
|
||||
|
||||
return compact('posts_per_day','threads_per_day','registrations','top_posters','active_hours');
|
||||
}
|
||||
|
||||
// ── Benutzerdefinierte Profilfelder ───────────────────────────────────────
|
||||
|
||||
public static function get_profile_field_defs() {
|
||||
$fields = get_option( 'wbf_profile_fields', [] );
|
||||
return is_array( $fields ) ? $fields : [];
|
||||
}
|
||||
|
||||
public static function save_profile_field_defs( $fields ) {
|
||||
update_option( 'wbf_profile_fields', $fields );
|
||||
}
|
||||
|
||||
public static function get_user_meta( $user_id ) {
|
||||
global $wpdb;
|
||||
$rows = $wpdb->get_results( $wpdb->prepare(
|
||||
"SELECT meta_key, meta_value FROM {$wpdb->prefix}forum_user_meta WHERE user_id = %d",
|
||||
(int) $user_id
|
||||
) );
|
||||
$out = [];
|
||||
foreach ( $rows as $r ) $out[ $r->meta_key ] = $r->meta_value;
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function set_user_meta( $user_id, $key, $value ) {
|
||||
global $wpdb;
|
||||
$wpdb->replace(
|
||||
"{$wpdb->prefix}forum_user_meta",
|
||||
[ 'user_id' => (int) $user_id, 'meta_key' => $key, 'meta_value' => $value ],
|
||||
[ '%d', '%s', '%s' ]
|
||||
);
|
||||
}
|
||||
|
||||
public static function delete_user_meta_all( $user_id ) {
|
||||
global $wpdb;
|
||||
$wpdb->delete( "{$wpdb->prefix}forum_user_meta", [ 'user_id' => (int) $user_id ] );
|
||||
}
|
||||
|
||||
// ── Zeitlich begrenzte Sperren ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Setzt eine zeitlich begrenzte Sperre für einen User.
|
||||
* Speichert die vorherige Rolle in pre_ban_role.
|
||||
*
|
||||
* @param int $user_id
|
||||
* @param string $until MySQL DATETIME z.B. '2025-12-31 23:59:00'
|
||||
* @param string $reason Sperrgrund
|
||||
*/
|
||||
public static function temp_ban( $user_id, $until, $reason = '' ) {
|
||||
global $wpdb;
|
||||
$user = self::get_user( (int) $user_id );
|
||||
if ( ! $user || $user->role === 'superadmin' ) return false;
|
||||
|
||||
$wpdb->update(
|
||||
"{$wpdb->prefix}forum_users",
|
||||
[
|
||||
'pre_ban_role' => $user->role !== 'banned' ? $user->role : ( $user->pre_ban_role ?: 'member' ),
|
||||
'role' => 'banned',
|
||||
'ban_reason' => $reason,
|
||||
'ban_until' => $until,
|
||||
],
|
||||
[ 'id' => (int) $user_id ],
|
||||
[ '%s', '%s', '%s', '%s' ],
|
||||
[ '%d' ]
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hebt abgelaufene Sperren auf — läuft per WP-Cron täglich.
|
||||
* Gibt Anzahl entsperrter User zurück.
|
||||
*/
|
||||
public static function check_expired_bans() {
|
||||
global $wpdb;
|
||||
$expired = $wpdb->get_results(
|
||||
"SELECT id, pre_ban_role FROM {$wpdb->prefix}forum_users
|
||||
WHERE role = 'banned'
|
||||
AND ban_until IS NOT NULL
|
||||
AND ban_until <= NOW()"
|
||||
);
|
||||
$count = 0;
|
||||
foreach ( $expired as $u ) {
|
||||
$restore = ! empty( $u->pre_ban_role ) ? $u->pre_ban_role : 'member';
|
||||
$wpdb->update(
|
||||
"{$wpdb->prefix}forum_users",
|
||||
[ 'role' => $restore, 'ban_reason' => '', 'ban_until' => null, 'pre_ban_role' => '' ],
|
||||
[ 'id' => (int) $u->id ],
|
||||
[ '%s', '%s', null, '%s' ],
|
||||
[ '%d' ]
|
||||
);
|
||||
$count++;
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
// ── Umfragen (Polls) ──────────────────────────────────────────────────────
|
||||
|
||||
public static function create_poll( $thread_id, $question, $options, $multi = false, $ends_at = null ) {
|
||||
global $wpdb;
|
||||
$wpdb->insert( "{$wpdb->prefix}forum_polls", [
|
||||
'thread_id' => (int) $thread_id,
|
||||
'question' => sanitize_text_field( $question ),
|
||||
'options' => wp_json_encode( array_values( $options ) ),
|
||||
'multi' => $multi ? 1 : 0,
|
||||
'ends_at' => $ends_at,
|
||||
]);
|
||||
return $wpdb->insert_id;
|
||||
}
|
||||
|
||||
public static function get_poll( $thread_id ) {
|
||||
global $wpdb;
|
||||
$row = $wpdb->get_row( $wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->prefix}forum_polls WHERE thread_id = %d", (int) $thread_id
|
||||
) );
|
||||
if ( ! $row ) return null;
|
||||
$row->options = json_decode( $row->options, true ) ?: [];
|
||||
return $row;
|
||||
}
|
||||
|
||||
public static function get_poll_results( $poll_id ) {
|
||||
global $wpdb;
|
||||
$rows = $wpdb->get_results( $wpdb->prepare(
|
||||
"SELECT option_idx, COUNT(*) AS votes FROM {$wpdb->prefix}forum_poll_votes
|
||||
WHERE poll_id = %d GROUP BY option_idx", (int) $poll_id
|
||||
) );
|
||||
$out = [];
|
||||
foreach ( $rows as $r ) $out[(int)$r->option_idx] = (int)$r->votes;
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function get_user_votes( $poll_id, $user_id ) {
|
||||
global $wpdb;
|
||||
return array_map( fn($r) => (int)$r->option_idx,
|
||||
$wpdb->get_results( $wpdb->prepare(
|
||||
"SELECT option_idx FROM {$wpdb->prefix}forum_poll_votes WHERE poll_id=%d AND user_id=%d",
|
||||
(int) $poll_id, (int) $user_id
|
||||
) )
|
||||
);
|
||||
}
|
||||
|
||||
public static function vote_poll( $poll_id, $user_id, $option_idxs ) {
|
||||
global $wpdb;
|
||||
$poll = $wpdb->get_row( $wpdb->prepare(
|
||||
"SELECT ends_at, multi FROM {$wpdb->prefix}forum_polls WHERE id=%d", (int) $poll_id
|
||||
) );
|
||||
if ( ! $poll ) return false;
|
||||
if ( $poll->ends_at && strtotime( $poll->ends_at ) < time() ) return false;
|
||||
if ( ! empty( self::get_user_votes( $poll_id, $user_id ) ) ) return false;
|
||||
if ( ! $poll->multi ) $option_idxs = [ (int)$option_idxs[0] ];
|
||||
foreach ( $option_idxs as $idx ) {
|
||||
$wpdb->insert( "{$wpdb->prefix}forum_poll_votes", [
|
||||
'poll_id' => (int)$poll_id, 'user_id' => (int)$user_id, 'option_idx' => (int)$idx,
|
||||
]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function delete_poll( $thread_id ) {
|
||||
global $wpdb;
|
||||
$poll = self::get_poll( $thread_id );
|
||||
if ( ! $poll ) return;
|
||||
$wpdb->delete( "{$wpdb->prefix}forum_poll_votes", [ 'poll_id' => $poll->id ] );
|
||||
$wpdb->delete( "{$wpdb->prefix}forum_polls", [ 'id' => $poll->id ] );
|
||||
}
|
||||
|
||||
// ── Thread-Präfixe ────────────────────────────────────────────────────────
|
||||
|
||||
public static function get_prefixes() {
|
||||
global $wpdb;
|
||||
return $wpdb->get_results(
|
||||
"SELECT * FROM {$wpdb->prefix}forum_prefixes ORDER BY sort_order ASC, id ASC"
|
||||
);
|
||||
}
|
||||
|
||||
public static function get_prefix( $id ) {
|
||||
global $wpdb;
|
||||
return $wpdb->get_row( $wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->prefix}forum_prefixes WHERE id=%d", (int)$id
|
||||
));
|
||||
}
|
||||
|
||||
public static function create_prefix( $data ) {
|
||||
global $wpdb;
|
||||
$wpdb->insert( "{$wpdb->prefix}forum_prefixes", $data );
|
||||
return $wpdb->insert_id;
|
||||
}
|
||||
|
||||
public static function update_prefix( $id, $data ) {
|
||||
global $wpdb;
|
||||
$wpdb->update( "{$wpdb->prefix}forum_prefixes", $data, ['id' => (int)$id] );
|
||||
}
|
||||
|
||||
public static function delete_prefix( $id ) {
|
||||
global $wpdb;
|
||||
// Präfix bei betroffenen Threads entfernen
|
||||
$wpdb->update( "{$wpdb->prefix}forum_threads", ['prefix_id' => null], ['prefix_id' => (int)$id] );
|
||||
$wpdb->delete( "{$wpdb->prefix}forum_prefixes", ['id' => (int)$id] );
|
||||
}
|
||||
|
||||
// ── Lesezeichen ───────────────────────────────────────────────────────────
|
||||
|
||||
public static function is_bookmarked( $user_id, $thread_id ) {
|
||||
global $wpdb;
|
||||
return (bool)$wpdb->get_var( $wpdb->prepare(
|
||||
"SELECT id FROM {$wpdb->prefix}forum_bookmarks WHERE user_id=%d AND thread_id=%d",
|
||||
(int)$user_id, (int)$thread_id
|
||||
));
|
||||
}
|
||||
|
||||
public static function toggle_bookmark( $user_id, $thread_id ) {
|
||||
global $wpdb;
|
||||
if ( self::is_bookmarked( $user_id, $thread_id ) ) {
|
||||
$wpdb->delete( "{$wpdb->prefix}forum_bookmarks", [
|
||||
'user_id' => (int)$user_id,
|
||||
'thread_id' => (int)$thread_id,
|
||||
]);
|
||||
return false; // removed
|
||||
}
|
||||
$wpdb->insert( "{$wpdb->prefix}forum_bookmarks", [
|
||||
'user_id' => (int)$user_id,
|
||||
'thread_id' => (int)$thread_id,
|
||||
]);
|
||||
return true; // added
|
||||
}
|
||||
|
||||
public static function get_user_bookmarks( $user_id, $limit = 50 ) {
|
||||
global $wpdb;
|
||||
return $wpdb->get_results( $wpdb->prepare(
|
||||
"SELECT t.id, t.title, t.reply_count, t.views, t.created_at, t.last_reply_at,
|
||||
t.prefix_id, t.status, t.pinned,
|
||||
u.display_name, u.avatar_url, u.role as author_role,
|
||||
c.name as cat_name, c.slug as cat_slug,
|
||||
b.created_at as bookmarked_at
|
||||
FROM {$wpdb->prefix}forum_bookmarks b
|
||||
JOIN {$wpdb->prefix}forum_threads t ON t.id = b.thread_id
|
||||
JOIN {$wpdb->prefix}forum_users u ON u.id = t.user_id
|
||||
JOIN {$wpdb->prefix}forum_categories c ON c.id = t.category_id
|
||||
WHERE b.user_id = %d AND t.deleted_at IS NULL
|
||||
ORDER BY b.created_at DESC
|
||||
LIMIT %d",
|
||||
(int)$user_id, $limit
|
||||
));
|
||||
}
|
||||
|
||||
// ── Wortfilter ────────────────────────────────────────────────────────────
|
||||
|
||||
public static function get_word_filter() {
|
||||
$raw = get_option( 'wbf_word_filter', '' );
|
||||
if ( empty( $raw ) ) return [];
|
||||
return array_values( array_filter( array_map( 'trim', explode( "\n", $raw ) ) ) );
|
||||
}
|
||||
|
||||
public static function apply_word_filter( $text ) {
|
||||
$words = self::get_word_filter();
|
||||
if ( empty( $words ) ) return $text;
|
||||
foreach ( $words as $word ) {
|
||||
if ( empty($word) ) continue;
|
||||
$replacement = str_repeat( '*', mb_strlen($word) );
|
||||
$text = preg_replace( '/\b' . preg_quote($word, '/') . '\b/iu', $replacement, $text );
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
// ── Flood Control ─────────────────────────────────────────────────────────
|
||||
|
||||
public static function check_flood( $user_id ) {
|
||||
$interval = (int)( wbf_get_settings()['flood_interval'] ?? 0 );
|
||||
if ( $interval <= 0 ) return true; // deaktiviert
|
||||
$key = 'wbf_flood_' . (int)$user_id;
|
||||
$last = get_transient( $key );
|
||||
if ( $last !== false ) {
|
||||
return false; // noch gesperrt
|
||||
}
|
||||
set_transient( $key, time(), $interval );
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function flood_remaining( $user_id ) {
|
||||
$interval = (int)( wbf_get_settings()['flood_interval'] ?? 0 );
|
||||
if ( $interval <= 0 ) return 0;
|
||||
$key = 'wbf_flood_' . (int)$user_id;
|
||||
$last = get_transient( $key );
|
||||
if ( $last === false ) return 0;
|
||||
// Transients speichern keine genaue Restzeit — wir schätzen über $interval
|
||||
return $interval;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,6 +30,14 @@ class WBF_Shortcodes {
|
||||
return WBF_Roles::badge( $role );
|
||||
}
|
||||
|
||||
public static function render_prefix( $thread ) {
|
||||
if ( empty($thread->prefix_label) ) return '';
|
||||
$label = esc_html($thread->prefix_label);
|
||||
$color = esc_attr($thread->prefix_color ?? '#fff');
|
||||
$bg = esc_attr($thread->prefix_bg ?? '#475569');
|
||||
return "<span class=\"wbf-prefix-badge\" style=\"color:{$color};background:{$bg}\">{$label}</span>";
|
||||
}
|
||||
|
||||
public static function render_tags( $tags, $small = false ) {
|
||||
if ( empty($tags) ) return '';
|
||||
$cls = $small ? 'wbf-tag wbf-tag--sm' : 'wbf-tag';
|
||||
@@ -54,7 +62,7 @@ class WBF_Shortcodes {
|
||||
}
|
||||
|
||||
private static function reaction_bar( $object_id, $object_type, $current_user ) {
|
||||
$emojis = ['👍','❤️','😂','😮','😢','😡'];
|
||||
$emojis = WBF_DB::get_allowed_reactions();
|
||||
$user_id = $current_user ? (int)$current_user->id : 0;
|
||||
$data = WBF_DB::get_reactions($object_id, $object_type, $user_id);
|
||||
$counts = $data['counts'];
|
||||
@@ -147,8 +155,39 @@ class WBF_Shortcodes {
|
||||
wp_redirect( wbf_get_forum_url() );
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Wartungsmodus — zentraler Check vor allem anderen ────────────────
|
||||
$wbf_current_user = WBF_Auth::get_current_user();
|
||||
$wbf_maint = wbf_get_settings()['maintenance_mode'] ?? '0';
|
||||
if ( $wbf_maint === '1' ) {
|
||||
$is_staff = $wbf_current_user && WBF_Roles::level($wbf_current_user->role) >= 50;
|
||||
if ( ! $is_staff ) {
|
||||
return self::view_maintenance();
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET['forum_members'])) return self::view_members();
|
||||
// Einladungscode aus URL vorausfüllen
|
||||
if (isset($_GET['wbf_invite'])) {
|
||||
$inv_code = strtoupper(sanitize_text_field($_GET['wbf_invite']));
|
||||
if (!WBF_DB::verify_invite($inv_code)) {
|
||||
// Ungültiger Code — zeige Meldung
|
||||
ob_start(); ?>
|
||||
<div class="wbf-wrap"><?php self::render_topbar(null); ?>
|
||||
<div class="wbf-container wbf-mt">
|
||||
<div class="wbf-notice wbf-notice--warning">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
Dieser Einladungslink ist ungültig oder bereits abgelaufen.
|
||||
</div>
|
||||
<div style="margin-top:1rem">
|
||||
<a href="<?php echo esc_url(wbf_get_forum_url()); ?>" class="wbf-btn wbf-btn--sm">← Zurück zum Forum</a>
|
||||
</div>
|
||||
</div></div>
|
||||
<?php return ob_get_clean();
|
||||
}
|
||||
}
|
||||
if (isset($_GET['wbf_reset_token'])) return self::view_reset_password();
|
||||
if (isset($_GET['forum_rules'])) return self::view_rules();
|
||||
if (isset($_GET['forum_thread'])) return self::view_thread();
|
||||
if (isset($_GET['forum_cat'])) return self::view_category();
|
||||
if (isset($_GET['forum_profile'])) return self::view_profile();
|
||||
@@ -158,6 +197,19 @@ class WBF_Shortcodes {
|
||||
return self::view_home();
|
||||
}
|
||||
|
||||
|
||||
/** Darf der Nutzer diese Kategorie sehen? */
|
||||
private static function can_see_category( $user, $cat ) {
|
||||
// Gäste: guest_visible prüfen
|
||||
if ( ! $user && (int)($cat->guest_visible ?? 1) === 0 ) return false;
|
||||
// Min-Rolle: Nutzer muss mindestens diese Rolle haben um die Kategorie zu sehen
|
||||
if ( $cat->min_role && $cat->min_role !== 'member' ) {
|
||||
if ( ! $user ) return false; // nicht eingeloggt → versteckt wenn min_role > member
|
||||
if ( WBF_Roles::level($user->role) < WBF_Roles::level($cat->min_role) ) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── HOME ──────────────────────────────────────────────────────────────────
|
||||
|
||||
private static function view_home() {
|
||||
@@ -195,11 +247,15 @@ class WBF_Shortcodes {
|
||||
<div class="wbf-section-header">
|
||||
<h2><?php echo esc_html(wbf_get_settings()['section_cats']); ?></h2>
|
||||
<?php if (WBF_DB::can($current,'create_thread')): ?>
|
||||
<button class="wbf-btn wbf-btn--primary" onclick="wbfShowNewThread()"><i class="fas fa-plus"></i> <?php echo esc_html(wbf_get_settings()['btn_new_thread']); ?></button>
|
||||
<div style="display:flex;gap:.5rem;flex-wrap:wrap">
|
||||
<button class="wbf-btn wbf-btn--outline-poll" onclick="wbfShowNewPoll()"><i class="fas fa-chart-bar"></i> Neue Umfrage</button>
|
||||
<button class="wbf-btn wbf-btn--primary" onclick="wbfShowNewThread()"><i class="fas fa-plus"></i> <?php echo esc_html(wbf_get_settings()['btn_new_thread']); ?></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="wbf-cat-group-list">
|
||||
<?php foreach ($tree as $parent): ?>
|
||||
<?php foreach ($tree as $parent):
|
||||
if (!self::can_see_category($current, $parent)) continue; ?>
|
||||
<div class="wbf-cat-group">
|
||||
<div class="wbf-cat-parent">
|
||||
<div class="wbf-cat-parent__icon"><i class="<?php echo esc_attr($parent->icon); ?>"></i></div>
|
||||
@@ -217,7 +273,8 @@ class WBF_Shortcodes {
|
||||
</div>
|
||||
<?php if (!empty($parent->children)): ?>
|
||||
<div class="wbf-cat-children">
|
||||
<?php foreach ($parent->children as $child): ?>
|
||||
<?php foreach ($parent->children as $child):
|
||||
if (!self::can_see_category($current, $child)) continue; ?>
|
||||
<div class="wbf-cat-child">
|
||||
<div class="wbf-cat-child__icon"><i class="<?php echo esc_attr($child->icon); ?>"></i></div>
|
||||
<div class="wbf-cat-child__body">
|
||||
@@ -286,6 +343,7 @@ class WBF_Shortcodes {
|
||||
</div>
|
||||
</div>
|
||||
<?php self::render_new_thread_modal(WBF_DB::get_categories_flat(), $current); ?>
|
||||
<?php self::render_forum_footer(); ?>
|
||||
<?php self::render_auth_modal(); ?>
|
||||
</div>
|
||||
<?php return ob_get_clean();
|
||||
@@ -298,11 +356,30 @@ class WBF_Shortcodes {
|
||||
$cat = WBF_DB::get_category($slug);
|
||||
if (!$cat) return '<p class="wbf-notice">Kategorie nicht gefunden.</p>';
|
||||
|
||||
$current = WBF_Auth::get_current_user();
|
||||
|
||||
// Zugang prüfen — Gäste + Min-Rolle
|
||||
if (!self::can_see_category($current, $cat)) {
|
||||
ob_start(); ?>
|
||||
<div class="wbf-wrap"><?php self::render_topbar($current); ?>
|
||||
<div class="wbf-container wbf-mt">
|
||||
<div class="wbf-notice wbf-notice--warning">
|
||||
<i class="fas fa-lock"></i>
|
||||
<?php if (!$current): ?>
|
||||
Diese Kategorie ist nur für eingeloggte Mitglieder sichtbar.
|
||||
<a href="#" class="wbf-login-link" style="margin-left:.5rem;font-weight:700">Jetzt einloggen</a>
|
||||
<?php else: ?>
|
||||
Du hast keine Berechtigung um diese Kategorie zu sehen.
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div></div>
|
||||
<?php return ob_get_clean();
|
||||
}
|
||||
|
||||
$page = max(1,(int)($_GET['fp']??1));
|
||||
$threads = WBF_DB::get_threads($cat->id, $page);
|
||||
$total = WBF_DB::count_threads($cat->id);
|
||||
$pages = ceil($total / 20) ?: 1;
|
||||
$current = WBF_Auth::get_current_user();
|
||||
$children = WBF_DB::get_child_categories($cat->id);
|
||||
$crumbs = WBF_DB::get_category_breadcrumb($cat);
|
||||
|
||||
@@ -322,12 +399,16 @@ class WBF_Shortcodes {
|
||||
<div><h2><i class="<?php echo esc_attr($cat->icon); ?>"></i> <?php echo esc_html($cat->name); ?></h2>
|
||||
<p class="wbf-muted"><?php echo esc_html($cat->description); ?></p></div>
|
||||
<?php if (WBF_DB::can($current,'create_thread') && WBF_DB::can_post_in($current,$cat)): ?>
|
||||
<button class="wbf-btn wbf-btn--primary" onclick="wbfShowNewThread(<?php echo $cat->id; ?>)"><i class="fas fa-plus"></i> <?php echo esc_html(wbf_get_settings()['btn_new_thread']); ?></button>
|
||||
<div style="display:flex;gap:.5rem;flex-wrap:wrap">
|
||||
<button class="wbf-btn wbf-btn--outline-poll" onclick="wbfShowNewPoll(<?php echo $cat->id; ?>)"><i class="fas fa-chart-bar"></i> Neue Umfrage</button>
|
||||
<button class="wbf-btn wbf-btn--primary" onclick="wbfShowNewThread(<?php echo $cat->id; ?>)"><i class="fas fa-plus"></i> <?php echo esc_html(wbf_get_settings()['btn_new_thread']); ?></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if (!empty($children)): ?>
|
||||
<div class="wbf-subcat-list">
|
||||
<?php foreach ($children as $child): ?>
|
||||
<?php foreach ($children as $child):
|
||||
if (!self::can_see_category($current, $child)) continue; ?>
|
||||
<a href="?forum_cat=<?php echo esc_attr($child->slug); ?>" class="wbf-subcat-card">
|
||||
<div class="wbf-subcat-card__icon"><i class="<?php echo esc_attr($child->icon); ?>"></i></div>
|
||||
<div class="wbf-subcat-card__body">
|
||||
@@ -347,12 +428,14 @@ class WBF_Shortcodes {
|
||||
$liked = $current ? WBF_DB::has_liked($current->id,$t->id,'thread') : false; ?>
|
||||
<div class="wbf-thread-row<?php echo $t->pinned?' wbf-thread-row--pinned':''; ?>"
|
||||
data-thread-id="<?php echo (int)$t->id; ?>"
|
||||
data-last-reply="<?php echo esc_attr($t->last_reply_at); ?>">
|
||||
data-last-reply="<?php echo esc_attr($t->last_reply_at); ?>"
|
||||
data-preview="<?php echo esc_attr(mb_substr(strip_tags(WBF_BBCode::render($t->content)),0,160)); ?>">
|
||||
<div class="wbf-thread-row__avatar"><?php echo self::avatar($t->avatar_url,$t->display_name); ?></div>
|
||||
<div class="wbf-thread-row__body">
|
||||
<div class="wbf-thread-row__top">
|
||||
<?php if ($t->pinned): ?><span class="wbf-pin"><i class="fas fa-thumbtack"></i></span><?php endif; ?>
|
||||
<?php if ($t->status==='closed'): ?><span class="wbf-badge wbf-badge--closed"><i class="fas fa-lock"></i> Geschlossen</span><?php endif; ?>
|
||||
<?php echo self::render_prefix($t); ?>
|
||||
<a href="?forum_thread=<?php echo (int)$t->id; ?>" class="wbf-thread-row__title"><?php echo esc_html($t->title); ?></a>
|
||||
<span class="wbf-new-badge" style="display:none"><i class="fas fa-circle-dot"></i> Neu</span>
|
||||
</div>
|
||||
@@ -419,6 +502,7 @@ class WBF_Shortcodes {
|
||||
<?php endif; endif; ?>
|
||||
|
||||
<?php self::render_new_thread_modal(WBF_DB::get_categories_flat(),$current,$cat->id); ?>
|
||||
<?php self::render_forum_footer(); ?>
|
||||
<?php self::render_auth_modal(); ?>
|
||||
</div>
|
||||
<?php return ob_get_clean();
|
||||
@@ -427,9 +511,33 @@ class WBF_Shortcodes {
|
||||
// ── THREAD ────────────────────────────────────────────────────────────────
|
||||
|
||||
private static function view_thread() {
|
||||
// Wartungsmodus
|
||||
$maint6 = wbf_get_settings()['maintenance_mode'] ?? '0';
|
||||
$cur6 = WBF_Auth::get_current_user();
|
||||
if ( $maint6 === '1' && ( !$cur6 || WBF_Roles::level($cur6->role) < 50 ) ) {
|
||||
return self::view_maintenance();
|
||||
}
|
||||
$id = (int)($_GET['forum_thread'] ?? 0);
|
||||
$thread = WBF_DB::get_thread($id);
|
||||
if (!$thread) return '<p class="wbf-notice">Thread nicht gefunden.</p>';
|
||||
// Kategorie-Zugang prüfen (Gäste + Min-Rolle)
|
||||
$cat6 = WBF_DB::get_category($thread->category_id);
|
||||
if ($cat6 && !self::can_see_category($cur6, $cat6)) {
|
||||
ob_start(); ?>
|
||||
<div class="wbf-wrap"><?php self::render_topbar($cur6); ?>
|
||||
<div class="wbf-container wbf-mt">
|
||||
<div class="wbf-notice wbf-notice--warning">
|
||||
<i class="fas fa-lock"></i>
|
||||
<?php if (!$cur6): ?>
|
||||
Dieser Thread ist nur für eingeloggte Mitglieder sichtbar.
|
||||
<a href="#" class="wbf-login-link" style="margin-left:.5rem;font-weight:700">Jetzt einloggen</a>
|
||||
<?php else: ?>
|
||||
Du hast keine Berechtigung um diesen Thread zu sehen.
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div></div>
|
||||
<?php return ob_get_clean();
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$wpdb->query($wpdb->prepare("UPDATE {$wpdb->prefix}forum_threads SET views=views+1 WHERE id=%d",$id));
|
||||
@@ -462,18 +570,101 @@ class WBF_Shortcodes {
|
||||
<?php if ($thread->pinned): ?><i class="fas fa-thumbtack wbf-pin-icon"></i><?php endif; ?>
|
||||
<?php if ($thread->status==='closed'): ?><i class="fas fa-lock" style="color:var(--c-muted);font-size:1rem;margin-right:.35rem"></i><?php endif; ?>
|
||||
<?php if ($thread->status==='archived'): ?><span class="wbf-badge wbf-badge--archived"><i class="fas fa-box-archive"></i> Archiviert</span><?php endif; ?>
|
||||
<?php echo self::render_prefix($thread); ?>
|
||||
<?php echo esc_html($thread->title); ?>
|
||||
</h1>
|
||||
<div class="wbf-thread-header-meta">
|
||||
<?php echo self::like_btn($id,'thread',$thread->like_count,$t_liked); ?>
|
||||
<?php if ($current): $is_bm = WBF_DB::is_bookmarked($current->id,$id); ?>
|
||||
<button class="wbf-bookmark-btn<?php echo $is_bm?' wbf-bookmarked':''; ?>" data-thread="<?php echo $id; ?>" title="<?php echo $is_bm?'Lesezeichen entfernen':'Lesezeichen hinzufügen'; ?>">
|
||||
<i class="fa<?php echo $is_bm?'s':'r'; ?> fa-bookmark"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<span><i class="fas fa-comment-dots"></i> <?php echo (int)$thread->reply_count; ?> Antworten</span>
|
||||
<span><i class="fas fa-eye"></i> <?php echo (int)$thread->views; ?> Views</span>
|
||||
</div>
|
||||
<?php $thread_tags = WBF_DB::get_thread_tags($id); echo self::render_tags($thread_tags); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wbf-posts" id="wbfPosts">
|
||||
<!-- OP -->
|
||||
<!-- Poll (optional) -->
|
||||
<?php
|
||||
$poll = WBF_DB::get_poll($id);
|
||||
$can_add_poll = $current && (int)$current->id === (int)$thread->user_id && !$poll && $thread->status === 'open';
|
||||
if ($can_add_poll): ?>
|
||||
<div style="margin-bottom:1rem">
|
||||
<button class="wbf-btn wbf-btn--sm wbf-btn--outline" id="wbfOpenPollModal">
|
||||
<i class="fas fa-chart-bar"></i> Umfrage hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
<?php endif;
|
||||
if ($poll):
|
||||
$results = WBF_DB::get_poll_results($poll->id);
|
||||
$my_votes = $current ? WBF_DB::get_user_votes($poll->id, $current->id) : [];
|
||||
$total = array_sum($results);
|
||||
$voted = !empty($my_votes);
|
||||
$expired = $poll->ends_at && strtotime($poll->ends_at) < time();
|
||||
$show_results = $voted || $expired || !$current;
|
||||
?>
|
||||
<div class="wbf-poll" id="wbfPoll-<?php echo (int)$poll->id; ?>" data-poll-id="<?php echo (int)$poll->id; ?>" data-multi="<?php echo (int)$poll->multi; ?>">
|
||||
<div class="wbf-poll__header">
|
||||
<i class="fas fa-chart-bar"></i>
|
||||
<span class="wbf-poll__title"><?php echo esc_html($poll->question); ?></span>
|
||||
<?php if ($poll->multi): ?><span class="wbf-poll__badge">Mehrfachauswahl</span><?php endif; ?>
|
||||
<?php if ($expired): ?><span class="wbf-poll__badge wbf-poll__badge--ended">Beendet</span><?php endif; ?>
|
||||
</div>
|
||||
<div class="wbf-poll__body">
|
||||
<?php if ($show_results): ?>
|
||||
<!-- Ergebnisse -->
|
||||
<?php foreach ($poll->options as $i => $opt):
|
||||
$votes = $results[$i] ?? 0;
|
||||
$pct = $total > 0 ? round($votes / $total * 100) : 0;
|
||||
$mine = in_array($i, $my_votes);
|
||||
?>
|
||||
<div class="wbf-poll__result<?php echo $mine?' wbf-poll__result--mine':''; ?>">
|
||||
<div class="wbf-poll__result-bar" style="width:<?php echo $pct; ?>%"></div>
|
||||
<div class="wbf-poll__result-content">
|
||||
<span class="wbf-poll__result-label"><?php if($mine): ?><i class="fas fa-check-circle" style="color:var(--c-primary)"></i> <?php endif; ?><?php echo esc_html($opt); ?></span>
|
||||
<span class="wbf-poll__result-pct"><?php echo $pct; ?>% <span style="color:var(--c-muted);font-size:.75em">(<?php echo $votes; ?>)</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<div class="wbf-poll__footer">
|
||||
<i class="fas fa-users"></i> <?php echo $total; ?> Stimme<?php echo $total!=1?'n':''; ?>
|
||||
<?php if ($poll->ends_at && !$expired): ?>
|
||||
· <i class="fas fa-clock"></i> Endet <?php echo esc_html(date_i18n('d.m.Y \u\m H:i \U\h\r', strtotime($poll->ends_at))); ?>
|
||||
<?php elseif ($expired): ?>
|
||||
· <i class="fas fa-flag-checkered"></i> Abgestimmt
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<!-- Abstimmung -->
|
||||
<form class="wbf-poll__form" data-poll-id="<?php echo (int)$poll->id; ?>">
|
||||
<?php foreach ($poll->options as $i => $opt): ?>
|
||||
<label class="wbf-poll__option">
|
||||
<input type="<?php echo $poll->multi?'checkbox':'radio'; ?>"
|
||||
name="wbf_poll_option" value="<?php echo $i; ?>"
|
||||
style="accent-color:var(--c-primary)">
|
||||
<?php echo esc_html($opt); ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
<div style="display:flex;align-items:center;gap:.75rem;margin-top:.75rem">
|
||||
<button type="submit" class="wbf-btn wbf-btn--sm wbf-btn--primary">
|
||||
<i class="fas fa-vote-yea"></i> Abstimmen
|
||||
</button>
|
||||
<span class="wbf-poll__msg wbf-msg"></span>
|
||||
</div>
|
||||
</form>
|
||||
<?php if ($poll->ends_at): ?>
|
||||
<div class="wbf-poll__footer">
|
||||
<i class="fas fa-clock"></i> Endet <?php echo esc_html(date_i18n('d.m.Y \u\m H:i \U\h\r', strtotime($poll->ends_at))); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
$op_reported = $current ? WBF_DB::has_reported($current->id, $id, 'thread') : false;
|
||||
$op_can_edit = $current && ((int)$current->id === (int)$thread->user_id || WBF_DB::can($current,'delete_post'));
|
||||
@@ -508,7 +699,16 @@ class WBF_Shortcodes {
|
||||
<div class="wbf-post__footer">
|
||||
<span class="wbf-post__date"><?php echo self::time_ago($thread->created_at); ?></span>
|
||||
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap">
|
||||
<?php if ($current && WBF_DB::can($current,'post') && $thread->status !== 'closed'): ?>
|
||||
<?php if ($current): ?>
|
||||
<?php $is_subbed = WBF_DB::is_subscribed($current->id, $id); ?>
|
||||
<button class="wbf-subscribe-btn wbf-btn wbf-btn--sm<?php echo $is_subbed?' wbf-btn--primary':''; ?>"
|
||||
data-thread="<?php echo (int)$id; ?>"
|
||||
title="<?php echo $is_subbed?'Abonnement entfernen':'Thread abonnieren'; ?>">
|
||||
<i class="fas fa-bell<?php echo $is_subbed?'':'-slash'; ?>"></i>
|
||||
<?php echo $is_subbed?'Abonniert':'Abonnieren'; ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if ($current && WBF_DB::can($current,'post') && $thread->status !== 'closed'): ?>
|
||||
<button class="wbf-quote-btn"
|
||||
data-source="wbf-thread-content-<?php echo (int)$id; ?>"
|
||||
data-author="<?php echo esc_attr($thread->display_name); ?>"
|
||||
@@ -575,9 +775,11 @@ class WBF_Shortcodes {
|
||||
<div class="wbf-notice wbf-notice--warning"><i class="fas fa-lock"></i> Dieser Thread ist geschlossen.</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php self::render_forum_footer(); ?>
|
||||
<?php self::render_auth_modal(); ?>
|
||||
<?php self::render_report_modal(); ?>
|
||||
<?php if (WBF_DB::can($current,'manage_cats')): self::render_move_modal(WBF_DB::get_categories_flat(), $id); endif; ?>
|
||||
<?php if ($can_add_poll): self::render_poll_modal($id); endif; ?>
|
||||
</div>
|
||||
<?php return ob_get_clean();
|
||||
}
|
||||
@@ -656,6 +858,18 @@ class WBF_Shortcodes {
|
||||
$profile = $profile_id ? WBF_DB::get_user($profile_id) : $current;
|
||||
if (!$profile) return '<p class="wbf-notice">Profil nicht gefunden.</p>';
|
||||
$is_own = $current && $current->id == $profile->id;
|
||||
$is_staff = $current && WBF_Roles::level($current->role) >= 50;
|
||||
// Profil-Sichtbarkeit prüfen
|
||||
if (!$is_own && !$is_staff && (int)($profile->profile_public ?? 1) === 0) {
|
||||
ob_start(); ?>
|
||||
<div class="wbf-wrap"><?php self::render_topbar($current); ?>
|
||||
<div class="wbf-container wbf-mt">
|
||||
<div class="wbf-notice wbf-notice--warning">
|
||||
<i class="fas fa-user-lock"></i> Dieses Profil ist nicht öffentlich.
|
||||
</div>
|
||||
</div></div>
|
||||
<?php return ob_get_clean();
|
||||
}
|
||||
$user_posts = WBF_DB::get_user_posts( $profile->id, 50 );
|
||||
|
||||
ob_start(); ?>
|
||||
@@ -727,6 +941,33 @@ class WBF_Shortcodes {
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Benutzerdefinierte Profilfelder (öffentliche) -->
|
||||
<?php
|
||||
$cf_defs_pub = WBF_DB::get_profile_field_defs();
|
||||
$cf_vals_pub = WBF_DB::get_user_meta( $profile->id );
|
||||
foreach ( $cf_defs_pub as $def ):
|
||||
if ( ! $is_own && empty($def['public']) ) continue;
|
||||
$val = trim( $cf_vals_pub[ $def['key'] ] ?? '' );
|
||||
if ( $val === '' ) continue;
|
||||
?>
|
||||
<div class="wbf-profile-sidebar__section">
|
||||
<span class="wbf-profile-sidebar__section-label">
|
||||
<i class="fas fa-<?php echo $def['type']==='url'?'link':($def['type']==='number'?'hashtag':'tag'); ?>"></i>
|
||||
<?php echo esc_html($def['label']); ?>
|
||||
</span>
|
||||
<?php if ( $def['type'] === 'url' ): ?>
|
||||
<a href="<?php echo esc_url($val); ?>" target="_blank" rel="noopener noreferrer"
|
||||
style="color:var(--c-primary);font-size:.85rem;word-break:break-all">
|
||||
<?php echo esc_html( mb_strtolower( preg_replace('#^https?://#i','',$val) ) ); ?>
|
||||
</a>
|
||||
<?php elseif ( $def['type'] === 'textarea' ): ?>
|
||||
<p style="font-size:.85rem"><?php echo nl2br(esc_html($val)); ?></p>
|
||||
<?php else: ?>
|
||||
<p style="font-size:.85rem"><?php echo esc_html($val); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</aside>
|
||||
|
||||
<!-- ── MAIN ────────────────────────────────────────────── -->
|
||||
@@ -754,7 +995,17 @@ class WBF_Shortcodes {
|
||||
</div>
|
||||
<div class="wbf-form-row">
|
||||
<label>Signatur <small>(max. 300 Zeichen)</small></label>
|
||||
<textarea id="wbfEditSignature" rows="2" maxlength="300" placeholder="Deine Signatur…"><?php echo esc_textarea($profile->signature ?? ''); ?></textarea>
|
||||
<div class="wbf-form-row" style="display:flex;align-items:center;gap:.75rem;margin-bottom:.75rem">
|
||||
<label style="font-size:.82rem;color:var(--c-muted)">Profil öffentlich sichtbar</label>
|
||||
<?php $pub = (int)($profile->profile_public ?? 1); ?>
|
||||
<button type="button" id="wbfToggleProfileVis"
|
||||
class="wbf-btn wbf-btn--sm<?php echo $pub?' wbf-btn--primary':''; ?>"
|
||||
data-state="<?php echo $pub; ?>">
|
||||
<i class="fas fa-<?php echo $pub?'eye':'eye-slash'; ?>"></i>
|
||||
<?php echo $pub?'Öffentlich':'Privat'; ?>
|
||||
</button>
|
||||
</div>
|
||||
<textarea id="wbfEditSignature" rows="2" maxlength="300" placeholder="Deine Signatur…"><?php echo esc_textarea($profile->signature ?? ''); ?></textarea>
|
||||
<div class="wbf-sig-counter"><span id="wbfSigCount"><?php echo mb_strlen($profile->signature??''); ?></span>/300</div>
|
||||
</div>
|
||||
<div class="wbf-profile-card__footer">
|
||||
@@ -765,8 +1016,104 @@ class WBF_Shortcodes {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Benutzerdefinierte Profilfelder ──────────────── -->
|
||||
<?php
|
||||
$cf_defs = WBF_DB::get_profile_field_defs();
|
||||
$cf_vals = WBF_DB::get_user_meta( $profile->id );
|
||||
if ( ! empty( $cf_defs ) ):
|
||||
?>
|
||||
<div class="wbf-profile-card">
|
||||
<div class="wbf-profile-card__header">
|
||||
<i class="fas fa-sliders"></i> Weitere Profilangaben
|
||||
</div>
|
||||
<div class="wbf-profile-card__body">
|
||||
<div class="wbf-profile-edit-grid">
|
||||
<?php foreach ( $cf_defs as $def ):
|
||||
$k = esc_attr( $def['key'] );
|
||||
$lbl = esc_html( $def['label'] );
|
||||
$ph = esc_attr( $def['placeholder'] ?? '' );
|
||||
$val = esc_attr( $cf_vals[ $def['key'] ] ?? '' );
|
||||
$req = ! empty($def['required']) ? 'required' : '';
|
||||
?>
|
||||
<div class="wbf-form-row">
|
||||
<label><?php echo $lbl; ?><?php if($req): ?> <span style="color:var(--c-danger)">*</span><?php endif; ?></label>
|
||||
<?php if ( $def['type'] === 'textarea' ): ?>
|
||||
<textarea class="wbf-cf-input" data-field="cf_<?php echo $k; ?>"
|
||||
rows="2" placeholder="<?php echo $ph; ?>"
|
||||
<?php echo $req; ?>><?php echo esc_textarea( $cf_vals[$def['key']] ?? '' ); ?></textarea>
|
||||
<?php elseif ( $def['type'] === 'select' ):
|
||||
$opts = array_filter( array_map('trim', explode("\n", $def['options'] ?? '')) );
|
||||
?>
|
||||
<select class="wbf-cf-input" data-field="cf_<?php echo $k; ?>">
|
||||
<option value="">— Bitte wählen —</option>
|
||||
<?php foreach ( $opts as $opt ): ?>
|
||||
<option value="<?php echo esc_attr($opt); ?>"
|
||||
<?php selected( $cf_vals[$def['key']] ?? '', $opt ); ?>><?php echo esc_html($opt); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php else: ?>
|
||||
<input type="<?php echo $def['type'] === 'url' ? 'url' : ($def['type'] === 'number' ? 'number' : 'text'); ?>"
|
||||
class="wbf-cf-input"
|
||||
data-field="cf_<?php echo $k; ?>"
|
||||
value="<?php echo $val; ?>"
|
||||
placeholder="<?php echo $ph; ?>"
|
||||
<?php echo $req; ?>>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="wbf-profile-card__footer">
|
||||
<button class="wbf-btn wbf-btn--primary" id="wbfSaveProfileCf">
|
||||
<i class="fas fa-save"></i> Speichern
|
||||
</button>
|
||||
<span class="wbf-msg" id="wbfProfileCfMsg"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ── DSGVO: Konto löschen ──────────────────────────── -->
|
||||
<div class="wbf-profile-card" style="border-color:rgba(240,82,82,.25)">
|
||||
<div class="wbf-profile-card__header" style="color:var(--c-danger);background:rgba(240,82,82,.06);border-bottom-color:rgba(240,82,82,.15)">
|
||||
<i class="fas fa-shield-halved"></i> Datenschutz & Konto löschen
|
||||
</div>
|
||||
<div class="wbf-profile-card__body">
|
||||
<p style="font-size:.85rem;color:var(--c-text-dim);margin-bottom:1rem;line-height:1.6">
|
||||
Gemäß <strong>DSGVO Art. 17</strong> (Recht auf Vergessenwerden) kannst du die vollständige Löschung deines Kontos und aller personenbezogenen Daten beantragen.<br>
|
||||
<span style="color:var(--c-muted);font-size:.8rem">Deine Beiträge bleiben anonymisiert sichtbar. Direktnachrichten, Likes, Profilinformationen und alle persönlichen Daten werden dauerhaft gelöscht.</span>
|
||||
</p>
|
||||
<div id="wbfGdprBox" style="background:rgba(240,82,82,.06);border:1px solid rgba(240,82,82,.2);border-radius:var(--radius-sm);padding:1.1rem;display:none">
|
||||
<p style="font-size:.82rem;font-weight:700;color:var(--c-danger);margin-bottom:.9rem"><i class="fas fa-triangle-exclamation"></i> Diese Aktion ist unwiderruflich.</p>
|
||||
<div class="wbf-form-row">
|
||||
<label style="font-size:.72rem">Passwort zur Bestätigung</label>
|
||||
<input type="password" id="wbfGdprPassword" placeholder="Dein aktuelles Passwort" autocomplete="current-password">
|
||||
</div>
|
||||
<label style="display:flex;align-items:center;gap:.6rem;font-size:.82rem;color:var(--c-text-dim);cursor:pointer;margin-bottom:1rem">
|
||||
<input type="checkbox" id="wbfGdprConfirm" style="width:15px;height:15px;accent-color:var(--c-danger);cursor:pointer">
|
||||
Ich verstehe, dass mein Konto und alle persönlichen Daten unwiderruflich gelöscht werden.
|
||||
</label>
|
||||
<div style="display:flex;gap:.75rem;align-items:center;flex-wrap:wrap">
|
||||
<button class="wbf-btn wbf-btn--sm" id="wbfGdprCancel" onclick="document.getElementById('wbfGdprBox').style.display='none';document.getElementById('wbfGdprToggle').style.display=''">
|
||||
<i class="fas fa-xmark"></i> Abbrechen
|
||||
</button>
|
||||
<button class="wbf-btn wbf-btn--sm" id="wbfGdprSubmit"
|
||||
style="background:rgba(240,82,82,.15);color:var(--c-danger);border-color:rgba(240,82,82,.4)">
|
||||
<i class="fas fa-trash-can"></i> Konto endgültig löschen
|
||||
</button>
|
||||
<span class="wbf-msg" id="wbfGdprMsg"></span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="wbf-btn wbf-btn--sm" id="wbfGdprToggle"
|
||||
style="background:rgba(240,82,82,.08);color:var(--c-danger);border-color:rgba(240,82,82,.3)"
|
||||
onclick="document.getElementById('wbfGdprBox').style.display='';document.getElementById('wbfGdprToggle').style.display='none'">
|
||||
<i class="fas fa-trash-can"></i> Konto löschen (DSGVO Art. 17)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php endif; /* end $is_own */ ?>
|
||||
|
||||
<!-- Beiträge -->
|
||||
<div class="wbf-profile-card">
|
||||
<div class="wbf-profile-card__header">
|
||||
@@ -812,6 +1159,33 @@ class WBF_Shortcodes {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lesezeichen (nur eigenes Profil) -->
|
||||
<?php if ($is_own):
|
||||
$bookmarks = WBF_DB::get_user_bookmarks($current->id, 50); ?>
|
||||
<div class="wbf-profile-card">
|
||||
<div class="wbf-profile-card__header">
|
||||
<i class="fas fa-bookmark"></i> Lesezeichen
|
||||
<span class="wbf-profile-card__count"><?php echo count($bookmarks); ?></span>
|
||||
</div>
|
||||
<div class="wbf-profile-card__body wbf-profile-card__body--posts">
|
||||
<?php if (empty($bookmarks)): ?>
|
||||
<p class="wbf-profile-empty">Noch keine Lesezeichen.</p>
|
||||
<?php else: foreach ($bookmarks as $bm): ?>
|
||||
<div class="wbf-profile-post-item">
|
||||
<div class="wbf-profile-post-item__top">
|
||||
<?php echo self::render_prefix($bm); ?>
|
||||
<a href="?forum_thread=<?php echo (int)$bm->id; ?>" class="wbf-profile-post-item__title">
|
||||
<?php echo esc_html(mb_substr($bm->title,0,60)); ?>
|
||||
</a>
|
||||
<span class="wbf-profile-post-item__cat"><i class="fas fa-folder"></i> <?php echo esc_html($bm->cat_name); ?></span>
|
||||
<span class="wbf-profile-post-item__time"><i class="fas fa-bookmark" style="font-size:.65rem"></i> <?php echo self::time_ago($bm->bookmarked_at); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div><!-- /.wbf-profile-main -->
|
||||
</div><!-- /.wbf-profile-layout -->
|
||||
</div>
|
||||
@@ -901,6 +1275,7 @@ class WBF_Shortcodes {
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php self::render_forum_footer(); ?>
|
||||
<?php self::render_auth_modal(); ?>
|
||||
</div>
|
||||
<?php return ob_get_clean();
|
||||
@@ -912,8 +1287,20 @@ class WBF_Shortcodes {
|
||||
$current = WBF_Auth::get_current_user();
|
||||
if (!$current) {
|
||||
ob_start(); ?>
|
||||
<div class="wbf-wrap"><?php self::render_topbar(null); ?>
|
||||
<div class="wbf-container wbf-mt"><div class="wbf-notice"><i class="fas fa-lock"></i> Bitte <a href="#" class="wbf-login-link">einloggen</a> um Nachrichten zu lesen.</div></div></div>
|
||||
<div class="wbf-wrap">
|
||||
<?php self::render_topbar(null); ?>
|
||||
<div class="wbf-container wbf-mt" style="display:flex;justify-content:center;padding:3rem 1rem">
|
||||
<div style="width:100%;max-width:460px;background:var(--c-surface);border:1px solid rgba(0,180,216,.25);border-radius:var(--radius);padding:2rem;position:relative;overflow:hidden;box-shadow:0 24px 60px rgba(0,0,0,.6)">
|
||||
<div style="position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,#00b4d8,transparent)"></div>
|
||||
<div style="text-align:center;margin-bottom:1.5rem">
|
||||
<div style="font-size:2.2rem;margin-bottom:.6rem">🔒</div>
|
||||
<h2 style="font-size:1.1rem;font-weight:700;color:var(--c-text);margin-bottom:.3rem">Bitte einloggen</h2>
|
||||
<p style="font-size:.85rem;color:var(--c-muted)">Um Nachrichten lesen zu können, musst du eingeloggt sein.</p>
|
||||
</div>
|
||||
<?php self::render_auth_forms(); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php return ob_get_clean();
|
||||
}
|
||||
$partner_id = (int)($_GET['with'] ?? 0);
|
||||
@@ -969,6 +1356,7 @@ class WBF_Shortcodes {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php self::render_forum_footer(); ?>
|
||||
<?php self::render_auth_modal(); ?>
|
||||
<?php self::render_dm_compose_modal(); ?>
|
||||
</div>
|
||||
@@ -1006,6 +1394,9 @@ class WBF_Shortcodes {
|
||||
// ── SEARCH ────────────────────────────────────────────────────────────────
|
||||
|
||||
private static function view_search() {
|
||||
$cur_s = WBF_Auth::get_current_user();
|
||||
$maint_s = wbf_get_settings()['maintenance_mode'] ?? '0';
|
||||
if ($maint_s === '1' && (!$cur_s || WBF_Roles::level($cur_s->role) < 50)) return self::view_maintenance();
|
||||
$query = sanitize_text_field($_GET['q'] ?? '');
|
||||
$current = WBF_Auth::get_current_user();
|
||||
$results = mb_strlen($query) >= 2 ? WBF_DB::search($query, 40) : [];
|
||||
@@ -1053,6 +1444,7 @@ class WBF_Shortcodes {
|
||||
<p style="color:var(--c-muted);font-size:.82rem;margin-top:1rem"><?php echo count($results); ?> Ergebnis(se) gefunden.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php self::render_forum_footer(); ?>
|
||||
<?php self::render_auth_modal(); ?>
|
||||
</div>
|
||||
<?php return ob_get_clean();
|
||||
@@ -1063,7 +1455,7 @@ class WBF_Shortcodes {
|
||||
?>
|
||||
<div class="wbf-topbar">
|
||||
<div class="wbf-topbar__inner">
|
||||
<a href="<?php echo esc_url(remove_query_arg(['forum_cat','forum_thread','forum_profile','forum_search','forum_tag','fp','tp'])); ?>" class="wbf-topbar__brand">
|
||||
<a href="<?php echo esc_url(wbf_get_forum_url()); ?>" class="wbf-topbar__brand">
|
||||
<i class="fas fa-comments"></i> <?php echo esc_html(wbf_get_settings()['topbar_brand']); ?>
|
||||
</a>
|
||||
<!-- Suchfeld -->
|
||||
@@ -1120,11 +1512,18 @@ class WBF_Shortcodes {
|
||||
</div>
|
||||
<?php }
|
||||
|
||||
private static function render_auth_forms() { ?>
|
||||
private static function render_auth_forms() {
|
||||
$reg_mode = wbf_get_settings()['registration_mode'] ?? 'open';
|
||||
$invite_msg = wbf_get_settings()['invite_message'] ?? 'Registrierung ist aktuell nur auf Einladung möglich.';
|
||||
?>
|
||||
<div class="wbf-auth-box">
|
||||
<div class="wbf-auth-tabs">
|
||||
<button class="wbf-auth-tab active" data-tab="login">Login</button>
|
||||
<?php if ($reg_mode === 'open'): ?>
|
||||
<button class="wbf-auth-tab" data-tab="register">Registrieren</button>
|
||||
<?php elseif ($reg_mode === 'invite'): ?>
|
||||
<button class="wbf-auth-tab wbf-auth-tab--muted" disabled title="<?php echo esc_attr($invite_msg); ?>">Registrieren <i class="fas fa-lock" style="font-size:.7em"></i></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="wbf-auth-panel active" data-panel="login">
|
||||
<div class="wbf-form-row"><input type="text" class="wbf-field-username" placeholder="Benutzername oder E-Mail"></div>
|
||||
@@ -1137,6 +1536,20 @@ class WBF_Shortcodes {
|
||||
<div style="text-align:right;margin-top:.4rem"><a href="#" class="wbf-forgot-link" style="font-size:.78rem;color:var(--c-muted)">Passwort vergessen?</a></div>
|
||||
<span class="wbf-login-msg wbf-msg"></span>
|
||||
</div>
|
||||
<!-- Registrierung gesperrt/invite -->
|
||||
<?php if ($reg_mode === 'invite'): ?>
|
||||
<div class="wbf-auth-panel" data-panel="register">
|
||||
<div class="wbf-notice" style="margin:.5rem 0;font-size:.85rem;text-align:center">
|
||||
<i class="fas fa-lock"></i> <?php echo esc_html($invite_msg); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php elseif ($reg_mode === 'disabled'): ?>
|
||||
<div class="wbf-auth-panel" data-panel="register">
|
||||
<div class="wbf-notice wbf-notice--warning" style="margin:.5rem 0;font-size:.85rem;text-align:center">
|
||||
<i class="fas fa-ban"></i> Registrierung ist deaktiviert.
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<!-- Passwort vergessen -->
|
||||
<div class="wbf-auth-panel" data-panel="forgot">
|
||||
<p style="font-size:.82rem;color:var(--c-text-dim);margin-bottom:.75rem">Gib deine E-Mail ein — wir schicken dir einen Reset-Link.</p>
|
||||
@@ -1146,10 +1559,37 @@ class WBF_Shortcodes {
|
||||
<span class="wbf-forgot-msg wbf-msg"></span>
|
||||
</div>
|
||||
<div class="wbf-auth-panel" data-panel="register">
|
||||
<?php
|
||||
$inv_code_prefill = '';
|
||||
if (isset($_GET['wbf_invite'])) $inv_code_prefill = strtoupper(sanitize_text_field($_GET['wbf_invite']));
|
||||
$reg_mode_now = wbf_get_settings()['registration_mode'] ?? 'open';
|
||||
if ($reg_mode_now === 'invite'): ?>
|
||||
<div class="wbf-form-row">
|
||||
<input type="text" class="wbf-field-invite-code"
|
||||
placeholder="Einladungscode"
|
||||
value="<?php echo esc_attr($inv_code_prefill); ?>"
|
||||
style="text-transform:uppercase;letter-spacing:.1em;font-weight:700">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<!-- Spam: Honeypot (versteckt) + Zeitstempel -->
|
||||
<input type="text" name="wbf_website" class="wbf-hp-field" tabindex="-1" autocomplete="off" style="display:none!important;visibility:hidden;position:absolute;left:-9999px">
|
||||
<input type="hidden" class="wbf-field-form-time" value="<?php echo time(); ?>">
|
||||
<div class="wbf-form-row"><input type="text" class="wbf-field-reg-user" placeholder="Benutzername"></div>
|
||||
<div class="wbf-form-row"><input type="text" class="wbf-field-reg-name" placeholder="Anzeigename"></div>
|
||||
<div class="wbf-form-row"><input type="email" class="wbf-field-reg-email" placeholder="E-Mail"></div>
|
||||
<div class="wbf-form-row"><input type="password" class="wbf-field-reg-pass" placeholder="Passwort (min. 6 Zeichen)"></div>
|
||||
<?php
|
||||
$rules_required = ( wbf_get_settings()['rules_accept_required'] ?? '1' ) === '1';
|
||||
$rules_enabled = ( wbf_get_settings()['rules_enabled'] ?? '1' ) === '1';
|
||||
if ( $rules_enabled ):
|
||||
?>
|
||||
<label style="display:flex;align-items:center;gap:.5rem;font-size:.82rem;color:var(--c-text-dim);cursor:pointer;margin-bottom:.75rem;line-height:1.4;flex-wrap:wrap">
|
||||
<input type="checkbox" class="wbf-field-rules-accept"
|
||||
style="width:15px;height:15px;accent-color:var(--c-primary);cursor:pointer;flex-shrink:0"
|
||||
<?php echo $rules_required ? 'required' : ''; ?>>
|
||||
<span>Ich akzeptiere die <a href="<?php echo esc_url(wbf_get_forum_url().'?forum_rules=1'); ?>" target="_blank" style="color:var(--c-primary);font-weight:600;white-space:nowrap">Forum-Regeln</a><?php echo $rules_required ? ' <span style="color:var(--c-danger)">*</span>' : ''; ?></span>
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
<button class="wbf-btn wbf-btn--primary wbf-btn--full wbf-reg-submit-btn"><i class="fas fa-user-plus"></i> Konto erstellen</button>
|
||||
<span class="wbf-reg-msg wbf-msg"></span>
|
||||
</div>
|
||||
@@ -1322,6 +1762,58 @@ class WBF_Shortcodes {
|
||||
<?php
|
||||
}
|
||||
|
||||
// ── UMFRAGE-MODAL ─────────────────────────────────────────────────────────
|
||||
|
||||
private static function render_poll_modal( $thread_id ) { ?>
|
||||
<div class="wbf-modal" id="wbfPollModal">
|
||||
<div class="wbf-modal__box">
|
||||
<button class="wbf-modal__close" onclick="document.getElementById('wbfPollModal').classList.remove('active')">×</button>
|
||||
<h3 style="margin-bottom:1.25rem"><i class="fas fa-chart-bar" style="color:var(--c-primary)"></i> Umfrage erstellen</h3>
|
||||
<input type="hidden" id="wbfPollThreadId" value="<?php echo (int)$thread_id; ?>">
|
||||
|
||||
<div class="wbf-form-row">
|
||||
<label>Frage <span style="color:var(--c-danger)">*</span></label>
|
||||
<input type="text" id="wbfPollQuestion" placeholder="Was ist deine Meinung zu…?" maxlength="200">
|
||||
</div>
|
||||
|
||||
<div class="wbf-form-row">
|
||||
<label>Antwortmöglichkeiten <small>(min. 2, max. 10)</small></label>
|
||||
<div id="wbfPollOptions">
|
||||
<div class="wbf-poll-opt-row">
|
||||
<input type="text" class="wbf-poll-opt" placeholder="Option 1" maxlength="100">
|
||||
<button type="button" class="wbf-btn wbf-btn--sm" style="background:rgba(240,82,82,.1);color:var(--c-danger);border-color:rgba(240,82,82,.3);min-width:32px;flex-shrink:0" onclick="wbfRemovePollOpt(this)">✕</button>
|
||||
</div>
|
||||
<div class="wbf-poll-opt-row">
|
||||
<input type="text" class="wbf-poll-opt" placeholder="Option 2" maxlength="100">
|
||||
<button type="button" class="wbf-btn wbf-btn--sm" style="background:rgba(240,82,82,.1);color:var(--c-danger);border-color:rgba(240,82,82,.3);min-width:32px;flex-shrink:0" onclick="wbfRemovePollOpt(this)">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="wbfPollAddOpt" class="wbf-btn wbf-btn--sm" style="margin-top:.5rem">
|
||||
<i class="fas fa-plus"></i> Option hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:1.25rem;flex-wrap:wrap;align-items:flex-end;margin-bottom:1rem">
|
||||
<label style="display:flex;align-items:center;gap:.4rem;font-size:.82rem;color:var(--c-text-dim);cursor:pointer">
|
||||
<input type="checkbox" id="wbfPollMulti" style="accent-color:var(--c-primary);width:15px;height:15px">
|
||||
Mehrfachauswahl erlauben
|
||||
</label>
|
||||
<div class="wbf-form-row" style="margin:0;flex:1;min-width:180px">
|
||||
<label style="font-size:.72rem">Endet am <small style="color:var(--c-muted)">(optional)</small></label>
|
||||
<input type="datetime-local" id="wbfPollEndsAt" min="<?php echo date('Y-m-d\TH:i'); ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:.75rem;align-items:center">
|
||||
<button class="wbf-btn wbf-btn--primary" id="wbfSubmitPoll">
|
||||
<i class="fas fa-chart-bar"></i> Umfrage erstellen
|
||||
</button>
|
||||
<span class="wbf-msg" id="wbfPollMsg"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php }
|
||||
|
||||
private static function render_new_thread_modal( $categories, $current, $preselect = 0 ) {
|
||||
if (!$current) return;
|
||||
$parents = array_filter($categories, fn($c) => (int)$c->parent_id === 0);
|
||||
@@ -1331,7 +1823,7 @@ class WBF_Shortcodes {
|
||||
<div class="wbf-modal" id="wbfNewThreadModal">
|
||||
<div class="wbf-modal__box wbf-modal__box--lg">
|
||||
<button class="wbf-modal__close" onclick="document.getElementById('wbfNewThreadModal').classList.remove('active')">×</button>
|
||||
<h3 style="margin-bottom:1.2rem"><i class="fas fa-plus-circle"></i> Neuen Thread erstellen</h3>
|
||||
<h3 id="wbfModalTitle" style="margin-bottom:1.2rem"><i class="fas fa-plus-circle"></i> Neuen Thread erstellen</h3>
|
||||
<div class="wbf-form-row"><label>Kategorie</label>
|
||||
<select id="wbfThreadCat">
|
||||
<?php foreach ($parents as $p): ?>
|
||||
@@ -1344,13 +1836,28 @@ class WBF_Shortcodes {
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="wbf-form-row"><label>Titel</label><input type="text" id="wbfThreadTitle" placeholder="Titel deines Threads"></div>
|
||||
<div class="wbf-form-row">
|
||||
<div class="wbf-form-row"><label id="wbfTitleLabel">Titel</label><input type="text" id="wbfThreadTitle" placeholder="Titel deines Threads"></div>
|
||||
<?php $prefixes = WBF_DB::get_prefixes(); if (!empty($prefixes)): ?>
|
||||
<div class="wbf-form-row" id="wbfPrefixRow">
|
||||
<label>Präfix <small>(optional)</small></label>
|
||||
<select id="wbfThreadPrefix">
|
||||
<option value="">— Kein Präfix —</option>
|
||||
<?php foreach ($prefixes as $px): ?>
|
||||
<option value="<?php echo (int)$px->id; ?>"
|
||||
data-color="<?php echo esc_attr($px->color); ?>"
|
||||
data-bg="<?php echo esc_attr($px->bg_color); ?>">
|
||||
<?php echo esc_html($px->label); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="wbf-form-row" id="wbfContentRow">
|
||||
<label>Inhalt</label>
|
||||
<?php self::render_editor_toolbar('wbfThreadContent'); ?>
|
||||
<textarea id="wbfThreadContent" rows="7" placeholder="Was möchtest du besprechen?"></textarea>
|
||||
</div>
|
||||
<div class="wbf-form-row">
|
||||
<div class="wbf-form-row" id="wbfTagsRow">
|
||||
<label>Tags <small>(kommagetrennt, max. 10 — z.B. php, wordpress, tipps)</small></label>
|
||||
<div class="wbf-tag-input-wrap" id="wbfTagInputWrap">
|
||||
<div class="wbf-tag-pills" id="wbfTagPills"></div>
|
||||
@@ -1359,10 +1866,55 @@ class WBF_Shortcodes {
|
||||
<div class="wbf-tag-suggest" id="wbfTagSuggest" style="display:none"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:1rem;align-items:center">
|
||||
|
||||
<!-- Thread Submit Row -->
|
||||
<div id="wbfThreadSubmitRow" style="display:flex;gap:1rem;align-items:center;margin-top:.25rem">
|
||||
<button class="wbf-btn wbf-btn--primary" id="wbfSubmitThread"><i class="fas fa-paper-plane"></i> Thread erstellen</button>
|
||||
<button type="button" id="wbfShowPollSection" class="wbf-btn wbf-btn--sm wbf-btn--outline-poll">
|
||||
<i class="fas fa-chart-bar"></i> Umfrage hinzufügen
|
||||
</button>
|
||||
<span class="wbf-msg" id="wbfThreadMsg"></span>
|
||||
</div>
|
||||
|
||||
<!-- Poll Section -->
|
||||
<div id="wbfPollSection" style="margin-top:1.25rem;border-top:1px solid var(--c-border);padding-top:1.1rem;display:none">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:.9rem">
|
||||
<span style="font-size:.85rem;font-weight:700;color:#fbbf24"><i class="fas fa-chart-bar"></i> Umfrage</span>
|
||||
<button type="button" id="wbfRemovePollSection" class="wbf-btn wbf-btn--sm" style="background:rgba(240,82,82,.1);color:var(--c-danger);border-color:rgba(240,82,82,.3);padding:.25rem .6rem;font-size:.72rem"><i class="fas fa-xmark"></i> Entfernen</button>
|
||||
</div>
|
||||
<div class="wbf-form-row">
|
||||
<label>Frage <span style="color:var(--c-danger)">*</span></label>
|
||||
<input type="text" id="wbfNewThreadPollQuestion" placeholder="Was ist deine Meinung zu…?" maxlength="200">
|
||||
</div>
|
||||
<div class="wbf-form-row">
|
||||
<label>Antwortmöglichkeiten <small>(min. 2, max. 10)</small></label>
|
||||
<div id="wbfNewThreadPollOptions">
|
||||
<div class="wbf-poll-opt-row" style="display:flex;gap:.5rem;margin-bottom:.4rem">
|
||||
<input type="text" class="wbf-nt-poll-opt" placeholder="Option 1" maxlength="100">
|
||||
<button type="button" class="wbf-btn wbf-btn--sm" style="background:rgba(240,82,82,.1);color:var(--c-danger);border-color:rgba(240,82,82,.3);min-width:32px;flex-shrink:0" onclick="wbfRemoveNTPollOpt(this)">✕</button>
|
||||
</div>
|
||||
<div class="wbf-poll-opt-row" style="display:flex;gap:.5rem;margin-bottom:.4rem">
|
||||
<input type="text" class="wbf-nt-poll-opt" placeholder="Option 2" maxlength="100">
|
||||
<button type="button" class="wbf-btn wbf-btn--sm" style="background:rgba(240,82,82,.1);color:var(--c-danger);border-color:rgba(240,82,82,.3);min-width:32px;flex-shrink:0" onclick="wbfRemoveNTPollOpt(this)">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="wbfNTPollAddOpt" class="wbf-btn wbf-btn--sm" style="margin-top:.5rem">
|
||||
<i class="fas fa-plus"></i> Option hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
<label style="display:flex;align-items:center;gap:.4rem;font-size:.82rem;color:var(--c-text-dim);cursor:pointer;margin-bottom:.75rem">
|
||||
<input type="checkbox" id="wbfNTPollMulti" style="accent-color:var(--c-primary);width:15px;height:15px">
|
||||
Mehrfachauswahl erlauben
|
||||
</label>
|
||||
<div class="wbf-form-row" style="margin-bottom:1rem">
|
||||
<label style="font-size:.78rem;font-weight:600;color:var(--c-text-dim)">Endet am <small style="font-weight:400;color:var(--c-muted)">(optional)</small></label>
|
||||
<input type="datetime-local" id="wbfNTPollEndsAt" min="<?php echo date('Y-m-d\TH:i'); ?>" style="width:100%;box-sizing:border-box">
|
||||
</div>
|
||||
<div style="display:flex;gap:1rem;align-items:center">
|
||||
<button class="wbf-btn wbf-btn--outline-poll" id="wbfSubmitPollThread"><i class="fas fa-chart-bar"></i> Umfrage erstellen</button>
|
||||
<span class="wbf-msg" id="wbfPollThreadMsg"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php }
|
||||
@@ -1415,6 +1967,8 @@ class WBF_Shortcodes {
|
||||
|
||||
private static function view_members() {
|
||||
$current = WBF_Auth::get_current_user();
|
||||
$maint_m = wbf_get_settings()['maintenance_mode'] ?? '0';
|
||||
if ($maint_m === '1' && (!$current || WBF_Roles::level($current->role) < 50)) return self::view_maintenance();
|
||||
if ( ! $current ) {
|
||||
ob_start(); ?>
|
||||
<div class="wbf-wrap"><?php self::render_topbar(null); ?>
|
||||
@@ -1527,11 +2081,133 @@ class WBF_Shortcodes {
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
<?php self::render_forum_footer(); ?>
|
||||
<?php self::render_auth_modal(); ?>
|
||||
</div>
|
||||
<?php return ob_get_clean();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ── Wartungsmodus ─────────────────────────────────────────────────────────
|
||||
|
||||
private static function view_maintenance() {
|
||||
$s = wbf_get_settings();
|
||||
$title = esc_html($s['maintenance_title'] ?? 'Wartungsarbeiten');
|
||||
$msg = esc_html($s['maintenance_message'] ?? 'Das Forum wird gerade gewartet. Bitte versuche es später erneut.');
|
||||
ob_start(); ?>
|
||||
<div class="wbf-wrap">
|
||||
<div style="min-height:60vh;display:flex;align-items:center;justify-content:center;text-align:center;padding:2rem">
|
||||
<div style="max-width:480px">
|
||||
<div style="font-size:4rem;margin-bottom:1.5rem">🔧</div>
|
||||
<h1 style="font-size:1.6rem;font-weight:800;color:var(--c-text);margin-bottom:1rem"><?php echo $title; ?></h1>
|
||||
<p style="color:var(--c-text-dim);font-size:1rem;line-height:1.7"><?php echo $msg; ?></p>
|
||||
<p style="margin-top:2rem">
|
||||
<a href="#" class="wbf-login-link wbf-btn wbf-btn--sm wbf-btn--outline" style="font-size:.85rem">
|
||||
<i class="fas fa-lock"></i> Als Admin einloggen
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<?php self::render_auth_modal(); ?>
|
||||
</div>
|
||||
<?php return ob_get_clean();
|
||||
}
|
||||
|
||||
// ── FORUM-FOOTER ──────────────────────────────────────────────────────────
|
||||
|
||||
private static function render_forum_footer() {
|
||||
if ( ( wbf_get_settings()['rules_enabled'] ?? '1' ) !== '1' ) return;
|
||||
$rules_url = esc_url( wbf_get_forum_url() . '?forum_rules=1' );
|
||||
?>
|
||||
<div style="border-top:1px solid var(--c-border);margin-top:3rem;padding:1.25rem 1.5rem;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.75rem;background:var(--c-bg2)">
|
||||
<span style="font-size:.78rem;color:var(--c-muted)">
|
||||
<i class="fas fa-shield-halved" style="color:var(--c-primary);margin-right:.35rem"></i>
|
||||
Durch die Nutzung des Forums stimmst du unseren Regeln zu.
|
||||
</span>
|
||||
<a href="<?php echo $rules_url; ?>"
|
||||
style="display:inline-flex;align-items:center;gap:.4rem;font-size:.78rem;font-weight:700;color:#0d1117;background:var(--c-primary);padding:.35rem .9rem;border-radius:6px;text-decoration:none;transition:opacity .15s"
|
||||
onmouseover="this.style.opacity='.85'" onmouseout="this.style.opacity='1'">
|
||||
<i class="fas fa-scroll"></i> Forum-Regeln lesen
|
||||
</a>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// ── FORUM-REGELN ──────────────────────────────────────────────────────────
|
||||
|
||||
private static function view_rules() {
|
||||
$current = WBF_Auth::get_current_user();
|
||||
$s = wbf_get_settings();
|
||||
if ( ( $s['rules_enabled'] ?? '1' ) !== '1' ) {
|
||||
return '<p class="wbf-notice">Diese Seite ist nicht verfügbar.</p>';
|
||||
}
|
||||
$title = esc_html( $s['rules_title'] ?? 'Forum-Regeln & Nutzungsbedingungen' );
|
||||
$raw = $s['rules_content'] ?? '';
|
||||
ob_start(); ?>
|
||||
<div class="wbf-wrap">
|
||||
<?php self::render_topbar($current); ?>
|
||||
<div class="wbf-container wbf-mt" style="max-width:820px">
|
||||
<nav class="wbf-breadcrumb">
|
||||
<a href="<?php echo esc_url(wbf_get_forum_url()); ?>"><i class="fas fa-home"></i> Forum</a>
|
||||
<span>/</span><span><i class="fas fa-shield-halved"></i> Regeln</span>
|
||||
</nav>
|
||||
|
||||
<div style="background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--radius);overflow:hidden;margin-bottom:2rem">
|
||||
<div style="background:linear-gradient(135deg,rgba(0,180,216,.12),rgba(99,102,241,.08));border-bottom:1px solid var(--c-border);padding:1.75rem 2rem;display:flex;align-items:center;gap:1rem">
|
||||
<div style="width:48px;height:48px;border-radius:12px;background:rgba(0,180,216,.15);border:1px solid rgba(0,180,216,.25);display:flex;align-items:center;justify-content:center;font-size:1.3rem;flex-shrink:0">📜</div>
|
||||
<div>
|
||||
<h1 style="font-size:1.3rem;font-weight:800;color:var(--c-text);margin:0 0 .2rem"><?php echo $title; ?></h1>
|
||||
<p style="font-size:.8rem;color:var(--c-muted);margin:0">
|
||||
<i class="fas fa-clock"></i> Zuletzt aktualisiert: <?php echo date_i18n('d. F Y'); ?>
|
||||
· <i class="fas fa-eye"></i> Bitte sorgfältig lesen
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wbf-rules-body" style="padding:2rem">
|
||||
<?php echo self::render_rules_content( $raw ); ?>
|
||||
</div>
|
||||
<div style="border-top:1px solid var(--c-border);padding:1.25rem 2rem;background:rgba(0,0,0,.12);display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.75rem">
|
||||
<span style="font-size:.82rem;color:var(--c-muted)">
|
||||
<i class="fas fa-info-circle"></i> Mit der Nutzung des Forums stimmst du diesen Regeln zu.
|
||||
</span>
|
||||
<a href="<?php echo esc_url(wbf_get_forum_url()); ?>" class="wbf-btn wbf-btn--sm wbf-btn--primary" style="color:#0d1117;font-weight:700">
|
||||
<i class="fas fa-arrow-left"></i> Zurück zum Forum
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php return ob_get_clean();
|
||||
}
|
||||
|
||||
private static function render_rules_content( $raw ) {
|
||||
if ( empty( $raw ) ) {
|
||||
return '<p style="color:var(--c-muted)">Noch keine Regeln hinterlegt.</p>';
|
||||
}
|
||||
$paragraphs = preg_split( '/\n{2,}/', trim( $raw ) );
|
||||
$out = '';
|
||||
foreach ( $paragraphs as $para ) {
|
||||
$para = trim( $para );
|
||||
if ( $para === '' ) continue;
|
||||
if ( preg_match( '/^\*\*(\d+\.\s*.+?)\*\*$/', $para, $m ) ) {
|
||||
preg_match('/^(\d+)\.\s*(.+)$/', $m[1], $parts);
|
||||
$num = esc_html($parts[1] ?? '');
|
||||
$text = esc_html($parts[2] ?? $m[1]);
|
||||
$out .= '<div class="wbf-rules-section">'
|
||||
. '<span class="wbf-rules-num">' . $num . '</span>'
|
||||
. '<h2 class="wbf-rules-heading">' . $text . '</h2>'
|
||||
. '</div>';
|
||||
} else {
|
||||
$para = htmlspecialchars( $para, ENT_QUOTES, 'UTF-8' );
|
||||
$para = preg_replace( '/\*\*(.+?)\*\*/s', '<strong>$1</strong>', $para );
|
||||
$para = nl2br( $para );
|
||||
$out .= '<p class="wbf-rules-para">' . $para . '</p>';
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WBF_Shortcodes::init();
|
||||
Reference in New Issue
Block a user