Files
WP-Business-Forum/wp-business-forum.php

599 lines
28 KiB
PHP

<?php
/*
Plugin Name: WP Business Forum
Plugin URI:https://git.viper.ipv64.net/M_Viper/WP-Business-Forum
Description: Professionelles Forum mit eigenem Login, Rollen, Signaturen, Hierarchie und Moderations-Tools.
Version: 1.0.5
Author: M_Viper
Author URI: https://m-viper.de
Requires at least: 6.8
Tested up to: 6.8
PHP Version: 7.4
License: GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: wp-business-forum
Tags: forum, community, roles, moderation, shortcode, bbcode, admin, frontend, security, export-import
Support: [Discord Support](https://discord.com/invite/FdRs4BRd8D)
Support: [Telegram Support](https://t.me/M_Viper04)
*/
if ( ! defined( 'ABSPATH' ) ) exit;
define( 'WBF_PATH', plugin_dir_path( __FILE__ ) );
define( 'WBF_URL', plugin_dir_url( __FILE__ ) );
define( 'WBF_VERSION', '1.0.5' );
require_once WBF_PATH . 'includes/class-forum-db.php';
require_once WBF_PATH . 'includes/class-forum-roles.php';
require_once WBF_PATH . 'includes/class-forum-levels.php';
require_once WBF_PATH . 'includes/class-forum-bbcode.php';
require_once WBF_PATH . 'includes/class-forum-auth.php';
require_once WBF_PATH . 'includes/class-forum-shortcodes.php';
require_once WBF_PATH . 'includes/class-forum-ajax.php';
require_once WBF_PATH . 'includes/class-forum-export.php';
require_once WBF_PATH . 'includes/class-forum-mc-bridge.php';
require_once WBF_PATH . 'includes/class-forum-totp.php';
require_once WBF_PATH . 'includes/forum-statusapi.php';
require_once WBF_PATH . 'admin/forum-admin.php';
require_once WBF_PATH . 'admin/forum-settings.php';
require_once WBF_PATH . 'admin/forum-setup.php';
// ── Aktivierung ────────────────────────────────────────────────────────────────
register_activation_hook( __FILE__, function() {
WBF_DB::install();
// Transient für einmalige Weiterleitung zum Setup-Wizard setzen
set_transient( 'wbf_activation_redirect', true, 30 );
});
// ── Export / Import Hooks ─────────────────────────────────────────────────────
add_action( 'plugins_loaded', function() {
WBF_Export::hooks();
}, 5 );
// ── DB-Schema sicherstellen (läuft bei jedem Seitenaufruf, sehr günstig) ─────
// Stellt sicher dass neue Spalten auch auf bestehenden Installs vorhanden sind,
// ohne dass das Plugin erneut deaktiviert/aktiviert werden muss.
add_action( 'plugins_loaded', function() {
$db_ver = (int) get_option( 'wbf_db_version', 0 );
if ( $db_ver < 2 ) {
global $wpdb;
// profile_public: Sicherheits-kritisch — muss immer existieren
$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" );
// Alle bestehenden User explizit auf öffentlich setzen
$wpdb->query( "UPDATE {$wpdb->prefix}forum_users SET profile_public = 1 WHERE profile_public IS NULL" );
}
update_option( 'wbf_db_version', 2 );
}
}, 10 );
// ── Session frühzeitig starten (PHP 8.3 Fix) ────────────────────────────────
// session_start() MUSS vor jedem HTML-Output laufen.
// plugins_loaded (Prio 1) ist der früheste sichere Zeitpunkt in WordPress.
// Der 'init'-Hook (in class-forum-auth.php) läuft als Fallback weiterhin,
// aber dieser frühe Aufruf verhindert den PHP 8.3 E_WARNING "headers already sent".
add_action( 'plugins_loaded', function() {
WBF_Auth::init();
}, 1 );
// ── Superadmin-Sync ───────────────────────────────────────────────────────────
add_action( 'wp_login', function() { WBF_Roles::sync_superadmin(); } );
add_action( 'init', function() { WBF_Roles::sync_superadmin(); } );
// ── Body-Klasse ───────────────────────────────────────────────────────────────
add_filter( 'body_class', function( $classes ) {
global $post;
if ( $post && has_shortcode( $post->post_content, 'business_forum' ) ) {
$classes[] = 'wbf-forum-page';
}
return $classes;
});
// ── Cron: Abgelaufene Sperren aufheben ────────────────────────────────────────
add_action( 'wbf_check_expired_bans', function() {
WBF_DB::check_expired_bans();
} );
if ( ! wp_next_scheduled( 'wbf_check_expired_bans' ) ) {
wp_schedule_event( time(), 'hourly', 'wbf_check_expired_bans' );
}
register_deactivation_hook( __FILE__, function() {
wp_clear_scheduled_hook( 'wbf_check_expired_bans' );
wp_clear_scheduled_hook( 'wbf_check_for_updates' );
} );
// ── Forum-URL Hilfsfunktion ───────────────────────────────────────────────────
function wbf_get_forum_url() {
// 1. Gespeicherte Seite aus dem Setup-Wizard
$page_id = get_option('wbf_forum_page_id');
if ( $page_id ) {
$url = get_permalink( $page_id );
if ( $url ) return $url;
}
// 2. Fallback: Seite mit [business_forum] Shortcode suchen (direkt im post_content)
global $wpdb;
$page_id = $wpdb->get_var( $wpdb->prepare(
"SELECT ID FROM {$wpdb->posts} WHERE post_type = 'page' AND post_status = 'publish' AND post_content LIKE %s LIMIT 1",
'%[business_forum]%'
) );
if ( $page_id ) return get_permalink( $page_id );
// 3. Letzter Fallback: aktuelle Seite
return home_url('/');
}
// ── Assets ────────────────────────────────────────────────────────────────────
add_action( 'wp_enqueue_scripts', function() {
wp_enqueue_style( 'wbf-style', WBF_URL . 'assets/css/forum-style.css', [], WBF_VERSION );
wp_enqueue_script( 'wbf-script', WBF_URL . 'assets/js/forum-script.js', ['jquery'], WBF_VERSION, true );
// 2FA: QR-Code-Bibliothek (nur laden wenn User eingeloggt oder auf Loginseite)
wp_enqueue_script( 'qrcodejs', 'https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js', [], '1.0.0', true );
wp_add_inline_script( 'wbf-script', wbf_get_2fa_inline_js(), 'after' );
$wbf_user = WBF_Auth::get_current_user();
if ( $wbf_user ) {
WBF_DB::touch_last_active( $wbf_user->id );
}
wp_localize_script( 'wbf-script', 'WBF', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wbf_nonce'),
'logged_in' => WBF_Auth::is_forum_logged_in() ? 'yes' : 'no',
'auto_logout_minutes' => (int)( wbf_get_settings()['auto_logout_minutes'] ?? 30 ),
'my_id' => $wbf_user ? (int)$wbf_user->id : 0,
'unread_dm' => $wbf_user ? WBF_DB::count_unread_messages($wbf_user->id) : 0,
'forum_url' => wbf_get_forum_url(),
'reactions' => WBF_DB::get_allowed_reactions(),
]);
});
// ══════════════════════════════════════════════════════════════════════════════
// ── Update-Checker ────────────────────────────────────────────────────────────
// Prüft täglich gegen die Gitea-Releases-API ob eine neue Version verfügbar ist.
// Releases-URL: https://git.viper.ipv64.net/M_Viper/WP-Business-Forum/releases
// ══════════════════════════════════════════════════════════════════════════════
define( 'WBF_UPDATE_API', 'https://git.viper.ipv64.net/api/v1/repos/M_Viper/WP-Business-Forum/releases?limit=1&page=1' );
define( 'WBF_RELEASES_PAGE', 'https://git.viper.ipv64.net/M_Viper/WP-Business-Forum/releases' );
define( 'WBF_UPDATE_TRANSIENT','wbf_update_check' );
/**
* Holt die neueste Release-Info von Gitea (gecacht per Transient, 12h).
* Gibt null zurück wenn kein Update verfügbar oder API nicht erreichbar.
*
* @return array|null ['version'=>string, 'url'=>string, 'name'=>string, 'published'=>string, 'body'=>string]
*/
function wbf_get_latest_release() {
$cached = get_transient( WBF_UPDATE_TRANSIENT );
if ( $cached !== false ) {
return $cached ?: null; // false = noch nie gecacht, '' = kein Update
}
$response = wp_remote_get( WBF_UPDATE_API, [
'timeout' => 8,
'user-agent' => 'WP-Business-Forum/' . WBF_VERSION . '; ' . get_bloginfo('url'),
'sslverify' => true,
] );
if ( is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200 ) {
// Bei Fehler 1h warten bevor erneut versucht
set_transient( WBF_UPDATE_TRANSIENT, '', HOUR_IN_SECONDS );
return null;
}
$body = wp_remote_retrieve_body( $response );
$releases = json_decode( $body, true );
if ( empty($releases) || ! is_array($releases) || empty($releases[0]) ) {
set_transient( WBF_UPDATE_TRANSIENT, '', 12 * HOUR_IN_SECONDS );
return null;
}
$latest = $releases[0];
$version = ltrim( $latest['tag_name'] ?? '', 'v' ); // "v1.2.0" → "1.2.0"
$info = [
'version' => $version,
'url' => $latest['html_url'] ?? WBF_RELEASES_PAGE,
'name' => $latest['name'] ?? $latest['tag_name'] ?? $version,
'published' => $latest['published_at'] ?? '',
'body' => wp_strip_all_tags( $latest['body'] ?? '' ),
];
// 12 Stunden cachen
set_transient( WBF_UPDATE_TRANSIENT, $info, 12 * HOUR_IN_SECONDS );
return $info;
}
/**
* Prüft ob ein Update verfügbar ist.
* Gibt die Release-Info zurück wenn Gitea-Version > installierte Version.
*/
function wbf_update_available() {
$latest = wbf_get_latest_release();
if ( ! $latest || empty($latest['version']) ) return null;
if ( version_compare( $latest['version'], WBF_VERSION, '>' ) ) {
return $latest;
}
return null;
}
// ── Cron: täglich Update prüfen (Cache warm halten) ──────────────────────────
add_action( 'wbf_check_for_updates', function() {
delete_transient( WBF_UPDATE_TRANSIENT );
wbf_get_latest_release();
} );
if ( ! wp_next_scheduled( 'wbf_check_for_updates' ) ) {
wp_schedule_event( time(), 'twicedaily', 'wbf_check_for_updates' );
}
// ── Admin-Notice wenn Update verfügbar ───────────────────────────────────────
add_action( 'admin_notices', function() {
if ( ! current_user_can('manage_options') ) return;
$update = wbf_update_available();
if ( ! $update ) return;
// Notice ausblenden wenn der User sie weggeklickt hat (per GET-Parameter)
if ( isset($_GET['wbf_dismiss_update']) && check_admin_referer('wbf_dismiss_update') ) {
set_transient( 'wbf_update_dismissed_' . WBF_VERSION, $update['version'], 7 * DAY_IN_SECONDS );
wp_safe_redirect( remove_query_arg(['wbf_dismiss_update','_wpnonce']) );
exit;
}
$dismissed = get_transient( 'wbf_update_dismissed_' . WBF_VERSION );
if ( $dismissed === $update['version'] ) return;
$dismiss_url = wp_nonce_url(
add_query_arg('wbf_dismiss_update', '1'),
'wbf_dismiss_update'
);
$changelog_url = esc_url( $update['url'] );
$new_ver = esc_html( $update['version'] );
$cur_ver = esc_html( WBF_VERSION );
echo "
<div class=\"notice notice-warning is-dismissible\" style=\"border-left-color:#f59e0b;padding:12px 15px\">
<div style=\"display:flex;align-items:center;gap:14px;flex-wrap:wrap\">
<span style=\"font-size:1.6rem\">🔔</span>
<div>
<strong style=\"font-size:.95rem\">WP Business Forum — Update verfügbar!</strong>
<p style=\"margin:.3rem 0 0;color:#374151\">
Version <strong>{$new_ver}</strong> ist verfügbar. Du verwendest <strong>{$cur_ver}</strong>.
</p>
</div>
<div style=\"display:flex;gap:8px;margin-left:auto\">
<a href=\"{$changelog_url}\" target=\"_blank\" rel=\"noopener\"
class=\"button button-primary\" style=\"background:#f59e0b;border-color:#d97706\">
📋 Changelog & Download
</a>
<a href=\"" . esc_url($dismiss_url) . "\" class=\"button\">Später erinnern</a>
</div>
</div>
</div>";
} );
// ── Update-Badge im WP-Admin-Menü ─────────────────────────────────────────────
add_action( 'admin_menu', function() {
$update = wbf_update_available();
if ( ! $update ) return;
global $menu;
if ( ! is_array($menu) ) return;
foreach ( $menu as &$item ) {
if ( isset($item[2]) && $item[2] === 'wbf-admin' ) {
$item[0] .= ' <span class="update-plugins"><span class="plugin-count">1</span></span>';
break;
}
}
}, 999 );
// ── Manuellen Cache-Reset erlauben (für die Admin-UI) ─────────────────────────
add_action( 'admin_init', function() {
if ( ! isset($_GET['wbf_refresh_update']) ) return;
if ( ! current_user_can('manage_options') ) return;
if ( ! check_admin_referer('wbf_refresh_update') ) return;
delete_transient( WBF_UPDATE_TRANSIENT );
wp_safe_redirect( remove_query_arg(['wbf_refresh_update','_wpnonce']) );
exit;
} );
// ══════════════════════════════════════════════════════════════════════════════
// ── 2FA Inline-JavaScript ─────────────────────────────────────────────────────
// Liefert das JS für: Login-2FA-Step, Profil-Setup-Wizard, Deaktivierung
// ══════════════════════════════════════════════════════════════════════════════
function wbf_get_2fa_inline_js() {
return <<<'JS'
(function ($) {
'use strict';
/* ══════════════════════════════════════════════════════════════
2FA — Login-Flow
Wenn der Server 2fa_required:true zurückgibt, zeigt das
Login-Formular eine Code-Eingabe anstatt die Fehlermeldung.
══════════════════════════════════════════════════════════════ */
// Original-Login-Handler überschreiben um 2FA abzufangen
$(document).off('click', '.wbf-login-submit-btn');
$(document).on('click', '.wbf-login-submit-btn', function () {
var $btn = $(this).prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
var $box = $(this).closest('.wbf-auth-box');
// 2FA-Panel verstecken falls sichtbar
$box.find('.wbf-2fa-login-step').remove();
$.post(WBF.ajax_url, {
action: 'wbf_login',
nonce: WBF.nonce,
username: $box.find('.wbf-field-username').val(),
password: $box.find('.wbf-field-password').val(),
remember_me: $box.find('.wbf-field-remember').is(':checked') ? '1' : ''
}, function (res) {
if (res && res.success) {
location.reload();
} else if (res && res.data && res.data['2fa_required']) {
// 2FA erforderlich — Code-Eingabe einblenden
$btn.prop('disabled', false).html('<i class="fas fa-sign-in-alt"></i> Einloggen');
wbfShow2faLoginStep($box);
} else {
var msg = (res && res.data && res.data.message) ? res.data.message : 'Fehler';
$box.find('.wbf-login-msg').text(msg).css('color', '#f05252').show();
setTimeout(function () { $box.find('.wbf-login-msg').fadeOut(); }, 4000);
$btn.prop('disabled', false).html('<i class="fas fa-sign-in-alt"></i> Einloggen');
}
}, 'json').fail(function (xhr) {
$box.find('.wbf-login-msg').text('Verbindungsfehler (' + xhr.status + ')').css('color', '#f05252').show();
$btn.prop('disabled', false).html('<i class="fas fa-sign-in-alt"></i> Einloggen');
});
});
function wbfShow2faLoginStep($box) {
// Altes Modal entfernen falls vorhanden
$('#wbf2faLoginModal').remove();
var modal =
'<div id="wbf2faLoginModal" class="wbf-2fa-modal-overlay">' +
'<div class="wbf-2fa-modal-box">' +
'<div class="wbf-2fa-modal-header">' +
'<span class="wbf-2fa-modal-icon">🛡️</span>' +
'<div>' +
'<strong class="wbf-2fa-modal-title">Zwei-Faktor-Authentifizierung</strong>' +
'<p class="wbf-2fa-modal-sub">Gib den Code aus deiner Authenticator-App ein.</p>' +
'</div>' +
'</div>' +
'<input type="text" class="wbf-2fa-code-input" placeholder="1 2 3 4 5 6"' +
' maxlength="7" inputmode="numeric" autocomplete="one-time-code">' +
'<div class="wbf-2fa-modal-actions">' +
'<button class="wbf-btn wbf-btn--primary wbf-2fa-submit-btn">' +
'<i class="fas fa-check"></i> Bestätigen' +
'</button>' +
'<button class="wbf-btn wbf-2fa-cancel-btn">' +
'<i class="fas fa-xmark"></i> Abbrechen' +
'</button>' +
'</div>' +
'<span class="wbf-2fa-msg"></span>' +
'</div>' +
'</div>';
$('body').append(modal);
// Kurze Verzögerung für CSS-Transition
setTimeout(function () {
$('#wbf2faLoginModal').addClass('wbf-2fa-modal--visible');
$('#wbf2faLoginModal .wbf-2fa-code-input').focus();
}, 20);
}
// 2FA-Code absenden
$(document).on('click', '.wbf-2fa-submit-btn', function () {
var $step = $(this).closest('.wbf-2fa-modal-box');
var $btn = $(this).prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
var code = $step.find('.wbf-2fa-code-input').val().replace(/\s+/g, '');
if (code.length !== 6) {
$step.find('.wbf-2fa-msg').text('Bitte 6-stelligen Code eingeben.').css('color', '#f05252').show();
$btn.prop('disabled', false).html('<i class="fas fa-check"></i> Code bestätigen');
return;
}
$.post(WBF.ajax_url, {
action: 'wbf_2fa_verify_login',
code: code
}, function (res) {
if (res && res.success) {
location.reload();
} else {
var msg = (res && res.data && res.data.message) ? res.data.message : 'Ungültiger Code.';
$step.find('.wbf-2fa-msg').text(msg).css('color', '#f05252').show();
$step.find('.wbf-2fa-code-input').val('').focus();
$btn.prop('disabled', false).html('<i class="fas fa-check"></i> Code bestätigen');
}
}, 'json').fail(function (xhr) {
$step.find('.wbf-2fa-msg').text('Verbindungsfehler.').css('color', '#f05252').show();
$btn.prop('disabled', false).html('<i class="fas fa-check"></i> Code bestätigen');
});
});
// Enter-Taste im Code-Feld
$(document).on('keydown', '.wbf-2fa-code-input', function (e) {
if (e.key === 'Enter') $(this).closest('.wbf-2fa-login-step').find('.wbf-2fa-submit-btn').click();
});
// Abbrechen: 2FA-Modal schließen
$(document).on('click', '.wbf-2fa-cancel-btn', function () {
var $modal = $('#wbf2faLoginModal');
$modal.removeClass('wbf-2fa-modal--visible');
setTimeout(function () { $modal.remove(); }, 250);
});
// Klick außerhalb des Modals schließt es
$(document).on('click', '#wbf2faLoginModal', function (e) {
if ($(e.target).is('#wbf2faLoginModal')) {
var $modal = $(this);
$modal.removeClass('wbf-2fa-modal--visible');
setTimeout(function () { $modal.remove(); }, 250);
}
});
/* ══════════════════════════════════════════════════════════════
2FA — Profil-Setup-Wizard
══════════════════════════════════════════════════════════════ */
// Schritt 1 starten: Secret + QR generieren
$(document).on('click', '#wbf2faStartBtn', function () {
var $btn = $(this).prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Lädt…');
$.post(WBF.ajax_url, {
action: 'wbf_2fa_setup_begin',
nonce: WBF.nonce
}, function (res) {
if (!res || !res.success) {
$btn.prop('disabled', false).html('<i class="fas fa-shield-halved"></i> 2FA einrichten');
alert((res && res.data && res.data.message) ? res.data.message : 'Fehler');
return;
}
var secret = res.data.secret;
var uri = res.data.uri;
// QR-Code rendern (qrcodejs)
$('#wbf2faQr').empty();
if (typeof QRCode !== 'undefined') {
// QR-Code in isolierten Wrapper einbetten (kein Flex-Kontext)
var qrEl = document.getElementById('wbf2faQr');
qrEl.innerHTML = '';
new QRCode(qrEl, {
text: uri,
width: 200,
height: 200,
correctLevel: QRCode.CorrectLevel.M
});
// Kein JS-Eingriff nötig — CSS übernimmt Größe + img-Verstecken
} else {
// Fallback: Link anzeigen
$('#wbf2faQr').html(
'<a href="' + uri + '" style="font-size:.75rem;word-break:break-all">otpauth Link</a>'
);
}
// Secret für manuelle Eingabe formatiert anzeigen (Leerzeichen alle 4 Zeichen)
var fmt = secret.replace(/=/g, '').replace(/(.{4})/g, '$1 ').trim();
$('#wbf2faSecret').text(fmt);
// Panels tauschen
$('#wbf2faInactive').hide();
$('#wbf2faStep1').fadeIn(200);
}, 'json').fail(function () {
$btn.prop('disabled', false).html('<i class="fas fa-shield-halved"></i> 2FA einrichten');
alert('Verbindungsfehler. Bitte Seite neu laden.');
});
});
// Weiter zu Schritt 2
$(document).on('click', '#wbf2faToStep2', function () {
$('#wbf2faStep1').hide();
$('#wbf2faStep2').fadeIn(200);
$('#wbf2faVerifyCode').focus();
});
// Zurück zu Schritt 1
$(document).on('click', '#wbf2faBackBtn', function () {
$('#wbf2faStep2').hide();
$('#wbf2faStep1').fadeIn(200);
});
// Schritt 2: Code bestätigen und 2FA aktivieren
$(document).on('click', '#wbf2faVerifyBtn', function () {
var $btn = $(this).prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
var $msg = $('#wbf2faVerifyMsg');
var code = $('#wbf2faVerifyCode').val().replace(/\s+/g, '');
if (code.length !== 6) {
$msg.text('Bitte 6-stelligen Code eingeben.').css('color', '#f05252').show();
$btn.prop('disabled', false).html('<i class="fas fa-check"></i> Bestätigen & aktivieren');
return;
}
$.post(WBF.ajax_url, {
action: 'wbf_2fa_setup_verify',
nonce: WBF.nonce,
code: code
}, function (res) {
if (res && res.success) {
$('#wbf2faStep2').hide();
$('#wbf2faStep3').fadeIn(300);
// Badge im Header aktualisieren
$('#wbf2faCard .wbf-2fa-badge')
.removeClass('wbf-2fa-badge--off')
.addClass('wbf-2fa-badge--on')
.html('<i class="fas fa-check-circle"></i> Aktiv');
// Nach 2 Sek. Seite neu laden damit der Header-Status stimmt
setTimeout(function () { location.reload(); }, 2500);
} else {
var msg = (res && res.data && res.data.message) ? res.data.message : 'Ungültiger Code.';
$msg.text(msg).css('color', '#f05252').show();
$('#wbf2faVerifyCode').val('').focus();
$btn.prop('disabled', false).html('<i class="fas fa-check"></i> Bestätigen & aktivieren');
}
}, 'json').fail(function () {
$msg.text('Verbindungsfehler.').css('color', '#f05252').show();
$btn.prop('disabled', false).html('<i class="fas fa-check"></i> Bestätigen & aktivieren');
});
});
// Enter-Taste im Verifikationsfeld
$(document).on('keydown', '#wbf2faVerifyCode', function (e) {
if (e.key === 'Enter') $('#wbf2faVerifyBtn').click();
});
/* ══════════════════════════════════════════════════════════════
2FA — Deaktivierung (Profil)
══════════════════════════════════════════════════════════════ */
$(document).on('click', '#wbf2faDisableBtn', function () {
var $btn = $(this).prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i>');
var $msg = $('#wbf2faDisableMsg');
var pw = $('#wbf2faDisablePw').val();
var code = $('#wbf2faDisableCode').val().replace(/\s+/g, '');
if (!pw) {
$msg.text('Bitte Passwort eingeben.').css('color', '#f05252').show();
$btn.prop('disabled', false)
.html('<i class="fas fa-shield-xmark"></i> 2FA deaktivieren');
return;
}
if (code.length !== 6) {
$msg.text('Bitte 6-stelligen Code eingeben.').css('color', '#f05252').show();
$btn.prop('disabled', false)
.html('<i class="fas fa-shield-xmark"></i> 2FA deaktivieren');
return;
}
$.post(WBF.ajax_url, {
action: 'wbf_2fa_disable',
nonce: WBF.nonce,
password: pw,
code: code
}, function (res) {
if (res && res.success) {
$msg.text('✔ ' + (res.data.message || '2FA deaktiviert.')).css('color', '#56cf7e').show();
setTimeout(function () { location.reload(); }, 1500);
} else {
var msg = (res && res.data && res.data.message) ? res.data.message : 'Fehler.';
$msg.text(msg).css('color', '#f05252').show();
$('#wbf2faDisableCode').val('').focus();
$btn.prop('disabled', false)
.html('<i class="fas fa-shield-xmark"></i> 2FA deaktivieren');
}
}, 'json').fail(function () {
$msg.text('Verbindungsfehler.').css('color', '#f05252').show();
$btn.prop('disabled', false)
.html('<i class="fas fa-shield-xmark"></i> 2FA deaktivieren');
});
});
}(jQuery));
JS;
}