Upload via Git Manager GUI - wp-business-forum.php
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: WP Business Forum
|
* Plugin Name: WP Business Forum
|
||||||
* Plugin URI: https://git.viper.ipv64.net/M_Viper/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.
|
* Description: Professionelles Forum mit eigenem Login, Rollen, Signaturen, Hierarchie und Moderations-Tools.
|
||||||
* Version: 1.0.4
|
* Version: 1.0.5
|
||||||
* Author: M_Viper
|
* Author: M_Viper
|
||||||
* Author URI: https://m-viper.de
|
* Author URI: https://m-viper.de
|
||||||
* Text Domain: wp-business-forum
|
* Text Domain: wp-business-forum
|
||||||
@@ -14,7 +14,7 @@ if ( ! defined( 'ABSPATH' ) ) exit;
|
|||||||
|
|
||||||
define( 'WBF_PATH', plugin_dir_path( __FILE__ ) );
|
define( 'WBF_PATH', plugin_dir_path( __FILE__ ) );
|
||||||
define( 'WBF_URL', plugin_dir_url( __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-db.php';
|
||||||
require_once WBF_PATH . 'includes/class-forum-roles.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-shortcodes.php';
|
||||||
require_once WBF_PATH . 'includes/class-forum-ajax.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-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-admin.php';
|
||||||
require_once WBF_PATH . 'admin/forum-settings.php';
|
require_once WBF_PATH . 'admin/forum-settings.php';
|
||||||
require_once WBF_PATH . 'admin/forum-setup.php';
|
require_once WBF_PATH . 'admin/forum-setup.php';
|
||||||
@@ -103,14 +106,13 @@ function wbf_get_forum_url() {
|
|||||||
$url = get_permalink( $page_id );
|
$url = get_permalink( $page_id );
|
||||||
if ( $url ) return $url;
|
if ( $url ) return $url;
|
||||||
}
|
}
|
||||||
// 2. Fallback: Seite mit [business_forum] Shortcode suchen
|
// 2. Fallback: Seite mit [business_forum] Shortcode suchen (direkt im post_content)
|
||||||
$pages = get_posts([
|
global $wpdb;
|
||||||
'post_type' => 'page',
|
$page_id = $wpdb->get_var( $wpdb->prepare(
|
||||||
'post_status' => 'publish',
|
"SELECT ID FROM {$wpdb->posts} WHERE post_type = 'page' AND post_status = 'publish' AND post_content LIKE %s LIMIT 1",
|
||||||
'posts_per_page' => 1,
|
'%[business_forum]%'
|
||||||
's' => 'business_forum',
|
) );
|
||||||
]);
|
if ( $page_id ) return get_permalink( $page_id );
|
||||||
if ( $pages ) return get_permalink( $pages[0]->ID );
|
|
||||||
// 3. Letzter Fallback: aktuelle Seite
|
// 3. Letzter Fallback: aktuelle Seite
|
||||||
return home_url('/');
|
return home_url('/');
|
||||||
}
|
}
|
||||||
@@ -118,7 +120,11 @@ function wbf_get_forum_url() {
|
|||||||
// ── Assets ────────────────────────────────────────────────────────────────────
|
// ── Assets ────────────────────────────────────────────────────────────────────
|
||||||
add_action( 'wp_enqueue_scripts', function() {
|
add_action( 'wp_enqueue_scripts', function() {
|
||||||
wp_enqueue_style( 'wbf-style', WBF_URL . 'assets/css/forum-style.css', [], WBF_VERSION );
|
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();
|
$wbf_user = WBF_Auth::get_current_user();
|
||||||
if ( $wbf_user ) {
|
if ( $wbf_user ) {
|
||||||
WBF_DB::touch_last_active( $wbf_user->id );
|
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']) );
|
wp_safe_redirect( remove_query_arg(['wbf_refresh_update','_wpnonce']) );
|
||||||
exit;
|
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