Update from Git Manager GUI
This commit is contained in:
@@ -43,23 +43,46 @@ class WBF_Ajax {
|
|||||||
// ── Auth ──────────────────────────────────────────────────────────────────
|
// ── Auth ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
public static function handle_login() {
|
public static function handle_login() {
|
||||||
|
// Brute-Force-Schutz: max. 10 Versuche pro IP in 15 Minuten
|
||||||
|
$ip_key = 'wbf_login_fail_' . md5( $_SERVER['REMOTE_ADDR'] ?? 'unknown' );
|
||||||
|
$fails = (int) get_transient( $ip_key );
|
||||||
|
if ( $fails >= 10 ) {
|
||||||
|
wp_send_json_error([
|
||||||
|
'message' => 'Zu viele fehlgeschlagene Loginversuche. Bitte warte 15 Minuten.',
|
||||||
|
'locked' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
// Login braucht keinen Nonce — Credentials sind die Authentifizierung
|
// Login braucht keinen Nonce — Credentials sind die Authentifizierung
|
||||||
$result = WBF_Auth::login(
|
$result = WBF_Auth::login(
|
||||||
sanitize_text_field($_POST['username'] ?? ''),
|
sanitize_text_field($_POST['username'] ?? ''),
|
||||||
$_POST['password'] ?? ''
|
$_POST['password'] ?? ''
|
||||||
);
|
);
|
||||||
if ($result['success']) {
|
if ($result['success']) {
|
||||||
|
// Erfolgreicher Login: Fehlzähler löschen
|
||||||
|
delete_transient( $ip_key );
|
||||||
$u = $result['user'];
|
$u = $result['user'];
|
||||||
if ( ! empty($_POST['remember_me']) ) {
|
if ( ! empty($_POST['remember_me']) ) {
|
||||||
WBF_Auth::set_remember_cookie($u->id);
|
WBF_Auth::set_remember_cookie($u->id);
|
||||||
}
|
}
|
||||||
wp_send_json_success(['display_name'=>$u->display_name,'avatar_url'=>$u->avatar_url,'user_id'=>$u->id]);
|
wp_send_json_success(['display_name'=>$u->display_name,'avatar_url'=>$u->avatar_url,'user_id'=>$u->id]);
|
||||||
} else {
|
} else {
|
||||||
|
// Fehlversuch zählen — außer bei gesperrten Konten (kein Passwortfehler)
|
||||||
|
if ( empty($result['banned']) ) {
|
||||||
|
set_transient( $ip_key, $fails + 1, 15 * MINUTE_IN_SECONDS );
|
||||||
|
}
|
||||||
wp_send_json_error($result);
|
wp_send_json_error($result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function handle_register() {
|
public static function handle_register() {
|
||||||
|
// Brute-Force/Spam-Schutz: max. 5 Registrierungen pro IP pro Stunde
|
||||||
|
$reg_ip_key = 'wbf_reg_ip_' . md5( $_SERVER['REMOTE_ADDR'] ?? 'unknown' );
|
||||||
|
$reg_fails = (int) get_transient( $reg_ip_key );
|
||||||
|
if ( $reg_fails >= 5 ) {
|
||||||
|
wp_send_json_error(['message' => 'Zu viele Registrierungsversuche von dieser IP. Bitte warte eine Stunde.']);
|
||||||
|
}
|
||||||
|
|
||||||
// Spam-Schutz: Honeypot + Zeitlimit
|
// Spam-Schutz: Honeypot + Zeitlimit
|
||||||
if ( ! empty($_POST['wbf_website']) ) {
|
if ( ! empty($_POST['wbf_website']) ) {
|
||||||
wp_send_json_error(['message' => 'Spam erkannt.']);
|
wp_send_json_error(['message' => 'Spam erkannt.']);
|
||||||
@@ -98,6 +121,8 @@ class WBF_Ajax {
|
|||||||
sanitize_text_field($_POST['display_name'] ?? '')
|
sanitize_text_field($_POST['display_name'] ?? '')
|
||||||
);
|
);
|
||||||
if ($result['success']) {
|
if ($result['success']) {
|
||||||
|
// Registrierungs-Zähler für IP erhöhen
|
||||||
|
set_transient( $reg_ip_key, $reg_fails + 1, HOUR_IN_SECONDS );
|
||||||
$u = $result['user'];
|
$u = $result['user'];
|
||||||
// Einladungscode einlösen
|
// Einladungscode einlösen
|
||||||
$reg_mode2 = wbf_get_settings()['registration_mode'] ?? 'open';
|
$reg_mode2 = wbf_get_settings()['registration_mode'] ?? 'open';
|
||||||
@@ -226,9 +251,11 @@ class WBF_Ajax {
|
|||||||
}
|
}
|
||||||
// Thread-Abonnenten benachrichtigen
|
// Thread-Abonnenten benachrichtigen
|
||||||
$subscribers = WBF_DB::get_thread_subscribers($thread_id);
|
$subscribers = WBF_DB::get_thread_subscribers($thread_id);
|
||||||
|
// $notif_users is a flat array of IDs (from get_col) — cast to int for comparison
|
||||||
|
$notif_ids = array_map('intval', $notif_users);
|
||||||
foreach ($subscribers as $sub) {
|
foreach ($subscribers as $sub) {
|
||||||
if ((int)$sub->id === (int)$user->id) continue; // nicht sich selbst
|
if ((int)$sub->id === (int)$user->id) continue; // nicht sich selbst
|
||||||
if (in_array($sub->id, array_column($notif_users, 'id') ?: [])) continue; // schon benachrichtigt
|
if (in_array((int)$sub->id, $notif_ids, true)) continue; // schon benachrichtigt
|
||||||
self::send_notification_email($sub, 'reply', $user->display_name, [
|
self::send_notification_email($sub, 'reply', $user->display_name, [
|
||||||
'thread_id' => $thread_id,
|
'thread_id' => $thread_id,
|
||||||
'thread_title' => $thread->title,
|
'thread_title' => $thread->title,
|
||||||
@@ -372,6 +399,19 @@ class WBF_Ajax {
|
|||||||
|
|
||||||
if (!empty($_POST['new_password'])) {
|
if (!empty($_POST['new_password'])) {
|
||||||
if (strlen($_POST['new_password']) < 6) wp_send_json_error(['message'=>'Passwort mindestens 6 Zeichen.']);
|
if (strlen($_POST['new_password']) < 6) wp_send_json_error(['message'=>'Passwort mindestens 6 Zeichen.']);
|
||||||
|
// Sicherheit: aktuelles Passwort muss zur Bestätigung angegeben werden
|
||||||
|
$current_pw = $_POST['current_password'] ?? '';
|
||||||
|
if ( empty($current_pw) ) {
|
||||||
|
wp_send_json_error(['message'=>'Bitte aktuelles Passwort zur Bestätigung eingeben.']);
|
||||||
|
}
|
||||||
|
if ( ! password_verify($current_pw, $user->password) ) {
|
||||||
|
wp_send_json_error(['message'=>'Aktuelles Passwort ist falsch.']);
|
||||||
|
}
|
||||||
|
// Bestätigungsfeld server-seitig prüfen
|
||||||
|
$new_pw2 = $_POST['new_password2'] ?? '';
|
||||||
|
if ( ! empty($new_pw2) && $new_pw2 !== $_POST['new_password'] ) {
|
||||||
|
wp_send_json_error(['message'=>'Die Passwörter stimmen nicht überein.']);
|
||||||
|
}
|
||||||
$update['password'] = password_hash($_POST['new_password'], PASSWORD_DEFAULT);
|
$update['password'] = password_hash($_POST['new_password'], PASSWORD_DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,6 +437,15 @@ class WBF_Ajax {
|
|||||||
$value = sanitize_textarea_field( $raw );
|
$value = sanitize_textarea_field( $raw );
|
||||||
} elseif ( $def['type'] === 'number' ) {
|
} elseif ( $def['type'] === 'number' ) {
|
||||||
$value = is_numeric($raw) ? (string)(float)$raw : '';
|
$value = is_numeric($raw) ? (string)(float)$raw : '';
|
||||||
|
} elseif ( $def['type'] === 'date' ) {
|
||||||
|
// Datum validieren — nur YYYY-MM-DD, nicht in der Zukunft
|
||||||
|
$raw_date = sanitize_text_field( trim($raw) );
|
||||||
|
if ( preg_match('/^\d{4}-\d{2}-\d{2}$/', $raw_date) ) {
|
||||||
|
$ts = strtotime($raw_date);
|
||||||
|
$value = ($ts && $ts <= time()) ? $raw_date : '';
|
||||||
|
} else {
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$value = sanitize_text_field( $raw );
|
$value = sanitize_text_field( $raw );
|
||||||
}
|
}
|
||||||
@@ -594,7 +643,8 @@ class WBF_Ajax {
|
|||||||
self::verify();
|
self::verify();
|
||||||
$query = sanitize_text_field( $_POST['query'] ?? '' );
|
$query = sanitize_text_field( $_POST['query'] ?? '' );
|
||||||
if ( mb_strlen( $query ) < 2 ) wp_send_json_error(['message' => 'Suchbegriff zu kurz.']);
|
if ( mb_strlen( $query ) < 2 ) wp_send_json_error(['message' => 'Suchbegriff zu kurz.']);
|
||||||
$results = WBF_DB::search( $query, 40 );
|
$current_search = WBF_Auth::get_current_user();
|
||||||
|
$results = WBF_DB::search( $query, 40, $current_search );
|
||||||
wp_send_json_success(['results' => $results, 'query' => $query]);
|
wp_send_json_success(['results' => $results, 'query' => $query]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1141,6 +1191,12 @@ class WBF_Ajax {
|
|||||||
self::verify();
|
self::verify();
|
||||||
$user = WBF_Auth::get_current_user();
|
$user = WBF_Auth::get_current_user();
|
||||||
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
if (!$user) wp_send_json_error(['message'=>'Nicht eingeloggt.']);
|
||||||
|
// Sicherstellen dass Spalte existiert (Schutz für bestehende Installs)
|
||||||
|
global $wpdb;
|
||||||
|
$cols = $wpdb->get_col( "DESCRIBE {$wpdb->prefix}forum_users" );
|
||||||
|
if ( ! in_array( 'profile_public', $cols ) ) {
|
||||||
|
$wpdb->query( "ALTER TABLE {$wpdb->prefix}forum_users ADD COLUMN profile_public TINYINT(1) NOT NULL DEFAULT 1" );
|
||||||
|
}
|
||||||
$current = (int)($user->profile_public ?? 1);
|
$current = (int)($user->profile_public ?? 1);
|
||||||
$new = $current ? 0 : 1;
|
$new = $current ? 0 : 1;
|
||||||
WBF_DB::update_user($user->id, ['profile_public'=>$new]);
|
WBF_DB::update_user($user->id, ['profile_public'=>$new]);
|
||||||
|
|||||||
@@ -6,8 +6,25 @@ class WBF_Auth {
|
|||||||
const SESSION_KEY = 'wbf_forum_user';
|
const SESSION_KEY = 'wbf_forum_user';
|
||||||
|
|
||||||
public static function init() {
|
public static function init() {
|
||||||
|
// PHP 8.3: session_start() nach gesendeten Headers erzeugt E_WARNING,
|
||||||
|
// der direkt in den HTML-Output fließt und das Layout zerstört.
|
||||||
|
// Lösung: headers_sent() prüfen + session_start() mit Cookie-Optionen aufrufen.
|
||||||
if ( ! session_id() ) {
|
if ( ! session_id() ) {
|
||||||
session_start();
|
if ( headers_sent() ) {
|
||||||
|
// Headers bereits gesendet — Session kann nicht sicher gestartet werden.
|
||||||
|
// Passiert z.B. wenn WP_DEBUG=true und PHP Notices vor dem Hook ausgegeben hat.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$session_opts = [
|
||||||
|
'cookie_httponly' => true,
|
||||||
|
'cookie_samesite' => 'Lax',
|
||||||
|
'use_strict_mode' => true,
|
||||||
|
];
|
||||||
|
// cookie_secure nur setzen wenn HTTPS aktiv — verhindert Session-Verlust bei HTTP
|
||||||
|
if ( is_ssl() || ( ! empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ) ) {
|
||||||
|
$session_opts['cookie_secure'] = true;
|
||||||
|
}
|
||||||
|
session_start( $session_opts );
|
||||||
}
|
}
|
||||||
// Auto-login via Remember-Me cookie if not already logged in
|
// Auto-login via Remember-Me cookie if not already logged in
|
||||||
if ( empty( $_SESSION[ self::SESSION_KEY ] ) && isset( $_COOKIE['wbf_remember'] ) ) {
|
if ( empty( $_SESSION[ self::SESSION_KEY ] ) && isset( $_COOKIE['wbf_remember'] ) ) {
|
||||||
@@ -55,6 +72,7 @@ class WBF_Auth {
|
|||||||
]);
|
]);
|
||||||
// Frisch laden und einloggen
|
// Frisch laden und einloggen
|
||||||
$user = WBF_DB::get_user( $user->id );
|
$user = WBF_DB::get_user( $user->id );
|
||||||
|
if ( session_id() ) session_regenerate_id( true ); // Session Fixation verhindern
|
||||||
$_SESSION[ self::SESSION_KEY ] = $user->id;
|
$_SESSION[ self::SESSION_KEY ] = $user->id;
|
||||||
WBF_DB::touch_last_active( $user->id );
|
WBF_DB::touch_last_active( $user->id );
|
||||||
return array( 'success' => true, 'user' => $user );
|
return array( 'success' => true, 'user' => $user );
|
||||||
@@ -67,6 +85,7 @@ class WBF_Auth {
|
|||||||
}
|
}
|
||||||
return array( 'success' => false, 'banned' => true, 'message' => $reason );
|
return array( 'success' => false, 'banned' => true, 'message' => $reason );
|
||||||
}
|
}
|
||||||
|
if ( session_id() ) session_regenerate_id( true ); // Session Fixation verhindern
|
||||||
$_SESSION[ self::SESSION_KEY ] = $user->id;
|
$_SESSION[ self::SESSION_KEY ] = $user->id;
|
||||||
WBF_DB::touch_last_active( $user->id );
|
WBF_DB::touch_last_active( $user->id );
|
||||||
return array( 'success' => true, 'user' => $user );
|
return array( 'success' => true, 'user' => $user );
|
||||||
@@ -96,6 +115,7 @@ class WBF_Auth {
|
|||||||
'avatar_url' => $avatar,
|
'avatar_url' => $avatar,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if ( session_id() ) session_regenerate_id( true ); // Session Fixation verhindern
|
||||||
$_SESSION[ self::SESSION_KEY ] = $id;
|
$_SESSION[ self::SESSION_KEY ] = $id;
|
||||||
return array('success'=>true,'user'=>WBF_DB::get_user($id));
|
return array('success'=>true,'user'=>WBF_DB::get_user($id));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -490,7 +490,7 @@ class WBF_DB {
|
|||||||
}
|
}
|
||||||
// Move post_count contribution too
|
// Move post_count contribution too
|
||||||
$post_count = (int)$wpdb->get_var($wpdb->prepare(
|
$post_count = (int)$wpdb->get_var($wpdb->prepare(
|
||||||
"SELECT COUNT(*) FROM {$wpdb->prefix}forum_posts WHERE thread_id=%d", $thread_id
|
"SELECT COUNT(*) FROM {$wpdb->prefix}forum_posts WHERE thread_id=%d AND deleted_at IS NULL", $thread_id
|
||||||
));
|
));
|
||||||
if ( $post_count > 0 ) {
|
if ( $post_count > 0 ) {
|
||||||
$wpdb->query($wpdb->prepare(
|
$wpdb->query($wpdb->prepare(
|
||||||
@@ -512,7 +512,7 @@ class WBF_DB {
|
|||||||
FROM {$wpdb->prefix}forum_threads t
|
FROM {$wpdb->prefix}forum_threads t
|
||||||
JOIN {$wpdb->prefix}forum_users u ON u.id = t.user_id
|
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
|
LEFT JOIN {$wpdb->prefix}forum_prefixes p ON p.id = t.prefix_id
|
||||||
WHERE t.id = %d", $id
|
WHERE t.id = %d AND t.deleted_at IS NULL", $id
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,7 +572,7 @@ class WBF_DB {
|
|||||||
|
|
||||||
public static function count_posts( $thread_id ) {
|
public static function count_posts( $thread_id ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
return (int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$wpdb->prefix}forum_posts WHERE thread_id=%d", $thread_id));
|
return (int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$wpdb->prefix}forum_posts WHERE thread_id=%d AND deleted_at IS NULL", $thread_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function create_post( $data ) {
|
public static function create_post( $data ) {
|
||||||
@@ -643,8 +643,8 @@ class WBF_DB {
|
|||||||
public static function get_stats() {
|
public static function get_stats() {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
return [
|
return [
|
||||||
'threads' => $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_threads WHERE status != 'archived'"),
|
'threads' => $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_threads WHERE status != 'archived' AND deleted_at IS NULL"),
|
||||||
'posts' => $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_posts"),
|
'posts' => $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_posts WHERE deleted_at IS NULL"),
|
||||||
'members' => $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_users"),
|
'members' => $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}forum_users"),
|
||||||
'newest' => $wpdb->get_var("SELECT display_name FROM {$wpdb->prefix}forum_users ORDER BY registered DESC LIMIT 1"),
|
'newest' => $wpdb->get_var("SELECT display_name FROM {$wpdb->prefix}forum_users ORDER BY registered DESC LIMIT 1"),
|
||||||
];
|
];
|
||||||
@@ -731,9 +731,23 @@ class WBF_DB {
|
|||||||
|
|
||||||
// ── Suche ─────────────────────────────────────────────────────────────────
|
// ── Suche ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
public static function search( $query, $limit = 30 ) {
|
public static function search( $query, $limit = 30, $user = null ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$like = '%' . $wpdb->esc_like( $query ) . '%';
|
$like = '%' . $wpdb->esc_like( $query ) . '%';
|
||||||
|
|
||||||
|
// Kategorie-Sichtbarkeit: Gäste und Member dürfen keine privaten Kategorien sehen
|
||||||
|
$user_level = $user ? WBF_Roles::level( $user->role ) : -99;
|
||||||
|
if ( $user_level >= 50 ) {
|
||||||
|
// Moderatoren+ sehen alles (inkl. soft-deleted ist extra)
|
||||||
|
$cat_filter = '';
|
||||||
|
} elseif ( $user ) {
|
||||||
|
// Eingeloggte Member/VIP: nur guest_visible oder eigene Rolle reicht
|
||||||
|
$cat_filter = "AND c.guest_visible = 1 AND (c.min_role IS NULL OR c.min_role IN ('member','vip'))";
|
||||||
|
} else {
|
||||||
|
// Gäste: nur komplett öffentliche Kategorien
|
||||||
|
$cat_filter = "AND c.guest_visible = 1 AND (c.min_role IS NULL OR c.min_role = 'member')";
|
||||||
|
}
|
||||||
|
|
||||||
return $wpdb->get_results( $wpdb->prepare(
|
return $wpdb->get_results( $wpdb->prepare(
|
||||||
"SELECT 'thread' AS result_type,
|
"SELECT 'thread' AS result_type,
|
||||||
t.id, t.title, t.content, t.created_at, t.reply_count,
|
t.id, t.title, t.content, t.created_at, t.reply_count,
|
||||||
@@ -742,7 +756,9 @@ class WBF_DB {
|
|||||||
FROM {$wpdb->prefix}forum_threads t
|
FROM {$wpdb->prefix}forum_threads t
|
||||||
JOIN {$wpdb->prefix}forum_users u ON u.id = t.user_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
|
JOIN {$wpdb->prefix}forum_categories c ON c.id = t.category_id
|
||||||
WHERE (t.title LIKE %s OR t.content LIKE %s) AND t.status != 'archived'
|
WHERE (t.title LIKE %s OR t.content LIKE %s)
|
||||||
|
AND t.status != 'archived' AND t.deleted_at IS NULL
|
||||||
|
$cat_filter
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT 'post' AS result_type,
|
SELECT 'post' AS result_type,
|
||||||
p.id, t.title, p.content, p.created_at, 0 AS reply_count,
|
p.id, t.title, p.content, p.created_at, 0 AS reply_count,
|
||||||
@@ -752,7 +768,9 @@ class WBF_DB {
|
|||||||
JOIN {$wpdb->prefix}forum_threads t ON t.id = p.thread_id
|
JOIN {$wpdb->prefix}forum_threads t ON t.id = p.thread_id
|
||||||
JOIN {$wpdb->prefix}forum_users u ON u.id = p.user_id
|
JOIN {$wpdb->prefix}forum_users u ON u.id = p.user_id
|
||||||
JOIN {$wpdb->prefix}forum_categories c ON c.id = t.category_id
|
JOIN {$wpdb->prefix}forum_categories c ON c.id = t.category_id
|
||||||
WHERE p.content LIKE %s AND t.status != 'archived'
|
WHERE p.content LIKE %s
|
||||||
|
AND p.deleted_at IS NULL AND t.status != 'archived' AND t.deleted_at IS NULL
|
||||||
|
$cat_filter
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
LIMIT %d",
|
LIMIT %d",
|
||||||
$like, $like, $like, $limit
|
$like, $like, $like, $limit
|
||||||
@@ -1476,6 +1494,25 @@ class WBF_DB {
|
|||||||
update_option( 'wbf_profile_fields', $fields );
|
update_option( 'wbf_profile_fields', $fields );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function get_profile_field_categories() {
|
||||||
|
$cats = get_option( 'wbf_profile_field_cats', null );
|
||||||
|
if ( $cats === null ) {
|
||||||
|
// Default-Kategorien beim ersten Aufruf
|
||||||
|
$defaults = [
|
||||||
|
[ 'id' => 'cat_allgemein', 'name' => 'Allgemein', 'icon' => '👤' ],
|
||||||
|
[ 'id' => 'cat_kontakt', 'name' => 'Kontakt', 'icon' => '✉️' ],
|
||||||
|
[ 'id' => 'cat_social', 'name' => 'Social Media', 'icon' => '🌐' ],
|
||||||
|
];
|
||||||
|
update_option( 'wbf_profile_field_cats', $defaults );
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
return is_array( $cats ) ? $cats : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function save_profile_field_categories( $cats ) {
|
||||||
|
update_option( 'wbf_profile_field_cats', $cats );
|
||||||
|
}
|
||||||
|
|
||||||
public static function get_user_meta( $user_id ) {
|
public static function get_user_meta( $user_id ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
$rows = $wpdb->get_results( $wpdb->prepare(
|
$rows = $wpdb->get_results( $wpdb->prepare(
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ class WBF_Export {
|
|||||||
case 'settings':
|
case 'settings':
|
||||||
$data['settings'] = get_option( 'wbf_settings', [] );
|
$data['settings'] = get_option( 'wbf_settings', [] );
|
||||||
$data['profile_fields'] = get_option( 'wbf_profile_fields', [] );
|
$data['profile_fields'] = get_option( 'wbf_profile_fields', [] );
|
||||||
|
$data['profile_field_cats'] = get_option( 'wbf_profile_field_cats', [] );
|
||||||
$data['reactions_cfg'] = get_option( 'wbf_reactions', [] );
|
$data['reactions_cfg'] = get_option( 'wbf_reactions', [] );
|
||||||
$data['word_filter'] = get_option( 'wbf_word_filter', '' );
|
$data['word_filter'] = get_option( 'wbf_word_filter', '' );
|
||||||
break;
|
break;
|
||||||
@@ -275,6 +276,7 @@ class WBF_Export {
|
|||||||
}
|
}
|
||||||
if ( isset( $data['profile_fields'] ) ) {
|
if ( isset( $data['profile_fields'] ) ) {
|
||||||
update_option( 'wbf_profile_fields', $data['profile_fields'] );
|
update_option( 'wbf_profile_fields', $data['profile_fields'] );
|
||||||
|
if ( isset($data['profile_field_cats']) ) update_option( 'wbf_profile_field_cats', $data['profile_field_cats'] );
|
||||||
$log[] = '✅ Profilfeld-Definitionen (' . count( $data['profile_fields'] ) . ') importiert.';
|
$log[] = '✅ Profilfeld-Definitionen (' . count( $data['profile_fields'] ) . ') importiert.';
|
||||||
}
|
}
|
||||||
if ( isset( $data['reactions_cfg'] ) && is_array( $data['reactions_cfg'] ) ) {
|
if ( isset( $data['reactions_cfg'] ) && is_array( $data['reactions_cfg'] ) ) {
|
||||||
@@ -1172,7 +1174,7 @@ class WBF_Export {
|
|||||||
/** Prüft ob eine Tabelle existiert */
|
/** Prüft ob eine Tabelle existiert */
|
||||||
private static function table_exists( string $table ): bool {
|
private static function table_exists( string $table ): bool {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
return $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) === $table;
|
return $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table ) ) === $table;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Erstellt ein standardisiertes Ergebnis-Array */
|
/** Erstellt ein standardisiertes Ergebnis-Array */
|
||||||
|
|||||||
@@ -43,12 +43,12 @@ class WBF_Levels {
|
|||||||
return $defaults;
|
return $defaults;
|
||||||
}
|
}
|
||||||
$levels = (array) $saved;
|
$levels = (array) $saved;
|
||||||
usort( $levels, fn($a,$b) => (int)$a['min'] <=> (int)$b['min'] );
|
usort( $levels, function($a, $b) { return (int)$a['min'] <=> (int)$b['min']; } );
|
||||||
return $levels;
|
return $levels;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function save( $levels ) {
|
public static function save( $levels ) {
|
||||||
usort( $levels, fn($a,$b) => (int)$a['min'] <=> (int)$b['min'] );
|
usort( $levels, function($a, $b) { return (int)$a['min'] <=> (int)$b['min']; } );
|
||||||
update_option( self::OPTION_KEY, $levels );
|
update_option( self::OPTION_KEY, $levels );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class WBF_Roles {
|
|||||||
/** Nach Level sortiert (höchstes zuerst) */
|
/** Nach Level sortiert (höchstes zuerst) */
|
||||||
public static function get_sorted() {
|
public static function get_sorted() {
|
||||||
$all = self::get_all();
|
$all = self::get_all();
|
||||||
uasort($all, fn($a,$b) => $b['level'] <=> $a['level']);
|
uasort($all, function($a, $b) { return $b['level'] <=> $a['level']; });
|
||||||
return $all;
|
return $all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,15 @@ class WBF_Shortcodes {
|
|||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Alter aus Geburtsdatum berechnen */
|
||||||
|
public static function calc_age( $date_str ) {
|
||||||
|
if ( ! $date_str || ! preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_str) ) return null;
|
||||||
|
$birth = new DateTime( $date_str );
|
||||||
|
$today = new DateTime();
|
||||||
|
if ( $birth > $today ) return null;
|
||||||
|
return (int) $birth->diff($today)->y;
|
||||||
|
}
|
||||||
|
|
||||||
public static function time_ago( $datetime ) {
|
public static function time_ago( $datetime ) {
|
||||||
$diff = time() - strtotime($datetime);
|
$diff = time() - strtotime($datetime);
|
||||||
if ($diff < 60) return 'Gerade eben';
|
if ($diff < 60) return 'Gerade eben';
|
||||||
@@ -149,8 +158,12 @@ class WBF_Shortcodes {
|
|||||||
// ── Router ────────────────────────────────────────────────────────────────
|
// ── Router ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
public static function forum_main( $atts ) {
|
public static function forum_main( $atts ) {
|
||||||
// Server-seitiger Logout-Fallback
|
// Server-seitiger Logout-Fallback — Nonce-Schutz gegen CSRF
|
||||||
if (isset($_GET['wbf_do_logout'])) {
|
if (isset($_GET['wbf_do_logout'])) {
|
||||||
|
if ( ! isset($_GET['_wpnonce']) || ! wp_verify_nonce( sanitize_text_field($_GET['_wpnonce']), 'wbf_logout' ) ) {
|
||||||
|
wp_redirect( wbf_get_forum_url() );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
WBF_Auth::logout();
|
WBF_Auth::logout();
|
||||||
wp_redirect( wbf_get_forum_url() );
|
wp_redirect( wbf_get_forum_url() );
|
||||||
exit;
|
exit;
|
||||||
@@ -310,7 +323,7 @@ class WBF_Shortcodes {
|
|||||||
</div>
|
</div>
|
||||||
<div class="wbf-profile-widget__actions">
|
<div class="wbf-profile-widget__actions">
|
||||||
<a href="?forum_profile=<?php echo (int)$current->id; ?>" class="wbf-btn wbf-btn--sm">Profil</a>
|
<a href="?forum_profile=<?php echo (int)$current->id; ?>" class="wbf-btn wbf-btn--sm">Profil</a>
|
||||||
<a href="<?php echo esc_url(wbf_get_forum_url() . '?wbf_do_logout=1'); ?>" class="wbf-btn wbf-btn--sm wbf-btn--outline"><?php echo esc_html(wbf_get_settings()['btn_logout']); ?></a>
|
<a href="<?php echo esc_url(wp_nonce_url(wbf_get_forum_url() . '?wbf_do_logout=1', 'wbf_logout')); ?>" class="wbf-btn wbf-btn--sm wbf-btn--outline"><?php echo esc_html(wbf_get_settings()['btn_logout']); ?></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -893,7 +906,9 @@ class WBF_Shortcodes {
|
|||||||
$is_own = $current && $current->id == $profile->id;
|
$is_own = $current && $current->id == $profile->id;
|
||||||
$is_staff = $current && WBF_Roles::level($current->role) >= 50;
|
$is_staff = $current && WBF_Roles::level($current->role) >= 50;
|
||||||
// Profil-Sichtbarkeit prüfen
|
// Profil-Sichtbarkeit prüfen
|
||||||
if (!$is_own && !$is_staff && (int)($profile->profile_public ?? 1) === 0) {
|
// profile_public NULL = Spalte fehlt noch = als öffentlich (1) behandeln
|
||||||
|
$profile_public = isset($profile->profile_public) ? (int)$profile->profile_public : 1;
|
||||||
|
if (!$is_own && !$is_staff && $profile_public === 0) {
|
||||||
ob_start(); ?>
|
ob_start(); ?>
|
||||||
<div class="wbf-wrap"><?php self::render_topbar($current); ?>
|
<div class="wbf-wrap"><?php self::render_topbar($current); ?>
|
||||||
<div class="wbf-container wbf-mt">
|
<div class="wbf-container wbf-mt">
|
||||||
@@ -907,12 +922,18 @@ class WBF_Shortcodes {
|
|||||||
$bookmarks = $is_own ? WBF_DB::get_user_bookmarks($current->id, 50) : [];
|
$bookmarks = $is_own ? WBF_DB::get_user_bookmarks($current->id, 50) : [];
|
||||||
$ignore_list = $is_own ? WBF_DB::get_ignore_list($current->id) : [];
|
$ignore_list = $is_own ? WBF_DB::get_ignore_list($current->id) : [];
|
||||||
$cf_defs = WBF_DB::get_profile_field_defs();
|
$cf_defs = WBF_DB::get_profile_field_defs();
|
||||||
|
$cf_cats = WBF_DB::get_profile_field_categories();
|
||||||
|
$cf_cat_map = array_column( $cf_cats, null, 'id' );
|
||||||
$cf_vals = WBF_DB::get_user_meta( $profile->id );
|
$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
|
// 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));
|
// Tab-ID: numerisch (1–4) oder String-Slug (z.B. 'mc' von der Forum-Bridge)
|
||||||
$active_tab = in_array($active_tab, [1,2,3]) ? $active_tab : ($is_own ? 1 : 2);
|
$ptab_raw = $_GET['ptab'] ?? ($is_own ? 1 : 2);
|
||||||
// Tab 1 + 3 nur für eigenes Profil
|
$active_tab = ctype_digit( (string) $ptab_raw ) ? (int) $ptab_raw : sanitize_key( $ptab_raw );
|
||||||
if (!$is_own && $active_tab !== 2) $active_tab = 2;
|
if ( is_int($active_tab) && ! in_array($active_tab, [1,2,3,4]) ) {
|
||||||
|
$active_tab = $is_own ? 1 : 2;
|
||||||
|
}
|
||||||
|
// Tab 1, 3, 4 und String-Tabs nur für eigenes Profil (außer Tab 2 = Aktivität)
|
||||||
|
if ( ! $is_own && $active_tab !== 2 ) $active_tab = 2;
|
||||||
|
|
||||||
ob_start(); ?>
|
ob_start(); ?>
|
||||||
<div class="wbf-wrap">
|
<div class="wbf-wrap">
|
||||||
@@ -930,6 +951,7 @@ class WBF_Shortcodes {
|
|||||||
<div class="wbf-profile-sidebar__avatar-wrap">
|
<div class="wbf-profile-sidebar__avatar-wrap">
|
||||||
<img src="<?php echo esc_url($profile->avatar_url); ?>"
|
<img src="<?php echo esc_url($profile->avatar_url); ?>"
|
||||||
alt="<?php echo esc_attr($profile->display_name); ?>"
|
alt="<?php echo esc_attr($profile->display_name); ?>"
|
||||||
|
id="wbfProfileAvatar"
|
||||||
class="wbf-profile-sidebar__avatar">
|
class="wbf-profile-sidebar__avatar">
|
||||||
<?php if ($is_own): ?>
|
<?php if ($is_own): ?>
|
||||||
<label class="wbf-avatar-upload-btn" title="Avatar ändern">
|
<label class="wbf-avatar-upload-btn" title="Avatar ändern">
|
||||||
@@ -981,26 +1003,70 @@ class WBF_Shortcodes {
|
|||||||
<p class="wbf-profile-sidebar__sig"><?php echo nl2br(esc_html($profile->signature)); ?></p>
|
<p class="wbf-profile-sidebar__sig"><?php echo nl2br(esc_html($profile->signature)); ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<!-- Öffentliche Custom Fields -->
|
<!-- Öffentliche Custom Fields — nach Kategorie gruppiert -->
|
||||||
<?php foreach ($cf_defs as $def):
|
<?php
|
||||||
if (!$is_own && empty($def['public'])) continue;
|
$cf_by_cat_sb = [];
|
||||||
$val = trim($cf_vals[$def['key']] ?? '');
|
foreach ( $cf_defs as $def_sb ) {
|
||||||
if ($val === '') continue; ?>
|
if (!$is_own && empty($def_sb['public'])) continue;
|
||||||
|
$val_sb = trim($cf_vals[$def_sb['key']] ?? '');
|
||||||
|
if ($val_sb === '') continue;
|
||||||
|
$cid_sb = $def_sb['category_id'] ?? '';
|
||||||
|
if (!$cid_sb || !isset($cf_cat_map[$cid_sb])) $cid_sb = '__none__';
|
||||||
|
$cf_by_cat_sb[$cid_sb][] = ['def'=>$def_sb,'val'=>$val_sb];
|
||||||
|
}
|
||||||
|
$sb_sections = $cf_cats;
|
||||||
|
if (isset($cf_by_cat_sb['__none__'])) {
|
||||||
|
$sb_sections[] = ['id'=>'__none__','name'=>'Weitere Infos','icon'=>''];
|
||||||
|
}
|
||||||
|
foreach ($sb_sections as $scat_sb):
|
||||||
|
$scid_sb = $scat_sb['id'];
|
||||||
|
if (empty($cf_by_cat_sb[$scid_sb])) continue;
|
||||||
|
?>
|
||||||
<div class="wbf-profile-sidebar__section">
|
<div class="wbf-profile-sidebar__section">
|
||||||
<span class="wbf-profile-sidebar__section-label">
|
<span class="wbf-profile-sidebar__section-label" style="display:flex;align-items:center;gap:5px;margin-bottom:4px">
|
||||||
<i class="fas fa-<?php echo $def['type']==='url'?'link':($def['type']==='number'?'hashtag':'tag'); ?>"></i>
|
<?php if(!empty($scat_sb['icon'])): ?>
|
||||||
<?php echo esc_html($def['label']); ?>
|
<span style="font-size:.9rem"><?php echo esc_html($scat_sb['icon']); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php echo esc_html($scat_sb['name']); ?>
|
||||||
</span>
|
</span>
|
||||||
<?php if ($def['type'] === 'url'): ?>
|
<?php foreach ($cf_by_cat_sb[$scid_sb] as $cf_entry_sb):
|
||||||
<a href="<?php echo esc_url($val); ?>" target="_blank" rel="noopener noreferrer"
|
$def_sb = $cf_entry_sb['def'];
|
||||||
style="color:var(--c-primary);font-size:.85rem;word-break:break-all">
|
$val_sb = $cf_entry_sb['val'];
|
||||||
<?php echo esc_html(mb_strtolower(preg_replace('#^https?://#i','',$val))); ?>
|
// Auto-Link für Telegram und Discord anhand des Feld-Keys erkennen
|
||||||
</a>
|
$key_sb = strtolower($def_sb['key']);
|
||||||
<?php elseif ($def['type'] === 'textarea'): ?>
|
$is_telegram = strpos($key_sb, 'telegram') !== false;
|
||||||
<p style="font-size:.85rem"><?php echo nl2br(esc_html($val)); ?></p>
|
$is_discord = strpos($key_sb, 'discord') !== false;
|
||||||
<?php else: ?>
|
?>
|
||||||
<p style="font-size:.85rem"><?php echo esc_html($val); ?></p>
|
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:6px;margin-bottom:4px;font-size:.85rem">
|
||||||
<?php endif; ?>
|
<span style="color:var(--c-muted,#94a3b8);flex-shrink:0"><?php echo esc_html($def_sb['label']); ?></span>
|
||||||
|
<?php if ($def_sb['type'] === 'url'): ?>
|
||||||
|
<a href="<?php echo esc_url($val_sb); ?>" target="_blank" rel="noopener noreferrer"
|
||||||
|
style="color:var(--c-primary);word-break:break-all;text-align:right">
|
||||||
|
<?php echo esc_html(mb_strtolower(preg_replace('#^https?://#i','',$val_sb))); ?>
|
||||||
|
</a>
|
||||||
|
<?php elseif ($is_telegram):
|
||||||
|
// Username bereinigen: @ und Leerzeichen entfernen
|
||||||
|
$tg_user = ltrim(trim($val_sb), '@');
|
||||||
|
$tg_url = 'https://t.me/' . rawurlencode($tg_user);
|
||||||
|
?>
|
||||||
|
<a href="<?php echo esc_url($tg_url); ?>" target="_blank" rel="noopener noreferrer"
|
||||||
|
style="color:#29b6f6;text-align:right;font-size:inherit">
|
||||||
|
@<?php echo esc_html($tg_user); ?>
|
||||||
|
</a>
|
||||||
|
<?php elseif ($is_discord): ?>
|
||||||
|
<span style="color:#7289da;text-align:right;font-size:inherit">
|
||||||
|
<?php echo esc_html($val_sb); ?>
|
||||||
|
</span>
|
||||||
|
<?php elseif ($def_sb['type'] === 'textarea'): ?>
|
||||||
|
<span style="text-align:right"><?php echo nl2br(esc_html($val_sb)); ?></span>
|
||||||
|
<?php elseif ($def_sb['type'] === 'date'):
|
||||||
|
$age_sb = self::calc_age($val_sb); ?>
|
||||||
|
<span><?php echo $age_sb !== null ? esc_html((string)$age_sb) . ' Jahre' : '—'; ?></span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span style="text-align:right"><?php echo esc_html($val_sb); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -1043,6 +1109,16 @@ class WBF_Shortcodes {
|
|||||||
class="wbf-profile-tab<?php echo $active_tab===3?' active':''; ?>">
|
class="wbf-profile-tab<?php echo $active_tab===3?' active':''; ?>">
|
||||||
<i class="fas fa-shield-halved"></i> Privatsphäre
|
<i class="fas fa-shield-halved"></i> Privatsphäre
|
||||||
</a>
|
</a>
|
||||||
|
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=4"
|
||||||
|
class="wbf-profile-tab<?php echo $active_tab===4?' active':''; ?>">
|
||||||
|
<i class="fas fa-lock"></i> Sicherheit
|
||||||
|
</a>
|
||||||
|
<?php if ( class_exists('MC_Gallery_Forum_Bridge') ) : ?>
|
||||||
|
<a href="?forum_profile=<?php echo (int)$profile->id; ?>&ptab=mc"
|
||||||
|
class="wbf-profile-tab<?php echo $active_tab==='mc'?' active':''; ?>">
|
||||||
|
<i class="fas fa-cubes"></i> Minecraft
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
@@ -1057,15 +1133,9 @@ class WBF_Shortcodes {
|
|||||||
<i class="fas fa-sliders"></i> Profil bearbeiten
|
<i class="fas fa-sliders"></i> Profil bearbeiten
|
||||||
</div>
|
</div>
|
||||||
<div class="wbf-profile-card__body">
|
<div class="wbf-profile-card__body">
|
||||||
<div class="wbf-profile-edit-grid">
|
<div class="wbf-form-row">
|
||||||
<div class="wbf-form-row">
|
<label>Anzeigename</label>
|
||||||
<label>Anzeigename</label>
|
<input type="text" id="wbfEditName" value="<?php echo esc_attr($profile->display_name); ?>">
|
||||||
<input type="text" id="wbfEditName" value="<?php echo esc_attr($profile->display_name); ?>">
|
|
||||||
</div>
|
|
||||||
<div class="wbf-form-row">
|
|
||||||
<label>Neues Passwort <small>(leer = nicht ändern)</small></label>
|
|
||||||
<input type="password" id="wbfNewPassword" placeholder="••••••">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="wbf-form-row">
|
<div class="wbf-form-row">
|
||||||
<label>Bio</label>
|
<label>Bio</label>
|
||||||
@@ -1076,62 +1146,36 @@ class WBF_Shortcodes {
|
|||||||
<textarea id="wbfEditSignature" rows="2" maxlength="300" placeholder="Deine Signatur…"><?php echo esc_textarea($profile->signature ?? ''); ?></textarea>
|
<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 class="wbf-sig-counter"><span id="wbfSigCount"><?php echo mb_strlen($profile->signature??''); ?></span>/300</div>
|
||||||
</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"
|
|
||||||
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>
|
|
||||||
<div class="wbf-profile-card__footer">
|
|
||||||
<button class="wbf-btn wbf-btn--primary" id="wbfSaveProfile">
|
|
||||||
<i class="fas fa-save"></i> Speichern
|
|
||||||
</button>
|
|
||||||
<span class="wbf-msg" id="wbfProfileMsg"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- E-Mail-Adresse ändern -->
|
<!-- Weitere Profilangaben — nach Kategorie gruppiert (ohne eigene Speichern-Buttons) -->
|
||||||
|
<?php
|
||||||
|
$cf_edit_by_cat = [];
|
||||||
|
foreach ( $cf_defs as $def_e ) {
|
||||||
|
$cid_e = $def_e['category_id'] ?? '';
|
||||||
|
if (!$cid_e || !isset($cf_cat_map[$cid_e])) $cid_e = '__none__';
|
||||||
|
$cf_edit_by_cat[$cid_e][] = $def_e;
|
||||||
|
}
|
||||||
|
$edit_sections = $cf_cats;
|
||||||
|
if (isset($cf_edit_by_cat['__none__'])) {
|
||||||
|
$edit_sections[] = ['id'=>'__none__','name'=>'Weitere Angaben','icon'=>'📋'];
|
||||||
|
}
|
||||||
|
if (!empty($cf_defs)):
|
||||||
|
foreach ($edit_sections as $ecat):
|
||||||
|
$ecid = $ecat['id'];
|
||||||
|
if (empty($cf_edit_by_cat[$ecid])) continue;
|
||||||
|
?>
|
||||||
<div class="wbf-profile-card">
|
<div class="wbf-profile-card">
|
||||||
<div class="wbf-profile-card__header">
|
<div class="wbf-profile-card__header">
|
||||||
<i class="fas fa-envelope"></i> E-Mail-Adresse
|
<?php if(!empty($ecat['icon'])): ?>
|
||||||
</div>
|
<span style="margin-right:5px"><?php echo esc_html($ecat['icon']); ?></span>
|
||||||
<div class="wbf-profile-card__body">
|
<?php else: ?><i class="fas fa-sliders"></i><?php endif; ?>
|
||||||
<p style="font-size:.82rem;color:var(--c-muted);margin-bottom:1rem">
|
<?php echo esc_html($ecat['name']); ?>
|
||||||
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
|
|
||||||
</div>
|
</div>
|
||||||
<div class="wbf-profile-card__body">
|
<div class="wbf-profile-card__body">
|
||||||
<div class="wbf-profile-edit-grid">
|
<div class="wbf-profile-edit-grid">
|
||||||
<?php foreach ($cf_defs as $def):
|
<?php foreach ($cf_edit_by_cat[$ecid] as $def):
|
||||||
$k = esc_attr($def['key']);
|
$k = esc_attr($def['key']);
|
||||||
$lbl = esc_html($def['label']);
|
$lbl = esc_html($def['label']);
|
||||||
$ph = esc_attr($def['placeholder'] ?? '');
|
$ph = esc_attr($def['placeholder'] ?? '');
|
||||||
@@ -1154,6 +1198,13 @@ class WBF_Shortcodes {
|
|||||||
<?php selected($cf_vals[$def['key']] ?? '', $opt); ?>><?php echo esc_html($opt); ?></option>
|
<?php selected($cf_vals[$def['key']] ?? '', $opt); ?>><?php echo esc_html($opt); ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
|
<?php elseif ($def['type'] === 'date'): ?>
|
||||||
|
<input type="date"
|
||||||
|
class="wbf-cf-input"
|
||||||
|
data-field="cf_<?php echo $k; ?>"
|
||||||
|
value="<?php echo esc_attr($cf_vals[$def['key']] ?? ''); ?>"
|
||||||
|
max="<?php echo date('Y-m-d'); ?>"
|
||||||
|
<?php echo $req; ?>>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<input type="<?php echo $def['type']==='url'?'url':($def['type']==='number'?'number':'text'); ?>"
|
<input type="<?php echo $def['type']==='url'?'url':($def['type']==='number'?'number':'text'); ?>"
|
||||||
class="wbf-cf-input"
|
class="wbf-cf-input"
|
||||||
@@ -1165,15 +1216,17 @@ class WBF_Shortcodes {
|
|||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endforeach; endif; ?>
|
||||||
|
|
||||||
|
<!-- Globaler Speichern-Button für Tab 1 -->
|
||||||
|
<div style="display:flex;align-items:center;gap:1rem;padding:.25rem 0 .5rem">
|
||||||
|
<button class="wbf-btn wbf-btn--primary" id="wbfSaveProfile" style="min-width:160px">
|
||||||
|
<i class="fas fa-save"></i> Alles speichern
|
||||||
|
</button>
|
||||||
|
<span class="wbf-msg" id="wbfProfileMsg"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<?php endif; /* end Tab 1 */ ?>
|
<?php endif; /* end Tab 1 */ ?>
|
||||||
|
|
||||||
@@ -1260,6 +1313,29 @@ class WBF_Shortcodes {
|
|||||||
══════════════════════════════════════════════════ -->
|
══════════════════════════════════════════════════ -->
|
||||||
<?php if ($is_own && $active_tab === 3): ?>
|
<?php if ($is_own && $active_tab === 3): ?>
|
||||||
|
|
||||||
|
<!-- Profil-Sichtbarkeit -->
|
||||||
|
<?php $pub = (int)($profile->profile_public ?? 1); ?>
|
||||||
|
<div class="wbf-profile-card">
|
||||||
|
<div class="wbf-profile-card__header">
|
||||||
|
<i class="fas fa-eye"></i> Profil-Sichtbarkeit
|
||||||
|
</div>
|
||||||
|
<div class="wbf-profile-card__body">
|
||||||
|
<div class="wbf-form-row" style="display:flex;align-items:center;gap:1rem">
|
||||||
|
<div>
|
||||||
|
<div style="font-size:.9rem;font-weight:600;margin-bottom:3px">Profil öffentlich sichtbar</div>
|
||||||
|
<div style="font-size:.8rem;color:var(--c-muted)">Wenn deaktiviert, können nur du selbst und Moderatoren dein Profil sehen.</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="wbfToggleProfileVis"
|
||||||
|
class="wbf-btn wbf-btn--sm<?php echo $pub?' wbf-btn--primary':''; ?>"
|
||||||
|
data-state="<?php echo $pub; ?>"
|
||||||
|
style="margin-left:auto;white-space:nowrap">
|
||||||
|
<i class="fas fa-<?php echo $pub?'eye':'eye-slash'; ?>"></i>
|
||||||
|
<?php echo $pub?'Öffentlich':'Privat'; ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Ignorierte Nutzer -->
|
<!-- Ignorierte Nutzer -->
|
||||||
<div class="wbf-profile-card">
|
<div class="wbf-profile-card">
|
||||||
<div class="wbf-profile-card__header">
|
<div class="wbf-profile-card__header">
|
||||||
@@ -1394,6 +1470,78 @@ class WBF_Shortcodes {
|
|||||||
|
|
||||||
<?php endif; /* end Tab 3 */ ?>
|
<?php endif; /* end Tab 3 */ ?>
|
||||||
|
|
||||||
|
<!-- ══════════════════════════════════════════════════
|
||||||
|
TAB 4 — Sicherheit (Passwort & E-Mail)
|
||||||
|
══════════════════════════════════════════════════ -->
|
||||||
|
<?php if ($is_own && $active_tab === 4): ?>
|
||||||
|
|
||||||
|
<!-- Passwort ändern -->
|
||||||
|
<div class="wbf-profile-card" style="border-color:rgba(0,180,216,.25)">
|
||||||
|
<div class="wbf-profile-card__header" style="background:rgba(0,180,216,.07);border-bottom-color:rgba(0,180,216,.18)">
|
||||||
|
<i class="fas fa-lock" style="color:var(--c-primary)"></i> Passwort ändern
|
||||||
|
</div>
|
||||||
|
<div class="wbf-profile-card__body">
|
||||||
|
<div class="wbf-form-row">
|
||||||
|
<label>Aktuelles Passwort</label>
|
||||||
|
<input type="password" id="wbfCurrentPassword" placeholder="Dein aktuelles Passwort" autocomplete="current-password">
|
||||||
|
</div>
|
||||||
|
<div class="wbf-profile-edit-grid">
|
||||||
|
<div class="wbf-form-row">
|
||||||
|
<label>Neues Passwort <small>(min. 6 Zeichen)</small></label>
|
||||||
|
<input type="password" id="wbfNewPassword" placeholder="Neues Passwort" autocomplete="new-password">
|
||||||
|
</div>
|
||||||
|
<div class="wbf-form-row">
|
||||||
|
<label>Neues Passwort wiederholen</label>
|
||||||
|
<input type="password" id="wbfNewPassword2" placeholder="Passwort bestätigen" autocomplete="new-password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="wbf-profile-card__footer">
|
||||||
|
<button class="wbf-btn wbf-btn--primary" id="wbfSavePassword">
|
||||||
|
<i class="fas fa-key"></i> Passwort ändern
|
||||||
|
</button>
|
||||||
|
<span class="wbf-msg" id="wbfPasswordMsg"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
|
|
||||||
|
<?php endif; /* end Tab 4 */ ?>
|
||||||
|
|
||||||
|
<!-- ══════════════════════════════════════════════════
|
||||||
|
TAB MC — Minecraft-Konto verknüpfen (Bridge)
|
||||||
|
Wird nur gerendert wenn MC Gallery Forum Bridge aktiv ist.
|
||||||
|
══════════════════════════════════════════════════ -->
|
||||||
|
<?php if ( $is_own && $active_tab === 'mc' && class_exists('MC_Gallery_Forum_Bridge') ) :
|
||||||
|
echo apply_filters('wbf_profile_tab_content', '', 'minecraft', $profile);
|
||||||
|
endif; /* end Tab MC */ ?>
|
||||||
|
|
||||||
</div><!-- /.wbf-profile-main -->
|
</div><!-- /.wbf-profile-main -->
|
||||||
</div><!-- /.wbf-profile-layout -->
|
</div><!-- /.wbf-profile-layout -->
|
||||||
</div>
|
</div>
|
||||||
@@ -1608,7 +1756,7 @@ class WBF_Shortcodes {
|
|||||||
if ($maint_s === '1' && (!$cur_s || WBF_Roles::level($cur_s->role) < 50)) return self::view_maintenance();
|
if ($maint_s === '1' && (!$cur_s || WBF_Roles::level($cur_s->role) < 50)) return self::view_maintenance();
|
||||||
$query = sanitize_text_field($_GET['q'] ?? '');
|
$query = sanitize_text_field($_GET['q'] ?? '');
|
||||||
$current = WBF_Auth::get_current_user();
|
$current = WBF_Auth::get_current_user();
|
||||||
$results = mb_strlen($query) >= 2 ? WBF_DB::search($query, 40) : [];
|
$results = mb_strlen($query) >= 2 ? WBF_DB::search($query, 40, $current) : [];
|
||||||
ob_start(); ?>
|
ob_start(); ?>
|
||||||
<div class="wbf-wrap">
|
<div class="wbf-wrap">
|
||||||
<?php self::render_topbar($current); ?>
|
<?php self::render_topbar($current); ?>
|
||||||
@@ -1711,7 +1859,7 @@ class WBF_Shortcodes {
|
|||||||
<?php echo esc_html($current->display_name); ?>
|
<?php echo esc_html($current->display_name); ?>
|
||||||
<?php echo self::role_badge($current->role); ?>
|
<?php echo self::role_badge($current->role); ?>
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo esc_url(wbf_get_forum_url() . '?wbf_do_logout=1'); ?>" class="wbf-btn wbf-btn--sm wbf-btn--outline"><?php echo esc_html(wbf_get_settings()['btn_logout']); ?></a>
|
<a href="<?php echo esc_url(wp_nonce_url(wbf_get_forum_url() . '?wbf_do_logout=1', 'wbf_logout')); ?>" class="wbf-btn wbf-btn--sm wbf-btn--outline"><?php echo esc_html(wbf_get_settings()['btn_logout']); ?></a>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<button class="wbf-btn wbf-btn--sm" id="wbfOpenLogin"><?php echo esc_html(wbf_get_settings()['btn_login']); ?></button>
|
<button class="wbf-btn wbf-btn--sm" id="wbfOpenLogin"><?php echo esc_html(wbf_get_settings()['btn_login']); ?></button>
|
||||||
<button class="wbf-btn wbf-btn--sm wbf-btn--primary" id="wbfOpenRegister"><?php echo esc_html(wbf_get_settings()['btn_register']); ?></button>
|
<button class="wbf-btn wbf-btn--sm wbf-btn--primary" id="wbfOpenRegister"><?php echo esc_html(wbf_get_settings()['btn_register']); ?></button>
|
||||||
|
|||||||
Reference in New Issue
Block a user