480 lines
21 KiB
PHP
480 lines
21 KiB
PHP
<?php
|
|
/**
|
|
* WBF_MC_Bridge — Minecraft ↔ Forum Verknüpfung & Ingame-Benachrichtigungen
|
|
*
|
|
* Dieses Modul verbindet das WP Business Forum mit dem BungeeCord StatusAPI Plugin.
|
|
*
|
|
* Features:
|
|
* - Account-Verknüpfung: Forum-User ↔ MC-UUID (über Token-System)
|
|
* - Push-Benachrichtigungen: Neue Antwort/Erwähnung/PN → Ingame-Nachricht
|
|
* - REST API Endpoints für die BungeeCord-Seite
|
|
*
|
|
* Einbindung in wp-business-forum.php:
|
|
* require_once WBF_PATH . 'includes/class-forum-mc-bridge.php';
|
|
*
|
|
* Konfiguration in WBF-Einstellungen (Admin → Forum → Einstellungen):
|
|
* mc_bridge_enabled = true/false
|
|
* mc_bridge_api_url = http://server-ip:9191 (StatusAPI URL)
|
|
* mc_bridge_api_secret = Shared Secret für API-Authentifizierung
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
|
|
|
class WBF_MC_Bridge {
|
|
|
|
/** Meta-Keys in forum_user_meta */
|
|
const META_MC_UUID = 'mc_uuid';
|
|
const META_MC_NAME = 'mc_name';
|
|
const META_LINK_TOKEN = 'mc_link_token';
|
|
const META_LINK_EXPIRY = 'mc_link_token_expires';
|
|
|
|
/**
|
|
* Hooks registrieren — wird beim Plugin-Laden aufgerufen.
|
|
*/
|
|
public static function init() {
|
|
// Hook: Wird in der modifizierten WBF_DB::create_notification() gefeuert
|
|
add_action( 'wbf_notification_created', [ __CLASS__, 'on_notification' ], 10, 4 );
|
|
|
|
// REST API Endpoints für BungeeCord
|
|
add_action( 'rest_api_init', [ __CLASS__, 'register_rest_routes' ] );
|
|
|
|
// AJAX: Token generieren (für eingeloggte Forum-User)
|
|
add_action( 'wp_ajax_wbf_mc_generate_token', [ __CLASS__, 'ajax_generate_token' ] );
|
|
add_action( 'wp_ajax_nopriv_wbf_mc_generate_token', [ __CLASS__, 'ajax_generate_token' ] );
|
|
|
|
// AJAX: Verknüpfung lösen
|
|
add_action( 'wp_ajax_wbf_mc_unlink', [ __CLASS__, 'ajax_unlink' ] );
|
|
add_action( 'wp_ajax_nopriv_wbf_mc_unlink', [ __CLASS__, 'ajax_unlink' ] );
|
|
|
|
// AJAX: Link-Status prüfen (Polling im Profil nach Token-Generierung)
|
|
add_action( 'wp_ajax_wbf_mc_link_status', [ __CLASS__, 'ajax_link_status' ] );
|
|
add_action( 'wp_ajax_nopriv_wbf_mc_link_status', [ __CLASS__, 'ajax_link_status' ] );
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
// ── Einstellungen ─────────────────────────────────────────────────────────
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
/**
|
|
* Prüft ob die MC-Bridge aktiviert ist.
|
|
*/
|
|
public static function is_enabled() {
|
|
$s = function_exists( 'wbf_get_settings' ) ? wbf_get_settings() : [];
|
|
return ! empty( $s['mc_bridge_enabled'] );
|
|
}
|
|
|
|
/**
|
|
* Gibt die StatusAPI-URL zurück (z.B. http://192.168.1.100:9191).
|
|
*/
|
|
private static function get_api_url() {
|
|
$s = function_exists( 'wbf_get_settings' ) ? wbf_get_settings() : [];
|
|
return rtrim( $s['mc_bridge_api_url'] ?? '', '/' );
|
|
}
|
|
|
|
/**
|
|
* Gibt das Shared Secret zurück.
|
|
*/
|
|
private static function get_api_secret() {
|
|
$s = function_exists( 'wbf_get_settings' ) ? wbf_get_settings() : [];
|
|
return $s['mc_bridge_api_secret'] ?? '';
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
// ── Notification Hook ─────────────────────────────────────────────────────
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
/**
|
|
* Wird aufgerufen wenn eine Forum-Notification erstellt wird.
|
|
* Prüft ob der Empfänger eine MC-UUID hat und pusht die Nachricht an BungeeCord.
|
|
*
|
|
* @param int $user_id Forum-User ID des Empfängers
|
|
* @param string $type Typ: 'reply', 'mention', 'message'
|
|
* @param int $object_id Thread-ID (bei reply/mention) oder Message-ID (bei message)
|
|
* @param int $actor_id Forum-User ID des Auslösers
|
|
*/
|
|
public static function on_notification( $user_id, $type, $object_id, $actor_id ) {
|
|
if ( ! self::is_enabled() ) return;
|
|
|
|
$api_url = self::get_api_url();
|
|
if ( empty( $api_url ) ) return;
|
|
|
|
// MC-UUID des Empfängers prüfen
|
|
$mc_uuid = WBF_DB::get_user_meta_single( $user_id, self::META_MC_UUID );
|
|
if ( empty( $mc_uuid ) ) return;
|
|
|
|
// Actor-Info laden
|
|
$actor = WBF_DB::get_user( (int) $actor_id );
|
|
$actor_name = $actor ? $actor->display_name : 'Unbekannt';
|
|
|
|
// Kontext-Daten sammeln
|
|
$title = '';
|
|
$url = '';
|
|
$forum_url = wbf_get_forum_url();
|
|
|
|
switch ( $type ) {
|
|
case 'reply':
|
|
case 'mention':
|
|
$thread = WBF_DB::get_thread( (int) $object_id );
|
|
if ( $thread ) {
|
|
$title = $thread->title;
|
|
$url = $forum_url . '?forum_thread=' . (int) $thread->id;
|
|
}
|
|
break;
|
|
|
|
case 'message':
|
|
$title = 'Neue Privatnachricht';
|
|
$url = $forum_url . '?forum_dm=1';
|
|
break;
|
|
}
|
|
|
|
// Push an BungeeCord senden
|
|
self::push_to_bungee( $mc_uuid, $type, $title, $actor_name, $url, $user_id );
|
|
}
|
|
|
|
/**
|
|
* Sendet die Benachrichtigung per HTTP POST an den BungeeCord StatusAPI Server.
|
|
*/
|
|
private static function push_to_bungee( $mc_uuid, $type, $title, $author, $url, $wp_user_id ) {
|
|
$api_url = self::get_api_url();
|
|
$secret = self::get_api_secret();
|
|
|
|
$payload = wp_json_encode( [
|
|
'player_uuid' => $mc_uuid,
|
|
'type' => $type,
|
|
'title' => $title,
|
|
'author' => $author,
|
|
'url' => $url,
|
|
'wp_user_id' => (int) $wp_user_id,
|
|
] );
|
|
|
|
$args = [
|
|
'method' => 'POST',
|
|
'timeout' => 5,
|
|
'blocking' => false, // Non-blocking — Seite wartet nicht auf Antwort
|
|
'headers' => [
|
|
'Content-Type' => 'application/json; charset=UTF-8',
|
|
'X-Api-Key' => $secret,
|
|
],
|
|
'body' => $payload,
|
|
'sslverify' => false, // Lokales Netzwerk braucht kein SSL
|
|
];
|
|
|
|
wp_remote_post( $api_url . '/forum/notify', $args );
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
// ── REST API (für BungeeCord → WordPress) ─────────────────────────────────
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
public static function register_rest_routes() {
|
|
// POST /wp-json/mc-bridge/v1/verify-link
|
|
// BungeeCord schickt Token + MC-UUID → WP verifiziert und speichert
|
|
register_rest_route( 'mc-bridge/v1', '/verify-link', [
|
|
'methods' => 'POST',
|
|
'callback' => [ __CLASS__, 'rest_verify_link' ],
|
|
'permission_callback' => '__return_true',
|
|
] );
|
|
|
|
// POST /wp-json/mc-bridge/v1/unlink
|
|
// BungeeCord kann Verknüpfung auch von der MC-Seite lösen
|
|
register_rest_route( 'mc-bridge/v1', '/unlink', [
|
|
'methods' => 'POST',
|
|
'callback' => [ __CLASS__, 'rest_unlink' ],
|
|
'permission_callback' => '__return_true',
|
|
] );
|
|
|
|
// GET /wp-json/mc-bridge/v1/status
|
|
// Verbindungstest
|
|
register_rest_route( 'mc-bridge/v1', '/status', [
|
|
'methods' => 'GET',
|
|
'callback' => [ __CLASS__, 'rest_status' ],
|
|
'permission_callback' => '__return_true',
|
|
] );
|
|
}
|
|
|
|
/**
|
|
* REST: Verknüpfung bestätigen.
|
|
* BungeeCord sendet: { "token": "...", "mc_uuid": "...", "mc_name": "..." }
|
|
*/
|
|
public static function rest_verify_link( $request ) {
|
|
// Rate Limiting: max 10 Versuche pro IP pro Minute
|
|
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
|
$limit_key = 'wbf_mc_link_' . md5($ip);
|
|
$attempts = (int) get_transient($limit_key);
|
|
if ($attempts >= 10) {
|
|
return new WP_REST_Response([
|
|
'success' => false,
|
|
'error' => 'rate_limited',
|
|
'message' => 'Zu viele Versuche. Bitte warte eine Minute.'
|
|
], 429);
|
|
}
|
|
set_transient($limit_key, $attempts + 1, 60);
|
|
// API-Secret prüfen
|
|
$secret = self::get_api_secret();
|
|
if ( ! empty( $secret ) ) {
|
|
$provided = $request->get_header( 'X-Api-Key' );
|
|
if ( $provided !== $secret ) {
|
|
return new WP_REST_Response( [ 'success' => false, 'error' => 'unauthorized' ], 403 );
|
|
}
|
|
}
|
|
|
|
$token = sanitize_text_field( $request->get_param( 'token' ) );
|
|
$mc_uuid = sanitize_text_field( $request->get_param( 'mc_uuid' ) );
|
|
$mc_name = sanitize_text_field( $request->get_param( 'mc_name' ) );
|
|
|
|
if ( empty( $token ) || empty( $mc_uuid ) ) {
|
|
return new WP_REST_Response( [ 'success' => false, 'error' => 'missing_fields' ], 400 );
|
|
}
|
|
|
|
// Token in forum_user_meta suchen
|
|
global $wpdb;
|
|
$meta_row = $wpdb->get_row( $wpdb->prepare(
|
|
"SELECT user_id FROM {$wpdb->prefix}forum_user_meta WHERE meta_key = %s AND meta_value = %s",
|
|
self::META_LINK_TOKEN, $token
|
|
) );
|
|
|
|
if ( ! $meta_row ) {
|
|
return new WP_REST_Response( [ 'success' => false, 'error' => 'invalid_token' ], 404 );
|
|
}
|
|
|
|
$forum_user_id = (int) $meta_row->user_id;
|
|
|
|
// Ablauf prüfen
|
|
$expiry_meta = WBF_DB::get_user_meta( $forum_user_id );
|
|
$expiry = $expiry_meta[ self::META_LINK_EXPIRY ] ?? '0';
|
|
if ( (int) $expiry < time() ) {
|
|
// Token abgelaufen — aufräumen
|
|
WBF_DB::set_user_meta( $forum_user_id, self::META_LINK_TOKEN, '' );
|
|
WBF_DB::set_user_meta( $forum_user_id, self::META_LINK_EXPIRY, '' );
|
|
return new WP_REST_Response( [ 'success' => false, 'error' => 'token_expired' ], 410 );
|
|
}
|
|
|
|
// Prüfen ob diese MC-UUID bereits mit einem anderen Account verknüpft ist
|
|
$existing = $wpdb->get_row( $wpdb->prepare(
|
|
"SELECT user_id FROM {$wpdb->prefix}forum_user_meta WHERE meta_key = %s AND meta_value = %s",
|
|
self::META_MC_UUID, $mc_uuid
|
|
) );
|
|
if ( $existing && (int) $existing->user_id !== $forum_user_id ) {
|
|
return new WP_REST_Response( [
|
|
'success' => false,
|
|
'error' => 'uuid_already_linked',
|
|
'message' => 'Diese Minecraft-UUID ist bereits mit einem anderen Forum-Account verknüpft.',
|
|
], 409 );
|
|
}
|
|
|
|
// Verknüpfung speichern
|
|
WBF_DB::set_user_meta( $forum_user_id, self::META_MC_UUID, $mc_uuid );
|
|
WBF_DB::set_user_meta( $forum_user_id, self::META_MC_NAME, $mc_name );
|
|
|
|
// Token aufräumen
|
|
WBF_DB::set_user_meta( $forum_user_id, self::META_LINK_TOKEN, '' );
|
|
WBF_DB::set_user_meta( $forum_user_id, self::META_LINK_EXPIRY, '' );
|
|
|
|
// Forum-User-Info für BungeeCord zurückgeben
|
|
$forum_user = WBF_DB::get_user( $forum_user_id );
|
|
|
|
return new WP_REST_Response( [
|
|
'success' => true,
|
|
'forum_user_id' => $forum_user_id,
|
|
'display_name' => $forum_user ? $forum_user->display_name : '',
|
|
'username' => $forum_user ? $forum_user->username : '',
|
|
], 200 );
|
|
}
|
|
|
|
/**
|
|
* REST: Verknüpfung lösen (von BungeeCord-Seite).
|
|
*/
|
|
public static function rest_unlink( $request ) {
|
|
$secret = self::get_api_secret();
|
|
if ( ! empty( $secret ) ) {
|
|
$provided = $request->get_header( 'X-Api-Key' );
|
|
if ( $provided !== $secret ) {
|
|
return new WP_REST_Response( [ 'success' => false, 'error' => 'unauthorized' ], 403 );
|
|
}
|
|
}
|
|
|
|
$mc_uuid = sanitize_text_field( $request->get_param( 'mc_uuid' ) );
|
|
if ( empty( $mc_uuid ) ) {
|
|
return new WP_REST_Response( [ 'success' => false, 'error' => 'missing_mc_uuid' ], 400 );
|
|
}
|
|
|
|
global $wpdb;
|
|
$meta_row = $wpdb->get_row( $wpdb->prepare(
|
|
"SELECT user_id FROM {$wpdb->prefix}forum_user_meta WHERE meta_key = %s AND meta_value = %s",
|
|
self::META_MC_UUID, $mc_uuid
|
|
) );
|
|
|
|
if ( ! $meta_row ) {
|
|
return new WP_REST_Response( [ 'success' => false, 'error' => 'not_linked' ], 404 );
|
|
}
|
|
|
|
$forum_user_id = (int) $meta_row->user_id;
|
|
WBF_DB::set_user_meta( $forum_user_id, self::META_MC_UUID, '' );
|
|
WBF_DB::set_user_meta( $forum_user_id, self::META_MC_NAME, '' );
|
|
|
|
return new WP_REST_Response( [ 'success' => true ], 200 );
|
|
}
|
|
|
|
/**
|
|
* REST: Status-Endpoint für Verbindungstest.
|
|
*/
|
|
public static function rest_status( $request ) {
|
|
return new WP_REST_Response( [
|
|
'success' => true,
|
|
'enabled' => self::is_enabled(),
|
|
'version' => defined( 'WBF_VERSION' ) ? WBF_VERSION : '?',
|
|
'plugin' => 'WP Business Forum',
|
|
], 200 );
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
// ── AJAX: Token generieren ────────────────────────────────────────────────
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
/**
|
|
* Generiert einen 8-stelligen Verknüpfungs-Token (15 Minuten gültig).
|
|
* Der User gibt diesen Token dann ingame mit /forumlink <token> ein.
|
|
*/
|
|
public static function ajax_generate_token() {
|
|
check_ajax_referer( 'wbf_nonce', 'nonce' );
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) {
|
|
wp_send_json_error( [ 'message' => 'Nicht eingeloggt.' ] );
|
|
}
|
|
|
|
// Prüfen ob bereits verknüpft
|
|
$existing_uuid = WBF_DB::get_user_meta_single( $user->id, self::META_MC_UUID );
|
|
if ( ! empty( $existing_uuid ) ) {
|
|
$mc_name = WBF_DB::get_user_meta_single( $user->id, self::META_MC_NAME );
|
|
wp_send_json_error( [
|
|
'message' => 'Dein Account ist bereits mit ' . esc_html( $mc_name ?: $existing_uuid ) . ' verknüpft.',
|
|
'linked' => true,
|
|
'mc_name' => $mc_name,
|
|
'mc_uuid' => $existing_uuid,
|
|
] );
|
|
}
|
|
|
|
// Token generieren: 8 Zeichen, alphanumerisch, uppercase
|
|
$token = strtoupper( substr( bin2hex( random_bytes( 5 ) ), 0, 8 ) );
|
|
$expiry = time() + ( 15 * 60 ); // 15 Minuten
|
|
|
|
WBF_DB::set_user_meta( $user->id, self::META_LINK_TOKEN, $token );
|
|
WBF_DB::set_user_meta( $user->id, self::META_LINK_EXPIRY, (string) $expiry );
|
|
|
|
wp_send_json_success( [
|
|
'token' => $token,
|
|
'expires_in' => 15, // Minuten
|
|
'command' => '/forumlink ' . $token,
|
|
] );
|
|
}
|
|
|
|
/**
|
|
* AJAX: Verknüpfung lösen (von der Forum-Seite).
|
|
*/
|
|
public static function ajax_unlink() {
|
|
check_ajax_referer( 'wbf_nonce', 'nonce' );
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) {
|
|
wp_send_json_error( [ 'message' => 'Nicht eingeloggt.' ] );
|
|
}
|
|
|
|
$mc_uuid = WBF_DB::get_user_meta_single( $user->id, self::META_MC_UUID );
|
|
|
|
// Meta löschen
|
|
WBF_DB::set_user_meta( $user->id, self::META_MC_UUID, '' );
|
|
WBF_DB::set_user_meta( $user->id, self::META_MC_NAME, '' );
|
|
WBF_DB::set_user_meta( $user->id, self::META_LINK_TOKEN, '' );
|
|
WBF_DB::set_user_meta( $user->id, self::META_LINK_EXPIRY, '' );
|
|
|
|
// Optional: BungeeCord informieren
|
|
if ( ! empty( $mc_uuid ) && self::is_enabled() ) {
|
|
$api_url = self::get_api_url();
|
|
$secret = self::get_api_secret();
|
|
if ( ! empty( $api_url ) ) {
|
|
wp_remote_post( $api_url . '/forum/unlink', [
|
|
'method' => 'POST',
|
|
'timeout' => 3,
|
|
'blocking' => false,
|
|
'headers' => [
|
|
'Content-Type' => 'application/json',
|
|
'X-Api-Key' => $secret,
|
|
],
|
|
'body' => wp_json_encode( [ 'mc_uuid' => $mc_uuid ] ),
|
|
'sslverify' => false,
|
|
] );
|
|
}
|
|
}
|
|
|
|
wp_send_json_success( [ 'message' => 'Minecraft-Verknüpfung wurde aufgehoben.' ] );
|
|
}
|
|
|
|
/**
|
|
* AJAX: Verknüpfungs-Status prüfen.
|
|
* Wird vom Frontend-Polling alle 5 Sekunden nach Token-Generierung aufgerufen.
|
|
* Gibt zurück ob der User bereits verknüpft ist (BungeeCord hat verify-link gesendet).
|
|
*/
|
|
public static function ajax_link_status() {
|
|
check_ajax_referer( 'wbf_nonce', 'nonce' );
|
|
$user = WBF_Auth::get_current_user();
|
|
if ( ! $user ) {
|
|
wp_send_json_error( [ 'message' => 'Nicht eingeloggt.', 'linked' => false ] );
|
|
return;
|
|
}
|
|
$mc_uuid = WBF_DB::get_user_meta_single( $user->id, self::META_MC_UUID );
|
|
$mc_name = WBF_DB::get_user_meta_single( $user->id, self::META_MC_NAME );
|
|
if ( ! empty( $mc_uuid ) ) {
|
|
wp_send_json_success( [
|
|
'linked' => true,
|
|
'mc_uuid' => $mc_uuid,
|
|
'mc_name' => $mc_name,
|
|
] );
|
|
} else {
|
|
wp_send_json_success( [ 'linked' => false ] );
|
|
}
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
// ── Hilfsmethoden ─────────────────────────────────────────────────────────
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
|
|
/**
|
|
* Gibt die MC-UUID eines Forum-Users zurück (oder leer).
|
|
*/
|
|
public static function get_mc_uuid( $forum_user_id ) {
|
|
return WBF_DB::get_user_meta_single( $forum_user_id, self::META_MC_UUID );
|
|
}
|
|
|
|
/**
|
|
* Gibt den MC-Namen eines Forum-Users zurück (oder leer).
|
|
*/
|
|
public static function get_mc_name( $forum_user_id ) {
|
|
return WBF_DB::get_user_meta_single( $forum_user_id, self::META_MC_NAME );
|
|
}
|
|
|
|
/**
|
|
* Prüft ob ein Forum-User mit MC verknüpft ist.
|
|
*/
|
|
public static function is_linked( $forum_user_id ) {
|
|
$uuid = self::get_mc_uuid( $forum_user_id );
|
|
return ! empty( $uuid );
|
|
}
|
|
|
|
/**
|
|
* HTML-Badge für Profilansicht: Zeigt MC-Verknüpfung an.
|
|
*/
|
|
public static function profile_badge( $forum_user_id ) {
|
|
if ( ! self::is_enabled() ) return '';
|
|
$mc_name = self::get_mc_name( $forum_user_id );
|
|
if ( empty( $mc_name ) ) return '';
|
|
|
|
$name_esc = esc_html( $mc_name );
|
|
$head_url = 'https://mc-heads.net/avatar/' . urlencode( $mc_name ) . '/24';
|
|
return "<span class=\"wbf-mc-badge\" title=\"Minecraft: {$name_esc}\">"
|
|
. "<img src=\"{$head_url}\" alt=\"\" width=\"16\" height=\"16\" style=\"border-radius:2px;vertical-align:middle;margin-right:4px\">"
|
|
. "<span style=\"color:#55ff55\">{$name_esc}</span>"
|
|
. "</span>";
|
|
}
|
|
}
|
|
|
|
// Initialisierung
|
|
add_action( 'init', [ 'WBF_MC_Bridge', 'init' ], 20 ); |