Update from Git Manager GUI

This commit is contained in:
2026-03-22 00:40:18 +01:00
parent ead2f3a62a
commit 65d2371239
5 changed files with 1912 additions and 173 deletions

View File

@@ -18,6 +18,9 @@ class WBF_Ajax {
'wbf_create_poll',
'wbf_toggle_bookmark',
'wbf_set_thread_prefix',
'wbf_toggle_ignore',
'wbf_change_email',
'wbf_save_notification_prefs',
];
foreach ($actions as $action) {
add_action('wp_ajax_nopriv_' . $action, [__CLASS__, str_replace('wbf_','handle_',$action)]);
@@ -411,9 +414,37 @@ class WBF_Ajax {
if (empty($_FILES['avatar'])) wp_send_json_error(['message'=>'Keine Datei.']);
$allowed_types = ['image/jpeg','image/png','image/gif','image/webp'];
$mime = $_FILES['avatar']['type'] ?? '';
if (!in_array($mime, $allowed_types)) wp_send_json_error(['message'=>'Nur JPG, PNG, GIF und WebP erlaubt.']);
if ($_FILES['avatar']['size'] > 2 * 1024 * 1024) wp_send_json_error(['message'=>'Maximale Dateigröße: 2 MB.']);
// Dateigröße vor dem MIME-Check prüfen
if ( $_FILES['avatar']['size'] > 2 * 1024 * 1024 ) {
wp_send_json_error(['message'=>'Maximale Dateigröße: 2 MB.']);
}
// Server-seitige MIME-Typ-Prüfung — $_FILES['type'] kommt vom Client
// und ist beliebig fälschbar (z.B. PHP-Datei als image/jpeg getarnt).
// finfo_file() liest den echten Magic-Byte der temporären Datei.
$tmp = $_FILES['avatar']['tmp_name'] ?? '';
if ( ! $tmp || ! is_uploaded_file( $tmp ) ) {
wp_send_json_error(['message'=>'Ungültiger Datei-Upload.']);
}
if ( function_exists('finfo_open') ) {
$finfo = finfo_open( FILEINFO_MIME_TYPE );
$real_mime = finfo_file( $finfo, $tmp );
finfo_close( $finfo );
} else {
// Fallback: exif_imagetype() wenn finfo nicht verfügbar
$et_map = [
IMAGETYPE_JPEG => 'image/jpeg',
IMAGETYPE_PNG => 'image/png',
IMAGETYPE_GIF => 'image/gif',
IMAGETYPE_WEBP => 'image/webp',
];
$et = @exif_imagetype( $tmp );
$real_mime = $et_map[$et] ?? '';
}
if ( ! in_array( $real_mime, $allowed_types, true ) ) {
wp_send_json_error(['message'=>'Nur JPG, PNG, GIF und WebP erlaubt.']);
}
require_once ABSPATH . 'wp-admin/includes/image.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
@@ -468,16 +499,36 @@ class WBF_Ajax {
// Nur Bilder erlauben
$allowed_types = ['image/jpeg','image/png','image/gif','image/webp'];
$mime = $_FILES['image']['type'] ?? '';
if ( ! in_array($mime, $allowed_types) ) {
wp_send_json_error(['message' => 'Nur JPG, PNG, GIF und WebP erlaubt.']);
}
// Max 5 MB
// Max 5 MB — Größe zuerst prüfen bevor teure MIME-Erkennung läuft
if ( $_FILES['image']['size'] > 5 * 1024 * 1024 ) {
wp_send_json_error(['message' => 'Maximale Dateigröße: 5 MB.']);
}
// Server-seitige MIME-Typ-Prüfung — $_FILES['type'] ist client-kontrolliert
// und kann beliebig auf 'image/jpeg' gesetzt werden, auch für .php-Dateien.
$tmp = $_FILES['image']['tmp_name'] ?? '';
if ( ! $tmp || ! is_uploaded_file( $tmp ) ) {
wp_send_json_error(['message' => 'Ungültiger Datei-Upload.']);
}
if ( function_exists('finfo_open') ) {
$finfo = finfo_open( FILEINFO_MIME_TYPE );
$real_mime = finfo_file( $finfo, $tmp );
finfo_close( $finfo );
} else {
$et_map = [
IMAGETYPE_JPEG => 'image/jpeg',
IMAGETYPE_PNG => 'image/png',
IMAGETYPE_GIF => 'image/gif',
IMAGETYPE_WEBP => 'image/webp',
];
$et = @exif_imagetype( $tmp );
$real_mime = $et_map[$et] ?? '';
}
if ( ! in_array( $real_mime, $allowed_types, true ) ) {
wp_send_json_error(['message' => 'Nur JPG, PNG, GIF und WebP erlaubt.']);
}
require_once ABSPATH . 'wp-admin/includes/image.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
@@ -588,6 +639,18 @@ class WBF_Ajax {
$is_mod = WBF_DB::can($user, 'delete_post');
if ( ! $is_own && ! $is_mod ) wp_send_json_error(['message' => 'Keine Berechtigung.']);
// Post-Bearbeitungslimit prüfen — gilt auch für Thread-Erstbeiträge
// (spiegelt identisches Verhalten zu handle_edit_post() wider)
if ( $is_own && ! $is_mod ) {
$limit_min = (int)( wbf_get_settings()['post_edit_limit'] ?? 30 );
if ( $limit_min > 0 ) {
$age_min = ( time() - strtotime( $thread->created_at ) ) / 60;
if ( $age_min > $limit_min ) {
wp_send_json_error(['message' => "Bearbeitung nur innerhalb von {$limit_min} Minuten nach dem Erstellen möglich."]);
}
}
}
global $wpdb;
$wpdb->update(
"{$wpdb->prefix}forum_threads",
@@ -682,6 +745,11 @@ class WBF_Ajax {
if ($to_id === (int)$user->id) wp_send_json_error(['message'=>'Du kannst dir nicht selbst schreiben.']);
if (!WBF_DB::get_user($to_id)) wp_send_json_error(['message'=>'Empfänger nicht gefunden.']);
// DM-Blockierung: Empfänger hat Sender ignoriert
if ( WBF_DB::is_ignored( $to_id, $user->id ) ) {
wp_send_json_error(['message' => 'Diese Person akzeptiert keine Nachrichten von dir.']);
}
$id = WBF_DB::send_message($user->id, $to_id, $content);
// Notify recipient
WBF_DB::create_notification($to_id, 'message', $id, $user->id);
@@ -739,14 +807,22 @@ class WBF_Ajax {
// ── User-Autocomplete (für @Erwähnungen + DM) ─────────────────────────────
public static function handle_user_suggest() {
// Nur eingeloggte Nutzer dürfen die User-Suche nutzen
// (verhindert Enumeration aller Usernamen + Rollendaten durch Gäste)
if ( ! WBF_Auth::is_forum_logged_in() ) {
wp_send_json_success(['users'=>[]]);
}
$q = sanitize_text_field($_POST['q'] ?? $_GET['q'] ?? '');
if (mb_strlen($q) < 1) wp_send_json_success(['users'=>[]]);
global $wpdb;
$like = $wpdb->esc_like($q) . '%';
// Rolle wird bewusst NICHT zurückgegeben — nicht für Autocomplete nötig
// und verhindert Informationsleck über Rollen-Verteilung im Forum.
$users = $wpdb->get_results($wpdb->prepare(
"SELECT id, username, display_name, avatar_url, role
"SELECT id, username, display_name, avatar_url
FROM {$wpdb->prefix}forum_users
WHERE username LIKE %s OR display_name LIKE %s
WHERE (username LIKE %s OR display_name LIKE %s)
AND role != 'banned'
ORDER BY display_name ASC LIMIT 8",
$like, $like
));
@@ -817,6 +893,13 @@ class WBF_Ajax {
private static function send_notification_email( $to_user, $type, $actor_name, $extra = [] ) {
if ( ! $to_user || empty($to_user->email) ) return;
// Prüfen ob der User diesen Benachrichtigungstyp aktiviert hat
// Standard: alle aktiviert (1). User kann im Profil deaktivieren (0).
$pref_key = 'notify_' . $type; // notify_reply, notify_mention, notify_message
$meta = WBF_DB::get_user_meta( $to_user->id );
// Nur deaktivieren wenn explizit auf '0' gesetzt — Standard ist aktiviert
if ( isset($meta[$pref_key]) && $meta[$pref_key] === '0' ) return;
$blog_name = get_bloginfo('name');
$forum_url = wbf_get_forum_url();
$from_email = get_option('admin_email');
@@ -897,6 +980,17 @@ class WBF_Ajax {
$email = sanitize_email( $_POST['email'] ?? '' );
if ( ! is_email($email) ) wp_send_json_error(['message'=>'Ungültige E-Mail-Adresse.']);
// ── Rate-Limiting: max. 1 Reset-Mail pro E-Mail-Adresse alle 15 Minuten ──
// Verhindert, dass ein Angreifer tausende Reset-Mails pro Sekunde
// für beliebige Adressen triggert und den Mail-Server überlastet.
$rate_key = 'wbf_pwreset_' . md5( strtolower( $email ) );
if ( get_transient( $rate_key ) !== false ) {
// Immer Erfolg melden — kein Leak ob Rate-Limit oder kein Account
wp_send_json_success(['message'=>'Falls diese E-Mail registriert ist, wurde eine E-Mail gesendet.']);
}
// Cooldown setzen — 15 Minuten
set_transient( $rate_key, 1, 15 * MINUTE_IN_SECONDS );
$user = WBF_DB::get_user_by('email', $email);
// Immer Erfolg melden (kein User-Enumeration)
if ( ! $user ) {
@@ -922,7 +1016,13 @@ class WBF_Ajax {
}
public static function handle_reset_password() {
self::verify();
// Kein self::verify() hier — Gäste haben keine Forum-Session.
// Das Reset-Token selbst authentifiziert die Anfrage.
// Wir prüfen trotzdem den WP-Nonce als CSRF-Schutz; dieser wird
// von wp_localize_script für alle Besucher (auch Gäste) generiert.
if ( ! check_ajax_referer( 'wbf_nonce', 'nonce', false ) ) {
wp_send_json_error(['message' => 'Sicherheitsfehler.']);
}
$token = sanitize_text_field( $_POST['token'] ?? '' );
$password = $_POST['password'] ?? '';
$password2= $_POST['password2'] ?? '';
@@ -1207,6 +1307,90 @@ class WBF_Ajax {
wp_send_json_success(['prefix' => $prefix]);
}
// ── E-Mail-Adresse ändern ─────────────────────────────────────────────────
public static function handle_change_email() {
self::verify();
$user = WBF_Auth::get_current_user();
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
$new_email = sanitize_email( $_POST['new_email'] ?? '' );
$password = $_POST['password'] ?? '';
if ( ! is_email($new_email) ) {
wp_send_json_error(['message' => 'Ungültige E-Mail-Adresse.']);
}
if ( empty($password) ) {
wp_send_json_error(['message' => 'Bitte aktuelles Passwort zur Bestätigung eingeben.']);
}
if ( ! password_verify($password, $user->password) ) {
wp_send_json_error(['message' => 'Falsches Passwort.']);
}
if ( strtolower($new_email) === strtolower($user->email) ) {
wp_send_json_error(['message' => 'Das ist bereits deine aktuelle E-Mail-Adresse.']);
}
// Prüfen ob E-Mail bereits vergeben
$existing = WBF_DB::get_user_by('email', $new_email);
if ( $existing && (int)$existing->id !== (int)$user->id ) {
wp_send_json_error(['message' => 'Diese E-Mail-Adresse ist bereits registriert.']);
}
WBF_DB::update_user($user->id, ['email' => $new_email]);
wp_send_json_success(['message' => 'E-Mail-Adresse erfolgreich geändert.']);
}
// ── Benachrichtigungs-Einstellungen speichern ─────────────────────────────
public static function handle_save_notification_prefs() {
self::verify();
$user = WBF_Auth::get_current_user();
if ( ! $user ) wp_send_json_error(['message' => 'Nicht eingeloggt.']);
$allowed = ['notify_reply', 'notify_mention', 'notify_message'];
foreach ( $allowed as $key ) {
// 1 wenn Checkbox aktiviert, 0 wenn deaktiviert
$val = isset($_POST[$key]) && $_POST[$key] === '1' ? '1' : '0';
WBF_DB::set_user_meta($user->id, $key, $val);
}
wp_send_json_success(['message' => 'Benachrichtigungs-Einstellungen gespeichert.']);
}
// ── User ignorieren / Ignorierung aufheben ────────────────────────────────
public static function handle_toggle_ignore() {
self::verify();
$user = WBF_Auth::get_current_user();
if ( ! $user ) wp_send_json_error( ['message' => 'Nicht eingeloggt.'] );
$ignored_id = (int)( $_POST['ignored_id'] ?? 0 );
if ( ! $ignored_id ) wp_send_json_error( ['message' => 'Ungültiger Nutzer.'] );
if ( $ignored_id === (int)$user->id ) {
wp_send_json_error( ['message' => 'Du kannst dich nicht selbst ignorieren.'] );
}
$target = WBF_DB::get_user( $ignored_id );
if ( ! $target ) wp_send_json_error( ['message' => 'Nutzer nicht gefunden.'] );
// Prüfen ob diese Rolle geblockt werden darf (konfigurierbar in den Einstellungen)
if ( ! wbf_can_be_ignored( $target ) ) {
$role_label = WBF_Roles::get($target->role)['label'] ?? $target->role;
wp_send_json_error( ['message' => 'Nutzer mit der Rolle "' . $role_label . '" können nicht ignoriert werden.'] );
}
$now_ignored = WBF_DB::toggle_ignore( $user->id, $ignored_id );
wp_send_json_success( [
'ignored' => $now_ignored,
'ignored_id' => $ignored_id,
'display_name' => $target->display_name,
'message' => $now_ignored
? esc_html( $target->display_name ) . ' wird jetzt ignoriert.'
: 'Ignorierung von ' . esc_html( $target->display_name ) . ' aufgehoben.',
] );
}
}
add_action( 'init', [ 'WBF_Ajax', 'init' ] );

View File

@@ -279,6 +279,18 @@ class WBF_DB {
) $charset;";
dbDelta( $sql_bookmarks );
// ── Ignore-Liste ──────────────────────────────────────────────────────
$sql_ignore = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}forum_ignore_list (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
ignored_id BIGINT UNSIGNED NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY user_ignored (user_id, ignored_id),
KEY ignored_id (ignored_id)
) $charset;";
dbDelta( $sql_ignore );
// ── 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" );
@@ -623,7 +635,8 @@ class WBF_DB {
FROM {$wpdb->prefix}forum_threads t
JOIN {$wpdb->prefix}forum_users u ON u.id = t.user_id
JOIN {$wpdb->prefix}forum_categories c ON c.id = t.category_id
AND t.status != 'archived' ORDER BY t.created_at DESC LIMIT %d", $limit
WHERE t.status != 'archived' AND t.deleted_at IS NULL
ORDER BY t.created_at DESC LIMIT %d", $limit
));
}
@@ -1207,13 +1220,18 @@ class WBF_DB {
global $wpdb;
$token = bin2hex( random_bytes(32) );
$hash = hash( 'sha256', $token );
// Alte Tokens löschen
$wpdb->delete( "{$wpdb->prefix}forum_users", [] ); // nur placeholder
// Altes Token dieses Users zurücksetzen bevor ein neues gesetzt wird
$wpdb->query( $wpdb->prepare(
"UPDATE {$wpdb->prefix}forum_users
SET reset_token=NULL, reset_token_expires=NULL
WHERE id=%d",
(int) $user_id
) );
$wpdb->query( $wpdb->prepare(
"UPDATE {$wpdb->prefix}forum_users
SET reset_token=%s, reset_token_expires=DATE_ADD(NOW(), INTERVAL 1 HOUR)
WHERE id=%d",
$hash, $user_id
$hash, (int) $user_id
) );
return $token; // Klartext-Token → per E-Mail senden
}
@@ -1689,6 +1707,128 @@ class WBF_DB {
));
}
// ── Ignore-Liste ──────────────────────────────────────────────────────────
public static function toggle_ignore( $user_id, $ignored_id ) {
global $wpdb;
$user_id = (int) $user_id;
$ignored_id = (int) $ignored_id;
if ( self::is_ignored( $user_id, $ignored_id ) ) {
$wpdb->delete( "{$wpdb->prefix}forum_ignore_list", [
'user_id' => $user_id,
'ignored_id' => $ignored_id,
] );
return false;
}
$wpdb->replace( "{$wpdb->prefix}forum_ignore_list", [
'user_id' => $user_id,
'ignored_id' => $ignored_id,
] );
return true;
}
public static function is_ignored( $user_id, $ignored_id ) {
global $wpdb;
$table = "{$wpdb->prefix}forum_ignore_list";
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) !== $table ) return false;
return (bool) $wpdb->get_var( $wpdb->prepare(
"SELECT id FROM {$wpdb->prefix}forum_ignore_list WHERE user_id=%d AND ignored_id=%d",
(int) $user_id, (int) $ignored_id
) );
}
/** Gibt alle ignorierten User-IDs als int-Array zurück */
public static function get_ignored_ids( $user_id ) {
global $wpdb;
$table = "{$wpdb->prefix}forum_ignore_list";
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) !== $table ) return [];
$ids = $wpdb->get_col( $wpdb->prepare(
"SELECT ignored_id FROM {$wpdb->prefix}forum_ignore_list WHERE user_id=%d",
(int) $user_id
) );
return array_map( 'intval', $ids );
}
/** Vollständige Ignore-Liste mit User-Daten */
public static function get_ignore_list( $user_id ) {
global $wpdb;
$table = "{$wpdb->prefix}forum_ignore_list";
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) !== $table ) return [];
return $wpdb->get_results( $wpdb->prepare(
"SELECT u.id, u.username, u.display_name, u.avatar_url, u.role,
il.created_at AS ignored_since
FROM {$wpdb->prefix}forum_ignore_list il
JOIN {$wpdb->prefix}forum_users u ON u.id = il.ignored_id
WHERE il.user_id = %d
ORDER BY il.created_at DESC",
(int) $user_id
) );
}
// ── DSGVO Art. 17: Konto vollständig löschen ──────────────────────────────
public static function delete_user_gdpr( $user_id ) {
global $wpdb;
$user_id = (int) $user_id;
$user = self::get_user( $user_id );
if ( ! $user ) return false;
if ( $user->role === 'superadmin' ) return false;
$wpdb->delete( "{$wpdb->prefix}forum_messages", [ 'from_id' => $user_id ] );
$wpdb->delete( "{$wpdb->prefix}forum_messages", [ 'to_id' => $user_id ] );
$wpdb->delete( "{$wpdb->prefix}forum_remember_tokens", [ 'user_id' => $user_id ] );
$wpdb->delete( "{$wpdb->prefix}forum_notifications", [ 'user_id' => $user_id ] );
$wpdb->delete( "{$wpdb->prefix}forum_notifications", [ 'actor_id' => $user_id ] );
$wpdb->delete( "{$wpdb->prefix}forum_subscriptions", [ 'user_id' => $user_id ] );
$table_bm = "{$wpdb->prefix}forum_bookmarks";
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_bm'" ) === $table_bm ) {
$wpdb->delete( $table_bm, [ 'user_id' => $user_id ] );
}
$wpdb->delete( "{$wpdb->prefix}forum_likes", [ 'user_id' => $user_id ] );
$wpdb->delete( "{$wpdb->prefix}forum_reactions", [ 'user_id' => $user_id ] );
$wpdb->delete( "{$wpdb->prefix}forum_reports", [ 'reporter_id' => $user_id ] );
$table_pv = "{$wpdb->prefix}forum_poll_votes";
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_pv'" ) === $table_pv ) {
$wpdb->delete( $table_pv, [ 'user_id' => $user_id ] );
}
// Ignore-Liste beidseitig bereinigen
$table_il = "{$wpdb->prefix}forum_ignore_list";
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_il'" ) === $table_il ) {
$wpdb->delete( $table_il, [ 'user_id' => $user_id ] );
$wpdb->delete( $table_il, [ 'ignored_id' => $user_id ] );
}
delete_transient( 'wbf_flood_' . $user_id );
delete_transient( 'wbf_flood_ts_' . $user_id );
self::delete_user_meta_all( $user_id );
$anon_hash = substr( hash( 'sha256', $user_id . wp_salt() . microtime( true ) ), 0, 12 );
$wpdb->update(
"{$wpdb->prefix}forum_users",
[
'username' => 'deleted_' . $anon_hash,
'email' => 'deleted_' . $anon_hash . '@deleted.invalid',
'password' => '',
'display_name' => 'Gelöschter Nutzer',
'avatar_url' => '',
'bio' => '',
'signature' => '',
'ban_reason' => '',
'reset_token' => null,
'reset_token_expires' => null,
'pre_ban_role' => '',
'ban_until' => null,
'role' => 'banned',
],
[ 'id' => $user_id ]
);
return true;
}
// ── Wortfilter ────────────────────────────────────────────────────────────
public static function get_word_filter() {
@@ -1711,25 +1851,29 @@ class WBF_DB {
// ── Flood Control ─────────────────────────────────────────────────────────
public static function check_flood( $user_id ) {
$user_id = (int) $user_id;
if ( $user_id <= 0 ) return true; // kein eingeloggter User — kein Flood-Check
$interval = (int)( wbf_get_settings()['flood_interval'] ?? 0 );
if ( $interval <= 0 ) return true; // deaktiviert
$key = 'wbf_flood_' . (int)$user_id;
$ts_key = 'wbf_flood_ts_' . (int)$user_id;
$last = get_transient( $key );
if ( $last !== false ) {
return false; // noch gesperrt
}
set_transient( $key, time(), $interval );
set_transient( $key, 1, $interval );
set_transient( $ts_key, time(), $interval + 5 );
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;
$ts_key = 'wbf_flood_ts_' . (int)$user_id;
$sent = get_transient( $ts_key );
if ( $sent === false ) return 0;
$remaining = $interval - ( time() - (int)$sent );
return max( 0, $remaining );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ class WBF_Roles {
private static function default_roles() {
return [
'superadmin' => [
'label' => 'Superadmin',
'label' => 'Admin',
'level' => 100,
'color' => '#e11d48',
'bg_color' => 'rgba(225,29,72,.15)',

View File

@@ -341,9 +341,9 @@ class WBF_Shortcodes {
<?php endif; ?>
</aside>
</div>
<?php self::render_forum_footer(); ?>
</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();
@@ -500,9 +500,9 @@ class WBF_Shortcodes {
</div>
</div>
<?php endif; endif; ?>
<?php self::render_forum_footer(); ?>
<?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();
@@ -731,6 +731,23 @@ class WBF_Shortcodes {
<i class="fas fa-pen"></i> Bearbeiten
</button>
<?php endif; ?>
<?php
// Ignore-Button: nur wenn der Thread-Autor nicht der eingeloggte User ist
// und die Rolle blockiert werden darf (konfigurierbar in Einstellungen)
$op_author = WBF_DB::get_user((int)$thread->user_id);
if ($current && (int)$current->id !== (int)$thread->user_id && wbf_can_be_ignored($op_author)):
$op_is_ignored = WBF_DB::is_ignored($current->id, (int)$thread->user_id);
?>
<button class="wbf-ignore-btn"
data-id="<?php echo (int)$thread->user_id; ?>"
data-name="<?php echo esc_attr($thread->display_name); ?>"
data-ignored="<?php echo $op_is_ignored ? '1' : '0'; ?>"
title="<?php echo $op_is_ignored ? 'Ignorierung aufheben' : 'Nutzer ignorieren'; ?>"
style="background:none;border:none;cursor:pointer;color:var(--c-muted,#94a3b8);padding:2px 6px;border-radius:4px;font-size:.82rem">
<i class="fas fa-<?php echo $op_is_ignored ? 'eye' : 'eye-slash'; ?>"></i>
<?php echo $op_is_ignored ? 'Entblocken' : 'Ignorieren'; ?>
</button>
<?php endif; ?>
</div>
</div>
</div>
@@ -774,8 +791,8 @@ class WBF_Shortcodes {
<?php else: ?>
<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(); ?>
</div>
<?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; ?>
@@ -843,6 +860,22 @@ class WBF_Shortcodes {
<i class="fas fa-pen"></i> Bearbeiten
</button>
<?php endif; ?>
<?php
// Ignore-Button im Post-Footer
$post_author = WBF_DB::get_user((int)$post->user_id);
if ($current && (int)$current->id !== (int)$post->user_id && wbf_can_be_ignored($post_author)):
$post_is_ignored = WBF_DB::is_ignored($current->id, (int)$post->user_id);
?>
<button class="wbf-ignore-btn"
data-id="<?php echo (int)$post->user_id; ?>"
data-name="<?php echo esc_attr($post->display_name); ?>"
data-ignored="<?php echo $post_is_ignored ? '1' : '0'; ?>"
title="<?php echo $post_is_ignored ? 'Ignorierung aufheben' : 'Nutzer ignorieren'; ?>"
style="background:none;border:none;cursor:pointer;color:var(--c-muted,#94a3b8);padding:2px 6px;border-radius:4px;font-size:.82rem">
<i class="fas fa-<?php echo $post_is_ignored ? 'eye' : 'eye-slash'; ?>"></i>
<?php echo $post_is_ignored ? 'Entblocken' : 'Ignorieren'; ?>
</button>
<?php endif; ?>
<?php echo self::mod_tools_post($post->id,$current); ?>
</div>
</div>
@@ -871,13 +904,22 @@ class WBF_Shortcodes {
<?php return ob_get_clean();
}
$user_posts = WBF_DB::get_user_posts( $profile->id, 50 );
$bookmarks = $is_own ? WBF_DB::get_user_bookmarks($current->id, 50) : [];
$ignore_list = $is_own ? WBF_DB::get_ignore_list($current->id) : [];
$cf_defs = WBF_DB::get_profile_field_defs();
$cf_vals = WBF_DB::get_user_meta( $profile->id );
// Aktiven Tab aus URL lesen (tab=1|2|3), Standard: 1 für eigenes, 2 für fremdes
$active_tab = (int)($_GET['ptab'] ?? ($is_own ? 1 : 2));
$active_tab = in_array($active_tab, [1,2,3]) ? $active_tab : ($is_own ? 1 : 2);
// Tab 1 + 3 nur für eigenes Profil
if (!$is_own && $active_tab !== 2) $active_tab = 2;
ob_start(); ?>
<div class="wbf-wrap">
<?php self::render_topbar($current); ?>
<div class="wbf-container wbf-mt">
<nav class="wbf-breadcrumb">
<a href="<?php echo esc_url(remove_query_arg('forum_profile')); ?>"><i class="fas fa-home"></i> Forum</a>
<a href="<?php echo esc_url(remove_query_arg(['forum_profile', 'ptab'])); ?>"><i class="fas fa-home"></i> Forum</a>
<span>/</span><span>Profil</span>
</nav>
@@ -885,8 +927,6 @@ class WBF_Shortcodes {
<!-- ── SIDEBAR ─────────────────────────────────────────── -->
<aside class="wbf-profile-sidebar">
<!-- Avatar -->
<div class="wbf-profile-sidebar__avatar-wrap">
<img src="<?php echo esc_url($profile->avatar_url); ?>"
alt="<?php echo esc_attr($profile->display_name); ?>"
@@ -898,15 +938,24 @@ class WBF_Shortcodes {
</label>
<?php endif; ?>
</div>
<!-- Name + Badge + Username -->
<div class="wbf-profile-sidebar__identity">
<h2><?php echo esc_html($profile->display_name); ?></h2>
<?php echo self::role_badge($profile->role); ?>
<span class="wbf-profile-sidebar__username">@<?php echo esc_html($profile->username); ?></span>
<?php
$profile_online = WBF_DB::is_online($profile->id, 15);
if ($profile_online): ?>
<span class="wbf-profile-online-badge">
<span class="wbf-profile-online-dot"></span> Online
</span>
<?php else:
$last = $profile->last_active ?? null;
if ($last && $last !== '0000-00-00 00:00:00'): ?>
<span class="wbf-profile-lastseen">
<i class="fas fa-clock"></i> Zuletzt aktiv: <?php echo self::time_ago($last); ?>
</span>
<?php endif; endif; ?>
</div>
<!-- Stats -->
<div class="wbf-profile-sidebar__stats">
<div class="wbf-profile-sidebar__stat">
<span><?php echo (int)$profile->post_count; ?></span>
@@ -917,39 +966,26 @@ class WBF_Shortcodes {
<em>Dabei seit</em>
</div>
</div>
<!-- Level-Fortschritt -->
<?php $level_bar = WBF_Levels::progress_bar((int)$profile->post_count); if ($level_bar): ?>
<div class="wbf-profile-sidebar__section">
<?php echo $level_bar; ?>
</div>
<div class="wbf-profile-sidebar__section"><?php echo $level_bar; ?></div>
<?php endif; ?>
<!-- Bio -->
<?php if (!empty($profile->bio)): ?>
<div class="wbf-profile-sidebar__section">
<span class="wbf-profile-sidebar__section-label"><i class="fas fa-align-left"></i> Bio</span>
<p><?php echo nl2br(esc_html($profile->bio)); ?></p>
</div>
<?php endif; ?>
<!-- Signatur -->
<?php if (!empty($profile->signature)): ?>
<div class="wbf-profile-sidebar__section">
<span class="wbf-profile-sidebar__section-label"><i class="fas fa-pen-nib"></i> Signatur</span>
<p class="wbf-profile-sidebar__sig"><?php echo nl2br(esc_html($profile->signature)); ?></p>
</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 ):
<!-- Öffentliche Custom Fields -->
<?php foreach ($cf_defs as $def):
if (!$is_own && empty($def['public'])) continue;
$val = trim( $cf_vals_pub[ $def['key'] ] ?? '' );
if ( $val === '' ) continue;
?>
$val = trim($cf_vals[$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>
@@ -967,15 +1003,57 @@ class WBF_Shortcodes {
<?php endif; ?>
</div>
<?php endforeach; ?>
</aside>
<!-- ── MAIN ────────────────────────────────────────────── -->
<div class="wbf-profile-main">
<!-- Profil bearbeiten (nur eigenes) -->
<!-- DM-Button + Ignorieren-Button (nur auf fremden Profilen) -->
<?php if ($current && !$is_own && WBF_Roles::level($profile->role) >= 0): ?>
<div style="display:flex;justify-content:flex-end;gap:.5rem;margin-bottom:.75rem;flex-wrap:wrap">
<a href="?forum_dm=inbox&with=<?php echo (int)$profile->id; ?>"
class="wbf-btn wbf-btn--sm wbf-btn--primary">
<i class="fas fa-envelope"></i> Nachricht senden
</a>
<?php if ( wbf_can_be_ignored($profile) ):
$viewer_ignores = WBF_DB::is_ignored($current->id, $profile->id); ?>
<button class="wbf-ignore-btn wbf-btn wbf-btn--sm<?php echo $viewer_ignores?' wbf-btn--primary':''; ?>"
data-id="<?php echo (int)$profile->id; ?>"
data-name="<?php echo esc_attr($profile->display_name); ?>"
data-ignored="<?php echo $viewer_ignores?'1':'0'; ?>">
<i class="fas fa-<?php echo $viewer_ignores?'eye':'eye-slash'; ?>"></i>
<?php echo $viewer_ignores?'Ignorierung aufheben':'Nutzer ignorieren'; ?>
</button>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- ── TAB-NAVIGATION ─────────────────────────────── -->
<?php if ($is_own): ?>
<div class="wbf-profile-card"> <div class="wbf-profile-card__header">
<div class="wbf-profile-tabs">
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=1"
class="wbf-profile-tab<?php echo $active_tab===1?' active':''; ?>">
<i class="fas fa-sliders"></i> Profil
</a>
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=2"
class="wbf-profile-tab<?php echo $active_tab===2?' active':''; ?>">
<i class="fas fa-comments"></i> Aktivität
</a>
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=3"
class="wbf-profile-tab<?php echo $active_tab===3?' active':''; ?>">
<i class="fas fa-shield-halved"></i> Privatsphäre
</a>
</div>
<?php endif; ?>
<!-- ══════════════════════════════════════════════════
TAB 1 — Profil bearbeiten + Weitere Profilangaben
══════════════════════════════════════════════════ -->
<?php if ($is_own && $active_tab === 1): ?>
<!-- Profil bearbeiten -->
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-sliders"></i> Profil bearbeiten
</div>
<div class="wbf-profile-card__body">
@@ -995,7 +1073,10 @@ class WBF_Shortcodes {
</div>
<div class="wbf-form-row">
<label>Signatur <small>(max. 300 Zeichen)</small></label>
<div class="wbf-form-row" style="display:flex;align-items:center;gap:.75rem;margin-bottom:.75rem">
<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-form-row" style="display:flex;align-items:center;gap:.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"
@@ -1005,9 +1086,6 @@ class WBF_Shortcodes {
<?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">
<button class="wbf-btn wbf-btn--primary" id="wbfSaveProfile">
<i class="fas fa-save"></i> Speichern
@@ -1017,12 +1095,36 @@ class WBF_Shortcodes {
</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 ) ):
?>
<!-- E-Mail-Adresse ändern -->
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-envelope"></i> E-Mail-Adresse
</div>
<div class="wbf-profile-card__body">
<p style="font-size:.82rem;color:var(--c-muted);margin-bottom:1rem">
Aktuelle Adresse: <strong style="color:var(--c-text)"><?php echo esc_html($profile->email); ?></strong>
</p>
<div class="wbf-profile-edit-grid">
<div class="wbf-form-row">
<label>Neue E-Mail-Adresse</label>
<input type="email" id="wbfNewEmail" placeholder="neue@email.de" autocomplete="off">
</div>
<div class="wbf-form-row">
<label>Aktuelles Passwort <small>(zur Bestätigung)</small></label>
<input type="password" id="wbfEmailPassword" placeholder="••••••" autocomplete="current-password">
</div>
</div>
<div class="wbf-profile-card__footer">
<button class="wbf-btn wbf-btn--primary" id="wbfSaveEmail">
<i class="fas fa-envelope"></i> E-Mail ändern
</button>
<span class="wbf-msg" id="wbfEmailMsg"></span>
</div>
</div>
</div>
<!-- Weitere Profilangaben -->
<?php if (!empty($cf_defs)): ?>
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-sliders"></i> Weitere Profilangaben
@@ -1073,46 +1175,38 @@ class WBF_Shortcodes {
</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 Tab 1 */ ?>
<?php endif; /* end $is_own */ ?>
<!-- ══════════════════════════════════════════════════
TAB 2 — Lesezeichen + Beiträge
══════════════════════════════════════════════════ -->
<?php if ($active_tab === 2): ?>
<!-- Lesezeichen (nur eigenes Profil) -->
<?php if ($is_own): ?>
<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; ?>
<!-- Beiträge -->
<div class="wbf-profile-card">
@@ -1159,32 +1253,146 @@ class WBF_Shortcodes {
</div>
</div>
<!-- Lesezeichen (nur eigenes Profil) -->
<?php if ($is_own):
$bookmarks = WBF_DB::get_user_bookmarks($current->id, 50); ?>
<?php endif; /* end Tab 2 */ ?>
<!-- ══════════════════════════════════════════════════
TAB 3 — Ignorierte Nutzer + Datenschutz
══════════════════════════════════════════════════ -->
<?php if ($is_own && $active_tab === 3): ?>
<!-- Ignorierte Nutzer -->
<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>
<i class="fas fa-bell"></i> E-Mail-Benachrichtigungen
</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)); ?>
<div class="wbf-profile-card__body">
<?php
$notif_meta = WBF_DB::get_user_meta($current->id);
$n_reply = ($notif_meta['notify_reply'] ?? '1') !== '0';
$n_mention = ($notif_meta['notify_mention'] ?? '1') !== '0';
$n_message = ($notif_meta['notify_message'] ?? '1') !== '0';
?>
<p style="font-size:.82rem;color:var(--c-muted);margin-bottom:1rem">
Lege fest bei welchen Ereignissen du eine E-Mail erhältst.
</p>
<div class="wbf-notif-pref-list">
<label class="wbf-notif-pref">
<div class="wbf-notif-pref__info">
<span><i class="fas fa-reply"></i> Antworten auf meine Threads</span>
<small>Wenn jemand in einem deiner Threads antwortet</small>
</div>
<div class="wbf-toggle<?php echo $n_reply?' wbf-toggle--on':''; ?>"
id="wbfNotifReply" data-key="notify_reply" data-state="<?php echo $n_reply?'1':'0'; ?>">
<div class="wbf-toggle__knob"></div>
</div>
</label>
<label class="wbf-notif-pref">
<div class="wbf-notif-pref__info">
<span><i class="fas fa-at"></i> @Erwähnungen</span>
<small>Wenn dich jemand in einem Beitrag erwähnt</small>
</div>
<div class="wbf-toggle<?php echo $n_mention?' wbf-toggle--on':''; ?>"
id="wbfNotifMention" data-key="notify_mention" data-state="<?php echo $n_mention?'1':'0'; ?>">
<div class="wbf-toggle__knob"></div>
</div>
</label>
<label class="wbf-notif-pref">
<div class="wbf-notif-pref__info">
<span><i class="fas fa-envelope"></i> Privatnachrichten</span>
<small>Wenn du eine neue Direktnachricht erhältst</small>
</div>
<div class="wbf-toggle<?php echo $n_message?' wbf-toggle--on':''; ?>"
id="wbfNotifMessage" data-key="notify_message" data-state="<?php echo $n_message?'1':'0'; ?>">
<div class="wbf-toggle__knob"></div>
</div>
</label>
</div>
<div class="wbf-profile-card__footer" style="margin-top:1rem">
<button class="wbf-btn wbf-btn--primary" id="wbfSaveNotifPrefs">
<i class="fas fa-save"></i> Einstellungen speichern
</button>
<span class="wbf-msg" id="wbfNotifPrefsMsg"></span>
</div>
</div>
</div>
<!-- Ignorierte Nutzer -->
<div class="wbf-profile-card">
<div class="wbf-profile-card__header">
<i class="fas fa-eye-slash"></i> Ignorierte Nutzer
<span class="wbf-profile-card__count" id="wbfIgnoreCount"><?php echo count($ignore_list); ?></span>
</div>
<div class="wbf-profile-card__body" id="wbfIgnoreListWrap">
<?php if (empty($ignore_list)): ?>
<p class="wbf-profile-empty" id="wbfIgnoreEmpty">Du ignorierst niemanden.</p>
<?php else: ?>
<div class="wbf-ignore-list" id="wbfIgnoreList">
<?php foreach ($ignore_list as $ign): ?>
<div class="wbf-ignore-item" id="wbf-ignore-item-<?php echo (int)$ign->id; ?>">
<a href="?forum_profile=<?php echo (int)$ign->id; ?>" class="wbf-ignore-item__avatar">
<?php echo self::avatar($ign->avatar_url, $ign->display_name, 36); ?>
</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 class="wbf-ignore-item__info">
<a href="?forum_profile=<?php echo (int)$ign->id; ?>" class="wbf-ignore-item__name">
<?php echo esc_html($ign->display_name); ?>
</a>
<span class="wbf-ignore-item__since">Ignoriert seit <?php echo self::time_ago($ign->ignored_since); ?></span>
</div>
<button class="wbf-ignore-btn wbf-btn wbf-btn--sm"
data-id="<?php echo (int)$ign->id; ?>"
data-name="<?php echo esc_attr($ign->display_name); ?>"
data-ignored="1"
style="margin-left:auto">
<i class="fas fa-eye"></i> Entblocken
</button>
</div>
<?php endforeach; endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<!-- Datenschutz & 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 Tab 3 */ ?>
</div><!-- /.wbf-profile-main -->
</div><!-- /.wbf-profile-layout -->
@@ -1193,6 +1401,7 @@ class WBF_Shortcodes {
<?php return ob_get_clean();
}
// ── TAG PAGE ─────────────────────────────────────────────────────────────
private static function view_tag() {
@@ -1274,8 +1483,8 @@ class WBF_Shortcodes {
</div>
</div>
<?php endif; ?>
</div>
<?php self::render_forum_footer(); ?>
</div>
<?php self::render_auth_modal(); ?>
</div>
<?php return ob_get_clean();
@@ -1355,8 +1564,8 @@ class WBF_Shortcodes {
<?php endif; ?>
</div>
</div>
</div>
<?php self::render_forum_footer(); ?>
</div>
<?php self::render_auth_modal(); ?>
<?php self::render_dm_compose_modal(); ?>
</div>
@@ -1443,8 +1652,8 @@ class WBF_Shortcodes {
</div>
<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(); ?>
</div>
<?php self::render_auth_modal(); ?>
</div>
<?php return ob_get_clean();
@@ -2080,8 +2289,8 @@ class WBF_Shortcodes {
</div>
<?php endif; ?>
</div>
<?php self::render_forum_footer(); ?>
</div>
<?php self::render_auth_modal(); ?>
</div>
<?php return ob_get_clean();
@@ -2120,7 +2329,7 @@ class WBF_Shortcodes {
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)">
<div style="border:1px solid var(--c-border);border-radius:var(--radius);margin-top:2rem;padding:1rem 1.25rem;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.75rem;background:var(--c-surface)">
<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.