Update from Git Manager GUI
This commit is contained in:
@@ -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' ] );
|
||||
Reference in New Issue
Block a user