Upload via Git Manager GUI - wp-business-forum.php
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user