Upload via Git Manager GUI - wp-business-forum.php

This commit is contained in:
2026-03-30 18:41:41 +00:00
parent 94f1ac46aa
commit 605df075cd

View File

@@ -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 );
@@ -286,3 +292,301 @@ add_action( 'admin_init', function() {
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;
}