diff --git a/wp-business-forum.php b/wp-business-forum.php index 9eed21d..99fc5ca 100644 --- a/wp-business-forum.php +++ b/wp-business-forum.php @@ -3,7 +3,7 @@ * 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.4 + * Version: 1.0.5 * Author: M_Viper * Author URI: https://m-viper.de * Text Domain: wp-business-forum @@ -14,7 +14,7 @@ if ( ! defined( 'ABSPATH' ) ) exit; define( 'WBF_PATH', plugin_dir_path( __FILE__ ) ); define( 'WBF_URL', plugin_dir_url( __FILE__ ) ); -define( 'WBF_VERSION', '1.0.4' ); +define( 'WBF_VERSION', '1.0.5' ); require_once WBF_PATH . 'includes/class-forum-db.php'; require_once WBF_PATH . 'includes/class-forum-roles.php'; @@ -24,6 +24,9 @@ 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'; @@ -103,14 +106,13 @@ function wbf_get_forum_url() { $url = get_permalink( $page_id ); if ( $url ) return $url; } - // 2. Fallback: Seite mit [business_forum] Shortcode suchen - $pages = get_posts([ - 'post_type' => 'page', - 'post_status' => 'publish', - 'posts_per_page' => 1, - 's' => 'business_forum', - ]); - if ( $pages ) return get_permalink( $pages[0]->ID ); + // 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('/'); } @@ -118,7 +120,11 @@ function wbf_get_forum_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'], time(), true ); + 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 ); @@ -285,4 +291,302 @@ add_action( 'admin_init', function() { delete_transient( WBF_UPDATE_TRANSIENT ); wp_safe_redirect( remove_query_arg(['wbf_refresh_update','_wpnonce']) ); exit; -} ); \ No newline at end of file +} ); + +// ══════════════════════════════════════════════════════════════════════════════ +// ── 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(''); + 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(' 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(' Einloggen'); + } + }, 'json').fail(function (xhr) { + $box.find('.wbf-login-msg').text('Verbindungsfehler (' + xhr.status + ')').css('color', '#f05252').show(); + $btn.prop('disabled', false).html(' Einloggen'); + }); + }); + + function wbfShow2faLoginStep($box) { + // Altes Modal entfernen falls vorhanden + $('#wbf2faLoginModal').remove(); + + var modal = + '
' + + '
' + + '
' + + '🛡️' + + '
' + + 'Zwei-Faktor-Authentifizierung' + + '

Gib den Code aus deiner Authenticator-App ein.

' + + '
' + + '
' + + '' + + '
' + + '' + + '' + + '
' + + '' + + '
' + + '
'; + + $('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(''); + 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(' 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(' Code bestätigen'); + } + }, 'json').fail(function (xhr) { + $step.find('.wbf-2fa-msg').text('Verbindungsfehler.').css('color', '#f05252').show(); + $btn.prop('disabled', false).html(' 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(' 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(' 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( + 'otpauth Link' + ); + } + // 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(' 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(''); + 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(' 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(' 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(' Bestätigen & aktivieren'); + } + }, 'json').fail(function () { + $msg.text('Verbindungsfehler.').css('color', '#f05252').show(); + $btn.prop('disabled', false).html(' 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(''); + 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(' 2FA deaktivieren'); + return; + } + if (code.length !== 6) { + $msg.text('Bitte 6-stelligen Code eingeben.').css('color', '#f05252').show(); + $btn.prop('disabled', false) + .html(' 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(' 2FA deaktivieren'); + } + }, 'json').fail(function () { + $msg.text('Verbindungsfehler.').css('color', '#f05252').show(); + $btn.prop('disabled', false) + .html(' 2FA deaktivieren'); + }); + }); + +}(jQuery)); +JS; +} \ No newline at end of file