Files
LiteBans-Manager/wp-litebans-manager.php

1110 lines
51 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/*
Plugin Name: LiteBans Manager
Description: Die ultimative Lösung, um deine LiteBans Datenbank nahtlos in WordPress zu integrieren. Verwalte Bans, Mutes, Warnings und Kicks direkt im WordPress Admin-Panel und biete deinen Spielern ein modernes Frontend-Dashboard.
Version: 1.0.1
Author: M_Viper
*/
if ( ! defined( 'ABSPATH' ) ) { exit; }
// ===============================
// LITEBANS MANAGER - UPDATE NOTICE
// ===============================
// Plugin-Version aus Header lesen
function litebans_get_plugin_version() {
if ( ! function_exists( 'get_plugin_data' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugin_data = get_plugin_data( __FILE__ );
return $plugin_data['Version'] ?? '0.0.0';
}
// Cache manuell leeren (Button "Jetzt neu prüfen")
function litebans_clear_update_cache() {
if ( isset( $_GET['litebans_clear_cache'] ) && current_user_can( 'manage_options' ) ) {
check_admin_referer( 'litebans_clear_cache_action' );
delete_transient( 'litebans_latest_release' );
wp_redirect( admin_url( 'plugins.php' ) );
exit;
}
}
add_action( 'admin_init', 'litebans_clear_update_cache' );
// Neueste Release-Infos von Gitea holen
function litebans_get_latest_release_info( $force_refresh = false ) {
$transient_key = 'litebans_latest_release';
if ( $force_refresh ) {
delete_transient( $transient_key );
}
$release_info = get_transient( $transient_key );
if ( false === $release_info ) {
$response = wp_remote_get(
'https://git.viper.ipv64.net/api/v1/repos/M_Viper/LiteBans-Manager/releases/latest',
array( 'timeout' => 10 )
);
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
if ( $data && isset( $data['tag_name'] ) ) {
$tag = ltrim( $data['tag_name'], 'vV' );
$release_info = array(
'version' => $tag,
'download_url' => $data['zipball_url'] ?? '',
'notes' => $data['body'] ?? '',
'published_at' => $data['published_at'] ?? '',
);
// Cache 6 Stunden
set_transient( $transient_key, $release_info, 6 * HOUR_IN_SECONDS );
} else {
// leere Struktur cachen (Kurzcache)
set_transient( $transient_key, array(), HOUR_IN_SECONDS );
}
} else {
set_transient( $transient_key, array(), HOUR_IN_SECONDS );
}
}
return $release_info;
}
// Admin-Notice anzeigen wenn Update vorhanden
function litebans_show_update_notice() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$current_version = litebans_get_plugin_version();
$latest_release = litebans_get_latest_release_info();
if ( ! empty( $latest_release['version'] ) && version_compare( $current_version, $latest_release['version'], '<' ) ) {
$refresh_url = wp_nonce_url( admin_url( 'plugins.php?litebans_clear_cache=1' ), 'litebans_clear_cache_action' );
?>
<div class="notice notice-warning is-dismissible">
<h3>LiteBans Manager Update verfügbar</h3>
<p>
Installiert: <strong><?php echo esc_html( $current_version ); ?></strong><br>
Neueste Version: <strong><?php echo esc_html( $latest_release['version'] ); ?></strong>
</p>
<p>
<a href="<?php echo esc_url( $latest_release['download_url'] ); ?>" class="button button-primary" target="_blank" rel="noreferrer noopener">
Update herunterladen
</a>
<a href="https://git.viper.ipv64.net/M_Viper/LiteBans-Manager/releases" class="button" target="_blank" rel="noreferrer noopener">
Releases ansehen
</a>
<a href="<?php echo esc_url( $refresh_url ); ?>" class="button">
Jetzt neu prüfen
</a>
</p>
</div>
<?php
}
}
add_action( 'admin_notices', 'litebans_show_update_notice' );
class WP_LiteBans_Pro {
private $option_name = 'wp_litebans_pro_settings';
private $db = null;
public function __construct() {
// 1. CPT für Entbannungsanträge
add_action( 'init', array( $this, 'register_cpt_unban_request' ) );
add_action( 'add_meta_boxes', array( $this, 'add_unban_meta_box' ) );
add_action( 'save_post_unban_request', array( $this, 'save_unban_meta_box' ), 10, 2 );
// Spalten im CPT Listing anpassen
add_filter( 'manage_unban_request_posts_columns', array( $this, 'set_unban_columns' ) );
add_action( 'manage_unban_request_posts_custom_column', array( $this, 'fill_unban_columns' ), 10, 2 );
// 2. Admin Menü & Assets
add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'admin_init', array( $this, 'handle_admin_actions' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
// Tabs in CPT-Übersicht einfügen + optionaler Filter
add_action( 'all_admin_notices', array( $this, 'inject_tabs_into_unban_cpt' ) );
add_action( 'pre_get_posts', array( $this, 'filter_unban_cpt_by_tab' ) );
// Schutz: Entferne Quick-Edit wenn Notiz / Status gesperrt
add_filter( 'post_row_actions', array( $this, 'filter_post_row_actions' ), 10, 2 );
// Ausgrauen gesperrter Zeilen in der CPT-Übersicht
add_action( 'admin_head-edit.php', array( $this, 'inject_locked_row_styles' ) );
// Verstecke Standard Editor für CPT
add_action( 'admin_head-post.php', array( $this, 'hide_default_editor' ) );
add_action( 'admin_head-post-new.php', array( $this, 'hide_default_editor' ) );
// 3. Frontend Assets & Shortcodes
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
add_shortcode( 'litebans_dashboard', array( $this, 'render_dashboard' ) );
add_shortcode( 'litebans_unban', array( $this, 'render_unban_shortcode' ) );
}
// --- Admin assets ---
public function enqueue_admin_assets( $hook ) {
$load = false;
if ( strpos( (string)$hook, 'litebans' ) !== false || strpos( (string)$hook, 'litebans-manager' ) !== false ) {
$load = true;
}
if ( function_exists( 'get_current_screen' ) ) {
$screen = get_current_screen();
if ( $screen && isset( $screen->post_type ) && $screen->post_type === 'unban_request' ) {
$load = true;
}
}
if ( $load ) {
wp_enqueue_style( 'litebans-pro-admin', plugins_url( 'css/admin.css', __FILE__ ), array(), '6.4.7' );
wp_enqueue_style( 'litebans-pro-frontend-css', plugins_url( 'css/style.css', __FILE__ ), array(), '6.4.7' );
}
}
/**
* Inject CSS/JS to visually mark locked rows in the CPT list (edit.php?post_type=unban_request)
* We collect locked post IDs server-side and output a small script that adds a CSS class and badge.
*/
public function inject_locked_row_styles() {
if ( ! is_admin() ) return;
if ( ! function_exists( 'get_current_screen' ) ) return;
$screen = get_current_screen();
if ( ! $screen ) return;
if ( $screen->id !== 'edit-unban_request' ) return;
// Query for locked posts (status or admin note locked)
$locked_posts = get_posts(array(
'post_type' => 'unban_request',
'posts_per_page' => -1,
'fields' => 'ids',
'meta_query' => array(
'relation' => 'OR',
array(
'key' => '_lb_status_locked',
'value' => '1',
'compare' => '='
),
array(
'key' => '_lb_admin_note_locked',
'value' => '1',
'compare' => '='
)
)
));
if ( empty( $locked_posts ) ) return;
// Prepare JSON array for JS
$ids_json = wp_json_encode( array_map( 'intval', $locked_posts ) );
// Output inline CSS + JS
echo '<style>
/* LiteBans: locked list-row styling */
.lb-locked-row td { opacity: 0.65; background: #fbfbfb; color: #6b6b6b; }
.lb-locked-badge { margin-left:6px; color:#a00; font-weight:700; font-size:12px; }
</style>';
echo '<script type="text/javascript">(function(){ var locked = ' . $ids_json . '; if(!locked || !locked.length) return; locked.forEach(function(id){ var row = document.getElementById("post-"+id); if(!row) return; row.classList.add("lb-locked-row"); try{ var title = row.querySelector(".row-title"); if(title && !row.querySelector(".lb-locked-badge")){ var span = document.createElement("span"); span.className = "lb-locked-badge"; span.textContent = " [Gesperrt]"; title.appendChild(span); } } catch(e){} });})();</script>';
}
public function hide_default_editor() {
global $post;
if ( $post && $post->post_type === 'unban_request' ) {
echo '<style>
#titlediv, #titlewrap, #post-body-content, #screen-meta-links, #submitdiv { display: none !important; }
#poststuff #post-body { margin-top: 20px; }
</style>';
}
}
// --- 1. CPT SYSTEM ---
public function register_cpt_unban_request() {
$labels = array(
'name' => 'Entbannungsanträge',
'singular_name' => 'Entbannungsantrag',
'menu_name' => 'Entbannungen',
'add_new' => 'Neu erstellen',
'edit_item' => 'Antrag bearbeiten',
);
$args = array(
'labels' => $labels,
'public' => false,
'show_ui' => true,
'show_in_menu' => 'litebans-manager',
'capability_type' => 'post',
'supports' => array(''),
'menu_icon' => 'dashicons-forms',
);
register_post_type( 'unban_request', $args );
}
public function set_unban_columns($columns) {
if ( isset($columns['title']) ) unset($columns['title']);
if ( isset($columns['date']) ) unset($columns['date']);
$columns['player'] = 'Spieler';
$columns['status'] = 'Status';
$columns['date'] = 'Datum';
return $columns;
}
public function fill_unban_columns($column, $post_id) {
if ($column == 'player') {
echo esc_html(get_post_meta($post_id, '_lb_player', true));
}
if ($column == 'status') {
$s = get_post_meta($post_id, '_lb_status', true);
$label = $s === 'approved' ? 'Angenommen' : ($s === 'rejected' ? 'Abgelehnt' : 'Wartend');
echo '<span class="lb-status-label lb-status-'.esc_attr($s).'">'.esc_html($label).'</span>';
}
}
public function add_unban_meta_box() {
// Primary ticket box
add_meta_box(
'lb_unban_ticket', 'Unban Ticket Verwaltung',
array( $this, 'render_unban_meta_box' ),
'unban_request', 'normal', 'high'
);
// Admin Note sidebox
add_meta_box(
'lb_admin_note_box', 'Admin Notiz',
array( $this, 'render_admin_note_box' ),
'unban_request', 'side', 'high'
);
}
public function render_unban_meta_box( $post ) {
wp_nonce_field( 'lb_unban_save', 'lb_unban_nonce' );
$player = get_post_meta( $post->ID, '_lb_player', true );
$reason = get_post_meta( $post->ID, '_lb_reason', true );
$message = $post->post_content;
$status = get_post_meta( $post->ID, '_lb_status', true );
if ( empty($status) ) $status = 'pending';
$status_locked = get_post_meta( $post->ID, '_lb_status_locked', true );
$admin_name = get_post_meta( $post->ID, '_lb_admin_name', true );
?>
<div class="lb-ticket-container">
<div class="lb-ticket-header">
<div class="lb-player-badge">
<img src="https://minotar.net/avatar/<?php echo rawurlencode($player); ?>/50" alt="Skin" class="lb-avatar-large">
<div>
<h3><?php echo esc_html($player); ?></h3>
<span class="lb-date-label">Eingereicht am: <?php echo get_the_date('d.m.Y H:i'); ?></span>
</div>
</div>
<div class="lb-status-select-wrapper">
<label>Entscheidung:</label>
<?php
// Wenn Status gesperrt ist, machen wir das Select disabled und fügen ein verstecktes Feld ein,
// damit der Wert beim POST gesendet wird (wird serverseitig aber ignoriert, falls gesperrt).
$disabled = ! empty( $status_locked ) ? 'disabled' : '';
?>
<select name="lb_status" class="lb-status-select" <?php echo $disabled; ?>>
<option value="pending" <?php selected($status, 'pending'); ?>>⏳ Ausstehend</option>
<option value="approved" <?php selected($status, 'approved'); ?>>✅ Annehmen</option>
<option value="rejected" <?php selected($status, 'rejected'); ?>>❌ Ablehnen</option>
</select>
<?php if ( ! empty( $status_locked ) ): ?>
<input type="hidden" name="lb_status" value="<?php echo esc_attr( $status ); ?>">
<div style="margin-left:10px;font-size:12px;color:#6b7280;">
Gesperrt von: <?php echo esc_html( $admin_name ?: '-' ); ?>
</div>
<?php endif; ?>
<p class="description" style="margin-top:4px;font-size:12px;">
⏳ Ausstehend = In Bearbeitung, kann noch geändert werden<br>
✅ Annehmen = Bann/Mute wird aufgehoben, Admin-Notiz gesperrt<br>
❌ Ablehnen = Status bleibt bestehen, Admin-Notiz gesperrt
</p>
</div>
</div>
<div class="lb-ticket-body">
<div class="lb-card lb-card-ban">
<div class="lb-card-title">Banngrund</div>
<div class="lb-card-content"><?php echo esc_textarea($reason); ?></div>
</div>
<div class="lb-card lb-card-msg">
<div class="lb-card-title">Spieler Nachricht</div>
<div class="lb-card-content"><?php echo wpautop(esc_textarea($message)); ?></div>
</div>
</div>
<div class="lb-ticket-footer">
<div class="lb-actions">
<button type="submit" class="button button-primary button-large">Entscheidung speichern</button>
</div>
</div>
</div>
<?php
}
/**
* Render the Admin Note sidebox.
* If locked, show read-only note with admin and timestamp.
* Otherwise show textarea for admin to add a note before approving/rejecting.
*/
public function render_admin_note_box( $post ) {
wp_nonce_field( 'lb_unban_save', 'lb_unban_nonce' ); // same nonce as main meta box
$note = get_post_meta( $post->ID, '_lb_admin_note', true );
$admin = get_post_meta( $post->ID, '_lb_admin_name', true );
$locked = get_post_meta( $post->ID, '_lb_admin_note_locked', true );
echo '<div style="margin-top:6px;">';
if ( $locked ) {
echo '<p style="margin:0 0 6px 0;"><strong>Gesperrt von:</strong> ' . esc_html( $admin ) . '</p>';
echo '<div style="background:#fafafa;border:1px solid #e6e9ec;padding:8px;border-radius:4px;white-space:pre-wrap;">';
echo nl2br( esc_html( $note ) );
echo '</div>';
echo '<p style="color:#8c8f94;margin-top:8px;">Diese Notiz wurde gesperrt und kann nicht mehr verändert werden.</p>';
} else {
echo '<p style="margin:0 0 6px 0;"><strong>Admin Notiz (wird beim Annehmen/Ablehnen gesperrt)</strong></p>';
echo '<textarea name="lb_admin_note" rows="6" style="width:100%;">' . esc_textarea( $note ) . '</textarea>';
echo '<p style="color:#8c8f94;margin-top:6px;">Bitte begründe kurz deine Entscheidung. Nach dem Speichern bei Annahme/Ablehnung wird diese Notiz dauerhaft gesperrt.</p>';
}
echo '</div>';
}
/**
* save_unban_meta_box
* - speichert Meta
* - sperrt Status und Admin-Notiz bei finaler Entscheidung (approved/rejected)
* - verhindert nachträgliche Änderungen am Status/Notiz, wenn gesperrt
*/
public function save_unban_meta_box( $post_id, $post ) {
// Nonce + capability checks
if ( ! isset( $_POST['lb_unban_nonce'] ) || ! wp_verify_nonce( $_POST['lb_unban_nonce'], 'lb_unban_save' ) ) return;
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( ! current_user_can( 'edit_post', $post_id ) ) return;
// Existing flags
$old_status = get_post_meta( $post_id, '_lb_status', true );
$status_locked = get_post_meta( $post_id, '_lb_status_locked', true );
$note_locked = get_post_meta( $post_id, '_lb_admin_note_locked', true );
// Incoming sanitized values
$incoming_status = isset( $_POST['lb_status'] ) ? sanitize_text_field( $_POST['lb_status'] ) : $old_status;
$incoming_note = isset( $_POST['lb_admin_note'] ) ? sanitize_textarea_field( wp_kses_post( $_POST['lb_admin_note'] ) ) : '';
$current_user = wp_get_current_user();
$timestamp = current_time( 'd.m.Y H:i' );
// ---- STATUS HANDLING ----
// If status is already locked, deny changing it.
if ( ! empty( $status_locked ) ) {
// keep old status; ignore incoming value
$status_to_store = $old_status;
} else {
// Accept incoming status and if it's final, lock it.
$status_to_store = $incoming_status;
if ( in_array( $status_to_store, array( 'approved', 'rejected' ), true ) ) {
update_post_meta( $post_id, '_lb_status_locked', 1 );
}
}
// Always store the status meta (either unchanged or new allowed value)
update_post_meta( $post_id, '_lb_status', $status_to_store );
// ---- ADMIN NOTE HANDLING ----
// If note already locked, do not overwrite.
if ( ! empty( $note_locked ) ) {
// do nothing for admin note
} else {
// If note is not locked yet:
if ( in_array( $status_to_store, array( 'approved', 'rejected' ), true ) ) {
// Final status: write note + admin name + lock (even if note is empty)
$admin_label = ( $current_user->display_name ? $current_user->display_name : $current_user->user_login ) . ' · ' . $timestamp;
update_post_meta( $post_id, '_lb_admin_note', $incoming_note );
update_post_meta( $post_id, '_lb_admin_name', $admin_label );
update_post_meta( $post_id, '_lb_admin_note_locked', 1 );
} else {
// Non-final: save incoming note (editable)
if ( isset( $_POST['lb_admin_note'] ) ) {
update_post_meta( $post_id, '_lb_admin_note', $incoming_note );
}
}
}
// ---- ACTION: wenn gerade auf approved gesetzt (und war nicht approved vorher) -> unban ingame ----
if ( $old_status !== 'approved' && $status_to_store === 'approved' ) {
$player = get_post_meta( $post_id, '_lb_player', true );
if ( $player ) {
$lbdb = $this->get_litebans_db();
if ( is_wp_error( $lbdb ) ) {
add_settings_error( 'lb', 'db_error', 'LiteBans DB nicht konfiguriert: Unban nicht ausgeführt.', 'error' );
} else {
$res = $this->unban_by_player( $lbdb, $player, $current_user );
if ( $res['updated'] > 0 ) {
add_settings_error( 'lb', 'unban_ok', sprintf( '%d Eintrag(e) für %s wurden ingame aufgehoben.', $res['updated'], esc_html($player) ), 'updated' );
} else {
add_settings_error( 'lb', 'unban_none', 'Keine aktive Strafe in LiteBans gefunden für ' . esc_html($player) . '.', 'notice' );
}
}
}
}
// If status changed to rejected and was not rejected previously, add a notice
if ( $old_status !== 'rejected' && $status_to_store === 'rejected' ) {
add_settings_error( 'lb', 'rejected', 'Antrag abgelehnt. Notiz gesperrt.', 'updated' );
}
}
// --- 2. ADMIN MENÜ ---
public function add_admin_menu() {
add_menu_page(
'LiteBans Manager',
'LiteBans Manager',
'manage_options',
'litebans-manager',
array( $this, 'render_admin_dashboard' ),
'dashicons-dismiss',
30
);
add_submenu_page( 'litebans-manager', 'Einstellungen', 'Einstellungen', 'manage_options', 'litebans-settings', array( $this, 'render_settings_page' ) );
}
public function register_settings() {
register_setting( $this->option_name, $this->option_name );
}
public function handle_admin_actions() {
// Actions handled in dashboard
}
public function render_settings_page() {
if ( isset( $_POST['save_settings'] ) && check_admin_referer( 'lb_save_settings' ) ) {
update_option( $this->option_name, $_POST['lb_settings'] );
echo '<div class="updated"><p>Einstellungen gespeichert!</p></div>';
}
$settings = get_option( $this->option_name );
$fields = array(
'db_host' => array('label'=>'Host', 'type'=>'text', 'default'=>'localhost'),
'db_port' => array('label'=>'Port', 'type'=>'number', 'default'=>'3306'),
'db_name' => array('label'=>'Datenbank Name', 'type'=>'text', 'default'=>'litebans'),
'db_user' => array('label'=>'User', 'type'=>'text'),
'db_pass' => array('label'=>'Passwort', 'type'=>'password'),
'table_prefix' => array('label'=>'Prefix', 'type'=>'text', 'default'=>'litebans_'),
'theme_mode' => array('label'=>'Frontend Theme', 'type'=>'select', 'default'=>'light', 'options'=>['light'=>'Light Mode', 'dark'=>'Dark Mode'])
);
?>
<div class="wrap">
<h1>LiteBans Einstellungen</h1>
<form method="post">
<?php wp_nonce_field( 'lb_save_settings' ); ?>
<table class="form-table">
<?php foreach($fields as $key => $f): ?>
<tr>
<th scope="row"><label for="<?php echo $key; ?>"><?php echo $f['label']; ?></label></th>
<td>
<?php if($f['type'] === 'select'): ?>
<select id="<?php echo $key; ?>" name="lb_settings[<?php echo $key; ?>]" class="regular-text">
<?php foreach($f['options'] as $val => $label): ?>
<option value="<?php echo esc_attr($val); ?>" <?php selected( ($settings[$key] ?? $f['default']), $val ); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
<?php else: ?>
<input type="<?php echo esc_attr($f['type']); ?>" id="<?php echo esc_attr($key); ?>" name="lb_settings[<?php echo esc_attr($key); ?>]"
value="<?php echo esc_attr( $settings[$key] ?? $f['default'] ); ?>" class="regular-text">
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</table>
<input type="submit" name="save_settings" class="button button-primary" value="Speichern">
</form>
</div>
<?php
}
// --- 3. FRONTEND LOGIC ---
public function enqueue_scripts() {
wp_enqueue_style( 'litebans-pro-css', plugins_url( 'css/style.css', __FILE__ ), array(), '6.4.7' );
}
private function get_litebans_db() {
if ($this->db) return $this->db;
$s = get_option( $this->option_name );
if ( empty( $s['db_name'] ) ) return new WP_Error( 'no_config', 'LiteBans nicht konfiguriert.' );
// Host und Port zusammenfügen
$db_host = $s['db_host'];
if ( ! empty( $s['db_port'] ) ) {
$db_host .= ':' . intval( $s['db_port'] );
}
$this->db = new wpdb( $s['db_user'], $s['db_pass'], $s['db_name'], $db_host );
if ( $this->db->last_error ) return new WP_Error( 'db_error', 'DB Fehler: ' . $this->db->last_error );
$this->db->prefix = isset($s['table_prefix']) ? $s['table_prefix'] : 'litebans_';
return $this->db;
}
private function get_avatar($name) {
return "<img src='https://minotar.net/avatar/".rawurlencode($name)."/32' class='litebans-avatar' loading='lazy'>";
}
private function clean($t) {
if(!$t) return "-";
$t = preg_replace("/(?i)(\x{00a7}|&)[0-9A-FK-OR]/u", "", $t);
return esc_html($t);
}
private function millis_to_date($m) { return $m ? date("d.m.Y H:i", $m/1000) : "-"; }
// KORRIGIERTE BADGE LOGIK
private function get_badge($r, $type) {
$now = (int) ( microtime(true) * 1000 );
// entfernte Einträge (aufgehoben)
if (!empty($r->removed_by_name) && $r->removed_by_name !== "#expired") {
return "<span class='litebans-badge expired'>Aufgehoben</span>";
}
// Warnings & Kicks - keine Ablauf/Perm-Logik nötig
if ($type === 'warnings') {
return "<span class='litebans-badge warn'>Verwarnt</span>";
}
if ($type === 'kicks') {
return "<span class='litebans-badge kick'>Gekickt</span>";
}
// Bans / Mutes: prüfen Ablauf
if (!empty($r->until) && (int)$r->until > 0 && $now > (int)$r->until) {
return "<span class='litebans-badge expired'>Abgelaufen</span>";
}
if ( empty($r->until) || (int)$r->until <= 0 ) {
$label = ($type === 'mutes') ? "Permanent Mute" : "Permanent Ban";
return "<span class='litebans-badge permanent'>".esc_html($label)."</span>";
}
// aktiv
$label = ($type === 'mutes') ? "Gemutet" : "Gebannt";
$class = ($type === 'mutes') ? "muted" : "active";
return "<span class='litebans-badge ".esc_attr($class)."'>".esc_html($label)."</span>";
}
private function get_theme_script($default_theme) {
ob_start();
?>
<script>
(function() {
const wrapper = document.querySelector('.litebans-wrapper');
const toggleBtn = document.getElementById('lb-theme-toggle');
if(!wrapper || !toggleBtn) return;
const sunIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>';
const moonIcon = '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>';
const savedTheme = localStorage.getItem('lb_theme') || '<?php echo esc_js($default_theme); ?>';
function applyTheme(theme) {
if(theme === 'dark') {
wrapper.classList.add('dark-mode');
toggleBtn.innerHTML = sunIcon;
} else {
wrapper.classList.remove('dark-mode');
toggleBtn.innerHTML = moonIcon;
}
}
applyTheme(savedTheme);
toggleBtn.addEventListener('click', function() {
const isDark = wrapper.classList.contains('dark-mode');
const newTheme = isDark ? 'light' : 'dark';
localStorage.setItem('lb_theme', newTheme);
applyTheme(newTheme);
});
})();
</script>
<?php
return ob_get_clean();
}
private function render_unban_logic($player, $reason) {
$msg = '';
if ( isset($_POST['lb_submit_unban']) && check_admin_referer( 'lb_unban_form' ) ) {
$post_data = array(
'post_title' => 'Unban: ' . sanitize_text_field($_POST['lb_player']),
'post_content' => sanitize_textarea_field($_POST['lb_message']),
'post_status' => 'pending',
'post_type' => 'unban_request',
);
$post_id = wp_insert_post( $post_data );
if( $post_id ) {
update_post_meta( $post_id, '_lb_player', sanitize_text_field($_POST['lb_player']) );
update_post_meta( $post_id, '_lb_reason', sanitize_text_field($_POST['lb_reason']) );
update_post_meta( $post_id, '_lb_status', 'pending' );
$msg = '<div class="lb-success">Antrag erfolgreich eingereicht!</div>';
$player = ''; $reason = '';
}
}
ob_start();
?>
<div class="litebans-wrapper">
<div class="litebans-unban-form-container">
<div class="litebans-unban-header">
<h2>Entbannungsantrag</h2>
</div>
<?php echo $msg; ?>
<form method="post">
<?php wp_nonce_field( 'lb_unban_form' ); ?>
<div class="litebans-form-group">
<label class="litebans-form-label">Spielername</label>
<input type="text" name="lb_player" class="litebans-form-control" value="<?php echo esc_attr($player); ?>" readonly required>
</div>
<div class="litebans-form-group">
<label class="litebans-form-label">Grund</label>
<input type="text" name="lb_reason" class="litebans-form-control" value="<?php echo esc_attr($reason); ?>" readonly required>
</div>
<div class="litebans-form-group">
<label class="litebans-form-label">Warum sollten wir dich entbannen?</label>
<textarea name="lb_message" class="litebans-form-control" rows="6" required placeholder="Erkläre uns kurz dein Anliegen..."></textarea>
</div>
<div class="litebans-form-footer">
<a class="litebans-backlink" href="<?php echo esc_url( remove_query_arg(array('lb_player', 'lb_reason')) ); ?>">&larr; Zurück zur Liste</a>
<button type="submit" name="lb_submit_unban" class="litebans-btn">Antrag senden</button>
</div>
</form>
</div>
</div>
<?php
return ob_get_clean();
}
public function render_dashboard() {
$settings = get_option( $this->option_name );
$default_theme = $settings['theme_mode'] ?? 'light';
if ( isset($_GET['lb_player']) ) {
$player = sanitize_text_field($_GET['lb_player']);
$reason = isset($_GET['lb_reason']) ? sanitize_textarea_field($_GET['lb_reason']) : '';
$content = $this->render_unban_logic($player, $reason);
$content .= $this->get_theme_script($default_theme);
return $content;
}
$lbdb = $this->get_litebans_db();
if(is_wp_error($lbdb)) return '<div class="error">Datenbank Fehler: Bitte Einstellungen überprüfen.</div>';
$current_url = get_permalink();
// robustes render_tab: COALESCE für optionale Felder, stellt sicher dass kicks/warnings sichtbar sind
$render_tab = function($t) use($lbdb, $current_url) {
$table = esc_sql($lbdb->prefix . $t);
$limit = 20;
// FIX: Kicks haben keine removed_by_name Spalte in der DB
$select_removed = ($t === 'kicks') ? "''" : "COALESCE(l.removed_by_name, '')";
$q = $lbdb->prepare(
"SELECT l.id, l.uuid, l.reason, l.banned_by_name, l.time,
COALESCE(l.until, 0) as until,
COALESCE(l.active, 0) as active,
" . $select_removed . " as removed_by_name,
h.name
FROM {$table} l
LEFT JOIN {$lbdb->prefix}history h ON l.uuid = h.uuid
WHERE (l.uuid IS NOT NULL OR h.name IS NOT NULL)
GROUP BY l.id
ORDER BY l.time DESC LIMIT %d",
$limit
);
$rows = $lbdb->get_results($q);
if(!$rows) return "<div class='litebans-empty'>Keine Daten</div>";
$html = '<table class="litebans-table"><thead><tr><th>Spieler</th><th>Status</th><th>Grund</th><th>Datum</th><th>Aktion</th></tr></thead><tbody>';
foreach($rows as $r) {
$playerName = !empty($r->name) ? $r->name : (!empty($r->banned_by_name) ? $r->banned_by_name : '-');
// active kann 0/1 oder NULL sein -> COALESCE stellt sicher: 1=aktiv
$is_active = (isset($r->active) && intval($r->active) == 1) && empty($r->removed_by_name);
$html .= '<tr>';
$html .= '<td data-label="Spieler"><div class="litebans-avatar-wrapper">'.$this->get_avatar($playerName);
$html .= '<span class="litebans-player-name">'.esc_html($playerName).'</span></div></td>';
$html .= '<td data-label="Status">'.$this->get_badge($r, $t).'</td>';
$html .= '<td data-label="Grund">'.$this->clean($r->reason).'</td>';
$html .= '<td data-label="Datum">'.$this->millis_to_date($r->time).'</td>';
// BUTTON LOGIK: nur für bans/mutes und nur wenn aktiv
$html .= '<td data-label="Aktion">';
if ($is_active && ($t === 'bans' || $t === 'mutes')) {
$unban_link = add_query_arg( array('lb_player' => $playerName, 'lb_reason' => rawurlencode($r->reason)), $current_url );
$html .= '<a href="'.esc_url($unban_link).'" class="litebans-unban-link">Antrag</a>';
} else {
$html .= '<span style="color:#aaa; font-size:12px;">-</span>';
}
$html .= '</td>';
$html .= '</tr>';
}
return $html.'</tbody></table>';
};
ob_start();
?>
<div class="litebans-wrapper">
<div class="litebans-card">
<div class="litebans-header">
<h2 class="litebans-title">Sanktionen</h2>
<div class="litebans-controls">
<button id="lb-theme-toggle" class="litebans-theme-toggle" title="Theme wechseln"></button>
<form method="post" class="litebans-search-form">
<!-- Suche könnte hier erweitert werden -->
</form>
</div>
</div>
<ul class="litebans-tabs">
<li><button class="litebans-tab-btn active" onclick="openTab(event,'bans')">Bans</button></li>
<li><button class="litebans-tab-btn" onclick="openTab(event,'mutes')">Mutes</button></li>
<li><button class="litebans-tab-btn" onclick="openTab(event,'warnings')">Warnings</button></li>
<li><button class="litebans-tab-btn" onclick="openTab(event,'kicks')">Kicks</button></li>
</ul>
<div id="bans" class="litebans-tab-content active"><?php echo $render_tab('bans'); ?></div>
<div id="mutes" class="litebans-tab-content"><?php echo $render_tab('mutes'); ?></div>
<div id="warnings" class="litebans-tab-content"><?php echo $render_tab('warnings'); ?></div>
<div id="kicks" class="litebans-tab-content"><?php echo $render_tab('kicks'); ?></div>
</div>
<script>
function openTab(e,n){
var c=document.getElementsByClassName("litebans-tab-content"), t=document.getElementsByClassName("litebans-tab-btn");
for(i=0;i<c.length;i++){ c[i].style.display="none"; c[i].classList.remove("active"); }
for(i=0;i<t.length;i++){ t[i].classList.remove("active"); }
document.getElementById(n).style.display="block";
document.getElementById(n).classList.add("active");
e.currentTarget.classList.add("active");
}
</script>
<?php echo $this->get_theme_script($default_theme); ?>
</div>
<?php
return ob_get_clean();
}
public function render_unban_shortcode() {
return $this->render_unban_logic('', '');
}
// --- 4. ADMIN BACKEND MIT TABS ---
public function render_admin_dashboard() {
// Tabs
$current_tab = isset($_GET['lb_tab']) ? sanitize_text_field($_GET['lb_tab']) : 'bans';
$valid_tabs = array('bans', 'mutes', 'warnings', 'kicks');
if(!in_array($current_tab, $valid_tabs, true)) $current_tab = 'bans';
$lbdb = $this->get_litebans_db();
if ( is_wp_error( $lbdb ) ) {
echo '<div class="notice notice-error"><p>' . esc_html( $lbdb->get_error_message() ) . '</p></div>';
return;
}
// Actions verarbeiten (Unban/Delete)
if ( isset( $_POST['lb_action'] ) && isset( $_POST['lb_id'] ) && check_admin_referer( 'lb_admin_action_' . $_POST['lb_id'] ) ) {
$this->process_admin_action($lbdb, sanitize_text_field($_POST['lb_type']));
}
settings_errors( 'lb' );
// Pagination
$search = isset( $_GET['s'] ) ? sanitize_text_field( $_GET['s'] ) : '';
$limit = 20;
$paged = isset( $_GET['paged'] ) ? max(1, intval($_GET['paged'])) : 1;
$offset = ( $paged - 1 ) * $limit;
$table = esc_sql($lbdb->prefix . $current_tab);
$search_sql = $search ? $lbdb->prepare(" AND h.name LIKE %s", "%$search%") : "";
// FIX: Kicks haben keine removed_by_name Spalte in der DB
$select_removed = ($current_tab === 'kicks') ? "''" : "COALESCE(l.removed_by_name, '')";
// Robust select for admin listing as well
$query = "SELECT l.id, l.uuid, l.reason, l.banned_by_name, l.time,
COALESCE(l.until,0) AS until,
COALESCE(l.active,0) AS active,
" . $select_removed . " AS removed_by_name,
h.name
FROM $table l LEFT JOIN {$lbdb->prefix}history h ON l.uuid = h.uuid
WHERE (l.uuid IS NOT NULL OR h.name IS NOT NULL) $search_sql GROUP BY l.id ORDER BY l.time DESC LIMIT %d OFFSET %d";
$results = $lbdb->get_results( $lbdb->prepare( $query, $limit, $offset ) );
$total = $lbdb->get_var("SELECT COUNT(*) FROM $table l WHERE l.uuid IS NOT NULL");
?>
<div class="wrap">
<h1>LiteBans Manager</h1>
<!-- TAB NAVIGATION -->
<ul class="lb-admin-tabs">
<?php foreach($valid_tabs as $t): ?>
<li class="lb-admin-tab <?php echo $current_tab == $t ? 'active' : ''; ?>">
<a href="<?php echo esc_url(admin_url('admin.php?page=litebans-manager&lb_tab='.$t)); ?>"><?php echo esc_html(ucfirst($t)); ?></a>
</li>
<?php endforeach; ?>
</ul>
<!-- SEARCH -->
<form method="get" class="search-box" style="margin-bottom: 20px;">
<input type="hidden" name="page" value="litebans-manager">
<input type="hidden" name="lb_tab" value="<?php echo esc_attr($current_tab); ?>">
<input type="search" name="s" value="<?php echo esc_attr($search); ?>" placeholder="Spieler suchen...">
<button type="submit" class="button">Suchen</button>
</form>
<!-- TABLE -->
<table class="wp-list-table widefat fixed striped table-view-list">
<thead>
<tr><th>ID</th><th>Spieler</th><th>Grund</th><th>Von</th><th>Status</th><th>Aktion</th></tr>
</thead>
<tbody>
<?php if(!$results) echo '<tr><td colspan="6">Keine Einträge.</td></tr>'; ?>
<?php foreach($results as $row):
$active = false;
if ( in_array($current_tab, array('bans','mutes'), true) ) {
$active = (isset($row->active) && $row->active == 1) && empty($row->removed_by_name);
}
?>
<tr>
<td><?php echo esc_html($row->id); ?></td>
<td><?php echo esc_html($row->name ?? $row->banned_by_name); ?></td>
<td><?php echo $this->clean($row->reason); ?></td>
<td><?php echo esc_html($row->banned_by_name); ?></td>
<td>
<?php
if ($current_tab === 'warnings') {
echo '<span class="lb-status label-warning">Verwarnung</span>';
} elseif ($current_tab === 'kicks') {
echo '<span class="lb-status label-kick">Kick</span>';
} else {
echo '<span class="lb-status '.($active?'label-active':'label-inactive').'">';
echo $active ? 'Aktiv' : 'Inaktiv';
echo '</span>';
}
?>
</td>
<td>
<form method="post" style="display:inline;">
<?php wp_nonce_field( 'lb_admin_action_' . $row->id ); ?>
<input type="hidden" name="lb_id" value="<?php echo esc_attr($row->id); ?>">
<input type="hidden" name="lb_type" value="<?php echo esc_attr($current_tab); ?>">
<?php if($active && in_array($current_tab, array('bans','mutes'), true)): ?>
<button type="submit" name="lb_action" value="unban"
onclick="return confirm('Möchtest du diesen Eintrag wirklich aufheben?');" class="button button-secondary button-small">Aufheben</button>
<?php endif; ?>
<button type="submit" name="lb_action" value="delete"
onclick="return confirm('Möchtest du diesen Eintrag löschen?');" class="button button-link button-link-delete delete" style="color:#a00;">Löschen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- PAGINATION -->
<div class="tablenav bottom">
<?php
echo paginate_links( array(
'total' => max(1, ceil($total/$limit)),
'current' => $paged,
'base' => add_query_arg('paged', '%#%')
));
?>
</div>
</div>
<?php
}
/**
* process_admin_action
* - verarbeitet 'unban' und 'delete' Aktionen aus dem Admin-Dashboard
* - unban aktualisiert LiteBans DB so, dass ingame aufgehoben wird
*/
private function process_admin_action($lbdb, $type) {
$action = sanitize_text_field( $_POST['lb_action'] ?? '' );
$id = intval( $_POST['lb_id'] ?? 0 );
$current_user = wp_get_current_user();
$table = esc_sql($lbdb->prefix . $type);
$now = (int) ( microtime(true) * 1000 );
// Disallow unban action for warnings/kicks
if ( ($type === 'warnings' || $type === 'kicks') && $action === 'unban' ) {
add_settings_error( 'lb', 'invalid_action', 'Diese Sanktion kann nicht aufgehoben werden.', 'error' );
return;
}
if ( $action === 'unban' ) {
// Nur Bans / Mutes
if ( ! in_array( $type, array('bans','mutes'), true ) ) {
add_settings_error( 'lb', 'invalid_type', 'Ungültiger Typ für Aufhebung.', 'error' );
return;
}
$data = array(
'active' => 0,
'removed_by_name' => 'WebAdmin: ' . $current_user->display_name,
'removed_by_uuid' => 'CONSOLE',
'removed_by_date' => $now
);
$where = array( 'id' => $id );
$formats = array( '%d', '%s', '%s', '%d' );
$where_formats = array( '%d' );
$result = $lbdb->update( $table, $data, $where, $formats, $where_formats );
if ( $result !== false ) {
// additionally, try to update any other rows with same uuid (defensive)
$uuid = $lbdb->get_var( $lbdb->prepare( "SELECT uuid FROM {$table} WHERE id = %d LIMIT 1", $id ) );
if ( $uuid ) {
$lbdb->update( esc_sql($lbdb->prefix . 'bans'), $data, array('uuid' => $uuid, 'active' => 1), $formats, array('%s','%d'));
$lbdb->update( esc_sql($lbdb->prefix . 'mutes'), $data, array('uuid' => $uuid, 'active' => 1), $formats, array('%s','%d'));
}
add_settings_error( 'lb', 'success', "Eintrag #$id erfolgreich aufgehoben und ingame entfernt.", 'updated' );
} else {
add_settings_error( 'lb', 'dbfail', "Datenbank Update fehlgeschlagen.", 'error' );
}
} elseif ( $action === 'delete' ) {
$result = $lbdb->delete( $table, array( 'id' => $id ), array( '%d' ) );
if ( $result ) add_settings_error( 'lb', 'success', "Eintrag #$id gelöscht.", 'updated' );
else add_settings_error( 'lb', 'dbfail', "Löschen fehlgeschlagen.", 'error' );
}
}
/**
* unban_by_player
* - versucht UUID aus history zu holen und active Bans/Mutes aufzuheben
* - returns array('updated' => int)
*/
private function unban_by_player( $lbdb, $player, $current_user ) {
$updated = 0;
$now = (int) ( microtime(true) * 1000 );
// find latest uuid from history
$uuid = $lbdb->get_var( $lbdb->prepare( "SELECT uuid FROM {$lbdb->prefix}history WHERE name = %s ORDER BY id DESC LIMIT 1", $player ) );
if ( ! $uuid ) {
// Kein Eintrag in history gefunden => nicht zuverlässig aufhebbar
return array('updated' => 0);
}
$data = array(
'active' => 0,
'removed_by_name' => 'WebAdmin: ' . $current_user->display_name,
'removed_by_uuid' => 'CONSOLE',
'removed_by_date' => $now
);
$formats = array('%d','%s','%s','%d');
foreach ( array('bans','mutes') as $t ) {
$table = esc_sql( $lbdb->prefix . $t );
// Update only active entries with this uuid
$where = array( 'uuid' => $uuid, 'active' => 1 );
$where_formats = array('%s','%d');
$res = $lbdb->update( $table, $data, $where, $formats, $where_formats );
if ( $res !== false ) {
$updated += (int) $res;
}
}
return array('updated' => $updated);
}
/**
* Inject tabs into the CPT listing screen (edit.php?post_type=unban_request)
* This displays the same tabs as the plugin dashboard and links to the plugin page.
*/
public function inject_tabs_into_unban_cpt() {
if ( ! is_admin() ) return;
if ( ! function_exists( 'get_current_screen' ) ) return;
$screen = get_current_screen();
if ( ! $screen ) return;
// Only show on the CPT list screen for unban_request
if ( $screen->id !== 'edit-unban_request' ) return;
$valid_tabs = array( 'bans', 'mutes', 'warnings', 'kicks' );
$current = isset( $_GET['lb_tab'] ) ? sanitize_text_field( $_GET['lb_tab'] ) : 'bans';
if ( ! in_array( $current, $valid_tabs, true ) ) $current = 'bans';
echo '<div style="margin-top:10px;margin-bottom:8px;">';
echo '<ul class="lb-admin-tabs">';
foreach ( $valid_tabs as $t ) {
$active = $current === $t ? 'active' : '';
$url = add_query_arg( array( 'page' => 'litebans-manager', 'lb_tab' => $t ), admin_url( 'admin.php' ) );
echo '<li class="lb-admin-tab ' . esc_attr( $active ) . '"><a href="' . esc_url( $url ) . '">' . esc_html( ucfirst( $t ) ) . '</a></li>';
}
echo '</ul>';
echo '</div>';
}
public function filter_unban_cpt_by_tab( $query ) {
if ( ! is_admin() || ! $query->is_main_query() ) return;
if ( $query->get('post_type') !== 'unban_request' ) return;
if ( isset( $_GET['lb_tab'] ) ) {
$tab = sanitize_text_field( $_GET['lb_tab'] );
$allowed = array( 'bans', 'mutes', 'warnings', 'kicks' );
if ( in_array( $tab, $allowed, true ) ) {
// apply meta_query only if meta exists; harmless otherwise
$query->set( 'meta_query', array(
array(
'key' => '_lb_type',
'value' => $tab,
'compare' => '='
)
) );
}
}
}
/**
* Remove inline Quick Edit when admin note is locked (prevents sneaky quick edits).
*/
public function filter_post_row_actions( $actions, $post ) {
if ( $post->post_type !== 'unban_request' ) return $actions;
$locked_note = get_post_meta( $post->ID, '_lb_admin_note_locked', true );
$locked_status = get_post_meta( $post->ID, '_lb_status_locked', true );
if ( $locked_note || $locked_status ) {
if ( isset( $actions['inline hide-if-no-js'] ) ) {
unset( $actions['inline hide-if-no-js'] );
}
if ( isset( $actions['inline'] ) ) {
unset( $actions['inline'] );
}
}
return $actions;
}
}
new WP_LiteBans_Pro();