Dateien nach "Minecraft-BungeeCord-Status" hochladen

This commit is contained in:
2026-01-01 01:50:28 +00:00
parent dd0fe44c35
commit 0c41cee3ea

View File

@@ -0,0 +1,694 @@
<?php
/**
* Plugin Name: Minecraft BungeeCord Status Network Edition
* Description: Der ultimative Live-Status für dein BungeeCord Netzwerk (Border None Fix).
* Tags: minecraft, bungeecord, server status, player list
* Version: 3.6.0 (Final Border Fix)
* Author: M_Viper
* Requires at least: 6.0
* Requires PHP: 7.4
*/
if (!defined('ABSPATH')) exit;
define('MCSS_DIR', plugin_dir_path(__FILE__));
define('MCSS_URL', plugin_dir_url(__FILE__));
require_once MCSS_DIR . 'rcon/Rcon.php';
/* ---------------- Assets ---------------- */
add_action('admin_enqueue_scripts', function($hook){
global $pagenow;
if ($pagenow === 'admin.php' && isset($_GET['page']) && $_GET['page'] === 'mcss-settings') {
wp_enqueue_media();
wp_enqueue_style('wp-color-picker');
wp_enqueue_script('wp-color-picker');
wp_enqueue_script('jquery-ui-datepicker');
wp_enqueue_style('jquery-ui-css', 'https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.min.css');
wp_enqueue_script('mcss-admin-js', MCSS_URL . 'js/admin.js', ['jquery', 'wp-color-picker', 'jquery-ui-datepicker'], '1.0.0', true);
}
});
add_action('wp_enqueue_scripts', function(){
wp_enqueue_style('mcss-style', MCSS_URL . 'css/style.css', [], '3.6.0');
wp_enqueue_script('mcss-frontend-js', MCSS_URL . 'js/mcss-frontend.js', ['jquery'], '3.6.0', true);
});
/* ---------------- Settings ---------------- */
add_action('admin_menu', function(){
add_menu_page(
'BungeeCord Einstellungen',
'BungeeCord Status',
'manage_options',
'mcss-settings',
'mcss_settings_page',
'dashicons-networking',
100
);
});
add_action('admin_init', function(){
register_setting('mcss_settings_group', 'mcss_servers', ['sanitize_callback'=>'mcss_sanitize_servers']);
});
function mcss_sanitize_servers($input) {
if (!is_array($input)) return [];
$clean = [];
foreach ($input as $srv) {
if (!is_array($srv)) continue;
$clean[] = [
'id' => sanitize_key($srv['id'] ?? 'srv_'.wp_rand()),
'name' => sanitize_text_field($srv['name'] ?? 'BungeeCord'),
'host' => sanitize_text_field($srv['host'] ?? ''),
'rcon_port' => absint($srv['rcon_port'] ?? 25577),
'rcon_pass' => sanitize_text_field($srv['rcon_pass'] ?? ''),
'player_port' => sanitize_text_field($srv['player_port'] ?? '9191'), // API Port
'player_port_copy' => sanitize_text_field($srv['player_port_copy'] ?? ''), // Display Port
'copy_address' => sanitize_text_field($srv['copy_address'] ?? ''),
'hide_port' => !empty($srv['hide_port']),
'show_motd' => !empty($srv['show_motd']),
'cache_ttl' => max(2, absint($srv['cache_ttl'] ?? 10)),
'logo_id' => absint($srv['logo_id'] ?? 0),
'logo_url' => esc_url_raw($srv['logo_url'] ?? ''),
'custom_text' => wp_kses_post($srv['custom_text'] ?? ''),
// Styles
'ip_color' => sanitize_hex_color($srv['ip_color'] ?? '#1f2937'),
'ct_color' => sanitize_hex_color($srv['ct_color'] ?? '#1e293b'),
'ip_size' => sanitize_text_field($srv['ip_size'] ?? '1.5em'),
'ct_size' => sanitize_text_field($srv['ct_size'] ?? '1.05em'),
'name_color' => sanitize_hex_color($srv['name_color'] ?? '#333333'),
'name_size' => sanitize_text_field($srv['name_size'] ?? '1.8em'),
// Events & Maintenance
'maintenance_mode' => !empty($srv['maintenance_mode']),
'maintenance_message' => wp_kses_post($srv['maintenance_message'] ?? 'Wartung'),
'announcement_enabled' => !empty($srv['announcement_enabled']),
'announcement_text' => wp_kses_post($srv['announcement_text'] ?? ''),
'announcement_start' => sanitize_text_field($srv['announcement_start'] ?? ''),
'announcement_end' => sanitize_text_field($srv['announcement_end'] ?? ''),
'announcement_type' => sanitize_text_field($srv['announcement_type'] ?? 'info'),
// Ranks
'ranks_json' => mcss_sanitize_ranks($srv['ranks_json'] ?? '[]'),
];
}
return $clean;
}
function mcss_sanitize_ranks($input) {
$decoded = json_decode($input, true);
if (!is_array($decoded)) return '[]';
$out = [];
foreach ($decoded as $r) {
if (!is_array($r) || empty($r['name'])) continue;
$out[] = ['name'=>sanitize_text_field($r['name']), 'color'=>sanitize_text_field($r['color']??'#6c5ce7')];
}
return wp_json_encode($out);
}
/* ---------------- Helper Functions ---------------- */
function mcss_should_show_announcement($srv) {
if (empty($srv['announcement_enabled'])) return false;
if (empty($srv['announcement_text'])) return false;
if (empty($srv['announcement_start'])) return true;
$current_time = current_time('timestamp');
$start_time = strtotime($srv['announcement_start']);
if ($start_time > $current_time) return false;
if (empty($srv['announcement_end'])) return true;
$end_time = strtotime($srv['announcement_end']);
return $end_time >= $current_time;
}
function mcss_get_announcement_style($type) {
switch ($type) {
case 'warning': return ['bg' => '#fef3c7','border' => '#fbbf24','text' => '#92400e','icon' => '⚠️'];
case 'success': return ['bg' => '#d1fae5','border' => '#10b981','text' => '#065f46','icon' => '✅'];
case 'error': return ['bg' => '#fee2e2','border' => '#ef4444','text' => '#991b1b','icon' => '❌'];
case 'info': default: return ['bg' => '#dbeafe','border' => '#3b82f6','text' => '#1e40af','icon' => ''];
}
}
/* ---------------- Backend Page ---------------- */
function mcss_settings_page() {
$servers = get_option('mcss_servers', []);
if (empty($servers)) {
$servers = [['id'=>'default', 'name'=>'Mein Netzwerk', 'host'=>'127.0.0.1', 'player_port'=>'9191', 'cache_ttl'=>10, 'hide_port'=>true, 'show_motd'=>true]];
}
// Vollständige Liste
$font_sizes = [
'0.7em'=>'Sehr klein','0.85em'=>'Klein','1em'=>'Normal','1.2em'=>'Etwas größer',
'1.4em'=>'Groß','1.5em'=>'Sehr groß','1.7em'=>'Extra groß','2em'=>'Riesig',
'2.5em'=>'Enorm','3em'=>'Gigantisch',
];
$announcement_types = ['info'=>'Info (Blau)','warning'=>'Warnung (Orange)','success'=>'Erfolg (Grün)','error'=>'Fehler (Rot)'];
?>
<div class="wrap">
<h1>BungeeCord Netzwerk Einstellungen</h1>
<p style="color:#d97706;background:#fef3c7;padding:10px;border-radius:5px;">
<strong>Final:</strong> Expliziter `border: none` im Inline-Style für Wartungsmodus.
</p>
<form method="post" action="options.php">
<?php settings_fields('mcss_settings_group'); ?>
<div id="mcss-servers-repeater">
<?php foreach ($servers as $i => $srv): mcss_render_row($srv, $i, $font_sizes, $announcement_types); endforeach; ?>
</div>
<p><button type="button" class="button button-primary" id="mcss-add-server">+ Netzwerk hinzufügen</button></p>
<?php submit_button(); ?>
</form>
</div>
<script>
jQuery(function($){
let counter = <?php echo count($servers); ?>;
$('#mcss-add-server').on('click', function(e){
e.preventDefault();
const newId = 'new_' + counter;
const newIndex = counter;
const newServerHtml = `
<div class="mcss-server-row" style="border:1px solid #ddd;padding:20px;margin:20px 0;background:#f9f9f9;position:relative;">
<button type="button" class="notice-dismiss mcss-remove-server" style="position:absolute;top:10px;right:10px;"></button>
<h3>Netzwerk ${counter+1}: Neues Netzwerk</h3>
<table class="form-table">
<tr><th>Name</th><td><input name="mcss_servers[${counter}][name]" value="BungeeCord" class="regular-text"></td></tr>
<tr>
<th>Name Schrift</th>
<td>
<label>Farbe:</label>
<input name="mcss_servers[${counter}][name_color]" type="color" value="#333333" style="width:50px;" />
<label style="margin-left:10px;">Größe:</label>
<select name="mcss_servers[${counter}][name_size]" style="width:200px;">
<?php foreach ($font_sizes as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>"><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr><th>Host (IP)</th><td><input name="mcss_servers[${counter}][host]" value="127.0.0.1" class="regular-text"></td></tr>
<tr><th>API Port (StatusAPI)</th><td><input name="mcss_servers[${counter}][player_port]" value="9191" class="regular-text"></td></tr>
<tr><th>Spielerport (für IP-Anzeige)</th><td><input name="mcss_servers[${counter}][player_port_copy]" value="" class="regular-text" placeholder="z.B. 25577" /></td></tr>
<tr><th>Cache TTL</th><td><input name="mcss_servers[${counter}][cache_ttl]" value="10" class="small-text"></td></tr>
<tr><th>Kopier-Adresse</th><td><input name="mcss_servers[${counter}][copy_address]" value="" class="regular-text"></td></tr>
<tr><th>Logo URL</th><td><input name="mcss_servers[${counter}][logo_url]" value="" class="regular-text"></td></tr>
<tr><th>Zusatztext</th><td><input name="mcss_servers[${counter}][custom_text]" value="" class="large-text"></td></tr>
<tr>
<th>Zusatztext Schrift</th>
<td>
<label>Farbe:</label>
<input name="mcss_servers[${counter}][ct_color]" type="color" value="#555555" style="width:50px;" />
<label style="margin-left:10px;">Größe:</label>
<select name="mcss_servers[${counter}][ct_size]" style="width:200px;">
<?php foreach ($font_sizes as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>"><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr><th><strong>Wartungsmodus</strong></th><td><input name="mcss_servers[${counter}][maintenance_mode]" type="checkbox" value="1" /></td></tr>
<tr><th>Wartungsnachricht</th><td><textarea name="mcss_servers[${counter}][maintenance_message]" class="large-text" rows="4">Wartung</textarea></td></tr>
<tr><th><strong>Ankündigungen</strong></th><td><input name="mcss_servers[${counter}][announcement_enabled]" type="checkbox" value="1" /></td></tr>
<tr><th>Ankündigungstext</th><td><textarea name="mcss_servers[${counter}][announcement_text]" class="large-text" rows="3"></textarea></td></tr>
<tr>
<th>Ankündigungszeitraum</th>
<td>
<div style="display:flex;gap:10px;align-items:center;">
<div style="flex:1;"><label for="mcss_start_${newIndex}">Start:</label><input name="mcss_servers[${counter}][announcement_start]" id="mcss_start_${newIndex}" type="text" class="regular-text mcss-datepicker" placeholder="TT.MM.JJJJ HH:MM" /></div>
<div style="flex:1;"><label for="mcss_end_${newIndex}">Ende:</label><input name="mcss_servers[${counter}][announcement_end]" id="mcss_end_${newIndex}" type="text" class="regular-text mcss-datepicker" placeholder="TT.MM.JJJJ HH:MM" /></div>
</div>
</td>
</tr>
<tr><th>Ankündigungstyp</th>
<td>
<select name="mcss_servers[${counter}][announcement_type]" style="width:200px;">
<?php foreach ($announcement_types as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>"><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
</table>
<input type="hidden" name="mcss_servers[${counter}][id]" value="${newId}">
</div>
`;
$('#mcss-servers-repeater').append(newServerHtml);
$('.mcss-server-row:last .mcss-datepicker').datepicker({ dateFormat: 'dd.mm.yy', timeFormat: 'HH:mm' });
$('.mcss-server-row:last input[type="color"]').wpColorPicker();
counter++;
});
$(document).on('click', '.mcss-remove-server', function(){ $(this).closest('.mcss-server-row').remove(); });
$('.mcss-datepicker').datepicker({ dateFormat: 'dd.mm.yy', timeFormat: 'HH:mm' });
$('input[type="color"]').wpColorPicker();
});
</script>
<?php
}
function mcss_render_row($srv, $i, $font_sizes, $announcement_types) {
?>
<div class="mcss-server-row" style="border:1px solid #ddd;padding:20px;margin:20px 0;background:#f9f9f9;position:relative;">
<button type="button" class="notice-dismiss mcss-remove-server" style="position:absolute;top:10px;right:10px;"></button>
<h3>Netzwerk <?php echo $i+1; ?></h3>
<table class="form-table">
<tr><th>Name</th><td><input name="mcss_servers[<?php echo $i; ?>][name]" value="<?php echo esc_attr($srv['name'] ?? ''); ?>" class="regular-text"></td></tr>
<tr>
<th>Name Schrift</th>
<td>
<label>Farbe:</label>
<input name="mcss_servers[<?php echo $i; ?>][name_color]" type="color" value="<?php echo esc_attr($srv['name_color'] ?? '#333333'); ?>" style="width:50px;" />
<label style="margin-left:10px;">Größe:</label>
<select name="mcss_servers[<?php echo $i; ?>][name_size]" style="width:200px;">
<?php foreach ($font_sizes as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($srv['name_size'] ?? '1.8em', $value); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
<option value="<?php echo esc_attr($srv['name_size'] ?? ''); ?>" <?php echo !isset($font_sizes[$srv['name_size'] ?? '']) ? 'selected' : ''; ?>>Benutzerdefiniert (<?php echo esc_html($srv['name_size'] ?? ''); ?>)</option>
</select>
</td>
</tr>
<tr><th>Host (IP)</th><td><input name="mcss_servers[<?php echo $i; ?>][host]" value="<?php echo esc_attr($srv['host'] ?? '127.0.0.1'); ?>" class="regular-text"></td></tr>
<tr><th>API Port (StatusAPI)</th><td><input name="mcss_servers[<?php echo $i; ?>][player_port]" value="<?php echo esc_attr($srv['player_port'] ?? '9191'); ?>" class="regular-text"></td></tr>
<tr><th>Spielerport (für IP-Anzeige)</th><td><input name="mcss_servers[<?php echo $i; ?>][player_port_copy]" value="<?php echo esc_attr($srv['player_port_copy'] ?? ''); ?>" class="regular-text" placeholder="z.B. 25577" /></td></tr>
<tr><th>Cache TTL</th><td><input name="mcss_servers[<?php echo $i; ?>][cache_ttl]" value="<?php echo esc_attr($srv['cache_ttl'] ?? '10'); ?>" class="small-text"></td></tr>
<tr><th>Kopier-Adresse</th><td><input name="mcss_servers[<?php echo $i; ?>][copy_address]" value="<?php echo esc_attr($srv['copy_address'] ?? ''); ?>" class="regular-text"></td></tr>
<tr><th>Logo URL</th><td><input name="mcss_servers[<?php echo $i; ?>][logo_url]" value="<?php echo esc_attr($srv['logo_url'] ?? ''); ?>" class="regular-text"></td></tr>
<tr><th>Zusatztext</th><td><input name="mcss_servers[<?php echo $i; ?>][custom_text]" value="<?php echo esc_attr($srv['custom_text'] ?? ''); ?>" class="large-text"></td></tr>
<tr>
<th>Zusatztext Schrift</th>
<td>
<label>Farbe:</label>
<input name="mcss_servers[<?php echo $i; ?>][ct_color]" type="color" value="<?php echo esc_attr($srv['ct_color'] ?? '#555555'); ?>" style="width:50px;" />
<label style="margin-left:10px;">Größe:</label>
<select name="mcss_servers[<?php echo $i; ?>][ct_size]" style="width:200px;">
<?php foreach ($font_sizes as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($srv['ct_size'] ?? '1.05em', $value); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
<option value="<?php echo esc_attr($srv['ct_size'] ?? ''); ?>" <?php echo !isset($font_sizes[$srv['ct_size'] ?? '']) ? 'selected' : ''; ?>>Benutzerdefiniert (<?php echo esc_html($srv['ct_size'] ?? ''); ?>)</option>
</select>
</td>
</tr>
<tr><th><strong>Wartungsmodus</strong></th><td><input name="mcss_servers[<?php echo $i; ?>][maintenance_mode]" type="checkbox" value="1" <?php checked(!empty($srv['maintenance_mode'])); ?> /></td></tr>
<tr><th>Wartungsnachricht</th><td><textarea name="mcss_servers[<?php echo $i; ?>][maintenance_message]" class="large-text" rows="4"><?php echo esc_textarea($srv['maintenance_message'] ?? 'Wartung'); ?></textarea></td></tr>
<tr><th><strong>Ankündigungen</strong></th><td><input name="mcss_servers[<?php echo $i; ?>][announcement_enabled]" type="checkbox" value="1" <?php checked(!empty($srv['announcement_enabled'])); ?> /></td></tr>
<tr><th>Ankündigungstext</th><td><textarea name="mcss_servers[<?php echo $i; ?>][announcement_text]" class="large-text" rows="3"><?php echo esc_textarea($srv['announcement_text'] ?? ''); ?></textarea></td></tr>
<tr>
<th>Ankündigungszeitraum</th>
<td>
<div style="display:flex;gap:10px;align-items:center;">
<div style="flex:1;"><label for="mcss_start_<?php echo $i; ?>">Start:</label><input name="mcss_servers[<?php echo $i; ?>][announcement_start]" id="mcss_start_<?php echo $i; ?>" type="text" class="regular-text mcss-datepicker" value="<?php echo esc_attr($srv['announcement_start'] ?? ''); ?>" placeholder="TT.MM.JJJJ HH:MM" /></div>
<div style="flex:1;"><label for="mcss_end_<?php echo $i; ?>">Ende:</label><input name="mcss_servers[<?php echo $i; ?>][announcement_end]" id="mcss_end_<?php echo $i; ?>" type="text" class="regular-text mcss-datepicker" value="<?php echo esc_attr($srv['announcement_end'] ?? ''); ?>" placeholder="TT.MM.JJJJ HH:MM" /></div>
</div>
</td>
</tr>
<tr>
<th>Ankündigungstyp</th>
<td>
<select name="mcss_servers[<?php echo $i; ?>][announcement_type]" style="width:200px;">
<?php foreach ($announcement_types as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($srv['announcement_type'] ?? 'info', $value); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
</table>
<input type="hidden" name="mcss_servers[<?php echo $i; ?>][id]" value="<?php echo esc_attr($srv['id'] ?? ''); ?>">
</div>
<?php
}
/* ---------------- LOGIC: STATUS API FETCHER ---------------- */
function mcss_fetch_data_via_api($host, $port, $timeout = 2) {
$socket = @fsockopen($host, $port, $errno, $errstr, $timeout);
if (!$socket) return false;
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: " . $host . "\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($socket, $out);
$response = '';
while (!feof($socket)) {
$response .= fgets($socket, 128);
}
fclose($socket);
$parts = explode("\r\n\r\n", $response);
$body = end($parts);
$data = json_decode($body, true);
return $data;
}
function mcss_parse_bungeecord_version($raw_version) {
if (preg_match('/(\d+\.\d+)/', $raw_version, $matches)) {
return "1.8 - " . $matches[1];
}
return "Unbekannt";
}
function mcss_fetch_server_with_ranks($srv) {
$cache_key = 'mcss_data_' . md5($srv['host']);
$cached = get_transient($cache_key);
if ($cached !== false) return $cached;
$host = $srv['host'];
$port = $srv['player_port'] ?? 9191;
$api_data = mcss_fetch_data_via_api($host, $port, 1);
if (!$api_data || !isset($api_data['online'])) {
return ['online'=>false,'players'=>[],'version'=>'Offline','ping'=>9999,'motd'=>'Verbindung fehlgeschlagen'];
}
$players_info = [];
$user_defined_ranks = json_decode($srv['ranks_json'] ?? '[]', true);
if (!is_array($user_defined_ranks)) $user_defined_ranks = [];
foreach ($api_data['players'] as $name) {
$rank = 'Spieler';
$color = '#566d8dff';
$players_info[] = [
'name' => $name,
'avatar' => "https://mc-heads.net/avatar/" . rawurlencode($name) . "/64",
'rank' => $rank,
'color' => $color,
];
}
$clean_version = mcss_parse_bungeecord_version($api_data['version'] ?? 'BungeeCord');
$result = [
'online' => true,
'players' => $players_info,
'version' => $clean_version,
'ping' => 1,
'motd' => is_array($api_data['motd']) ? implode(' ', $api_data['motd']) : $api_data['motd']
];
$fast_cache_ttl = min(2, $srv['cache_tl'] ?? $srv['cache_ttl']);
set_transient($cache_key, $result, $fast_cache_ttl);
return $result;
}
/* ---------------- AJAX ---------------- */
add_action('wp_ajax_mcss_fetch', 'mcss_ajax_fetch');
add_action('wp_ajax_nopriv_mcss_fetch', 'mcss_ajax_fetch');
function mcss_ajax_fetch() {
$id = sanitize_text_field($_POST['server_id'] ?? '');
$servers = get_option('mcss_servers', []);
foreach ($servers as $srv) {
if (($srv['id'] ?? '') === $id) wp_send_json(mcss_fetch_server_with_ranks($srv));
if (($srv['name'] ?? '') === $id) wp_send_json(mcss_fetch_server_with_ranks($srv));
}
wp_send_json(['online'=>false]);
}
/* ---------------- FRONTEND (SHORTCODE) ---------------- */
add_shortcode('bungeecord_status', 'mcss_shortcode');
function mcss_shortcode($atts) {
$atts = shortcode_atts(['id' => ''], $atts);
if (empty($atts['id'])) return 'Fehler';
$servers = get_option('mcss_servers', []);
$srv = null;
foreach ($servers as $s) {
if (($s['id'] ?? '') === $atts['id']) { $srv = $s; break; }
if (($s['name'] ?? '') === $atts['id']) { $srv = $s; break; }
}
if (!$srv) return 'Server nicht gefunden';
// MAINTENANCE MODE (Rich Style - No Border)
$maintenance_mode = !empty($srv['maintenance_mode']);
$maintenance_message = $srv['maintenance_message'] ?? 'Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!';
if ($maintenance_mode) {
$uid = md5($srv['host']);
$logo = $srv['logo_id'] ? wp_get_attachment_image_url($srv['logo_id'], 'full') : ($srv['logo_url'] ?: MCSS_URL.'img/default-server-logo.png');
$name_color = $srv['name_color'] ?? '#333333';
$name_size = $srv['name_size'] ?? '1.8em';
ob_start(); ?>
<style>
@keyframes pulse {
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(251, 146, 60, 0.7); }
70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(251, 146, 60, 0); }
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(251, 146, 60, 0); }
}
.mcss-status-maintenance { animation: pulse 2s infinite; }
</style>
<!-- FIX: border:none hinzugefügt -->
<div id="mcss-widget-<?php echo esc_attr($uid); ?>" style="max-width:650px;margin:30px auto;padding:0; background:#fef3c7;border:none;border-radius:20px; overflow:hidden;box-shadow:0 16px 40px rgba(0,0,0,0.12); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
<!-- Rich Header -->
<div style="padding:28px 32px;background:#fef3c7;border-bottom:1px solid #fbbf24;display:flex;align-items:center;gap:20px;">
<img src="<?php echo esc_url($logo); ?>" alt="Logo" loading="lazy" style="width:60px;height:60px;border-radius:12px;box-shadow:0 8px 20px rgba(0,0,0,0.15);" onerror="this.src='<?php echo MCSS_URL.'img/default-server-logo.png'; ?>'" />
<div style="flex:1;">
<h2 style="margin:0 0 8px 0;font-size:<?php echo $name_size; ?>;color:<?php echo $name_color; ?>;font-weight:800;"><?php echo esc_html($srv['name']); ?></h2>
<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px;">
<span class="mcss-status-maintenance" style="width:14px;height:14px;border-radius:50%;background:#fb923c;box-shadow:0 0 16px rgba(251, 146, 60, 0.5);"></span>
<span style="font-weight:600;color:#92400e;font-size:1em;">Wartungsmodus</span>
</div>
</div>
</div>
<!-- Rich Body -->
<div style="padding:24px 32px;background:#fef3c7;">
<div style="margin-bottom:16px;font-weight:700;color:#92400e;font-size:1.05em;">Wartungshinweis:</div>
<div style="font-size:1.1em;color:#78350f;line-height:1.6;"><?php echo wp_kses_post($maintenance_message); ?></div>
<div style="margin-top:20px;font-size:0.9em;color:#92400e;">
<p>Wir arbeiten daran, den Server so schnell wie möglich wieder verfügbar zu machen. Vielen Dank für deine Geduld!</p>
</div>
</div>
</div>
<?php return ob_get_clean();
}
// NORMAL MODE (No Border)
$data = mcss_fetch_server_with_ranks($srv);
$uid = md5($srv['host']);
// STYLES
$name_color = $srv['name_color'] ?? '#333333';
$name_size = $srv['name_size'] ?? '1.3em';
$ct_color = $srv['ct_color'] ?? '#555555';
$ct_size = $srv['ct_size'] ?? '0.9em';
$widget_width = "650px";
$widget_padding = "15px";
$logo_size = "70px";
$player_head_size = "32px";
// Copy Logic (player_port_copy)
$copy_addr = !empty($srv['copy_address']) ? $srv['copy_address'] : $srv['host'];
if (empty($srv['hide_port']) && !empty($srv['player_port_copy'])) {
$copy_addr .= ':' . $srv['player_port_copy'];
}
// ANNOUNCEMENT CHECK
$show_announcement = mcss_should_show_announcement($srv);
$announcement_text = $srv['announcement_text'] ?? '';
$announcement_type = $srv['announcement_type'] ?? 'info';
$announcement_style = mcss_get_announcement_style($announcement_type);
ob_start(); ?>
<style>
@keyframes pulse { 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); } 70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(16, 185, 129, 0); } 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); } }
.mcss-status-online { animation: pulse 2s infinite; }
@keyframes slideDown { 0% { transform: translateY(-100%); opacity: 0; } 100% { transform: translateY(0); opacity: 1; } }
.mcss-announcement { animation: slideDown 0.5s ease-out; }
</style>
<!-- FIX: border:none hinzugefügt -->
<div id="mcss-widget-<?php echo esc_attr($uid); ?>" style="max-width:<?php echo $widget_width; ?>;margin:20px auto;padding:<?php echo $widget_padding; ?>;background:white;border:none;border-radius:10px;box-shadow:0 4px 15px rgba(0,0,0,0.1);font-family:sans-serif;position:relative;">
<!-- POPUP / TOAST -->
<div id="mcss-toast-<?php echo esc_attr($uid); ?>" style="position:absolute;top:10px;right:10px;background:#4CAF50;color:white;padding:8px 16px;border-radius:4px;font-size:0.9em;box-shadow:0 2px 5px rgba(0,0,0,0.2);opacity:0;transition:opacity 0.5s ease;pointer-events:none;z-index:10;">IP kopiert!</div>
<!-- BANNER -->
<?php if ($show_announcement): ?>
<div class="mcss-announcement" style="padding:12px 20px;background:<?php echo esc_attr($announcement_style['bg']); ?>;border:1px solid <?php echo esc_attr($announcement_style['border']); ?>;border-radius:10px;margin:0 15px 15px 15px;display:flex;align-items:center;gap:12px;">
<span style="font-size:1.5em;"><?php echo esc_html($announcement_style['icon']); ?></span>
<div style="flex:1;font-size:1.0em;font-weight:600;color:<?php echo esc_attr($announcement_style['text']); ?>;"><?php echo wp_kses_post($announcement_text); ?></div>
<button type="button" class="mcss-announcement-close" style="background:none;border:none;color:<?php echo esc_attr($announcement_style['text']); ?>;cursor:pointer;font-size:1.2em;padding:0;line-height:1;" onclick="this.parentElement.style.display='none';">×</button>
</div>
<?php endif; ?>
<!-- HEADER -->
<div style="display:flex;align-items:center;gap:12px;border-bottom:1px solid #eee;padding-bottom:12px;">
<img src="<?php echo esc_url($srv['logo_url'] ?: MCSS_URL.'img/default-server-logo.png'); ?>" style="width:<?php echo $logo_size; ?>;height:<?php echo $logo_size; ?>;border-radius:6px;">
<!-- GEÄNDERTER HEADER: Name -> IP + Status -> Zusatztext -->
<div style="flex:1;display:flex;flex-direction:column;gap:6px;">
<!-- NAME -->
<h2 id="mcss-name-<?php echo esc_attr($uid); ?>"
style="margin:0;font-size:<?php echo $name_size; ?>;color:<?php echo $name_color; ?>;
cursor:pointer;user-select:none;font-weight:700;"
title="Klicken um IP zu kopieren">
<?php echo esc_html($srv['name']); ?>
</h2>
<!-- IP + STATUS -->
<div style="display:flex;align-items:center;gap:10px;font-size:0.9em;font-weight:600;">
<!-- IP -->
<span style="color:#374151;" id="mcss-ip-<?php echo esc_attr($uid); ?>">
<?php echo esc_html($copy_addr); ?>
</span>
<!-- PULSIERENDER STATUSPUNKT -->
<span id="mcss-status-dot-<?php echo esc_attr($uid); ?>"
class="<?php echo $data['online'] ? 'mcss-status-online' : ''; ?>"
style="width:10px;height:10px;border-radius:50%;
background:<?php echo $data['online'] ? '#10b981' : '#ef4444'; ?>;
box-shadow:0 0 8px <?php echo $data['online'] ? 'rgba(16,185,129,.7)' : 'rgba(239,68,68,.7)'; ?>;">
</span>
<!-- STATUS TEXT (EINDEUTIG) -->
<span id="mcss-status-text-<?php echo esc_attr($uid); ?>"
style="color:<?php echo $data['online'] ? '#10b981' : '#ef4444'; ?>;">
<?php echo $data['online'] ? 'Online' : 'Offline'; ?>
</span>
</div>
<!-- ZUSATZTEXT (wird NICHT von JS angefasst) -->
<?php if (!empty($srv['custom_text'])): ?>
<div id="mcss-custom-text-<?php echo esc_attr($uid); ?>" style="font-size:<?php echo $ct_size; ?>;color:<?php echo $ct_color; ?>;font-weight:500;">
<?php echo wp_kses_post($srv['custom_text']); ?>
</div>
<?php endif; ?>
</div>
</div>
<!-- INFO BAR -->
<div style="display:flex;justify-content:space-between;margin-top:12px;margin-bottom:30px;font-size:0.85em;color:#666;">
<span>Spieler: <strong id="mcss-player-count-<?php echo esc_attr($uid); ?>"><?php echo count($data['players']); ?></strong></span>
<span>Version: <strong id="mcss-version-<?php echo esc_attr($uid); ?>"><?php echo esc_html($data['version']); ?></strong></span>
<span>Ping: <strong id="mcss-ping-<?php echo esc_attr($uid); ?>"><?php echo esc_html($data['ping']); ?> ms</strong></span>
</div>
<!-- PLAYER GRID -->
<span style="color:#666;margin-bottom:15px;">Spieler:</span>
<div id="mcss-player-grid-<?php echo esc_attr($uid); ?>" style="display:flex;flex-wrap:wrap;gap:8px;margin-top:8px;color:#666;justify-content:center;">
<?php if (!empty($data['players']) && is_array($data['players'])): ?>
<?php foreach ($data['players'] as $p): ?>
<div style="text-align:center;">
<img src="<?php echo esc_url($p['avatar']); ?>" style="width:<?php echo $player_head_size; ?>;height:<?php echo $player_head_size; ?>;border-radius:4px;">
<div style="font-size:0.75em;"><?php echo esc_html($p['name']); ?></div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div style="width:100%;text-align:center;font-size:0.9em;color:#999;margin-top:4px;">Keine Spieler Online</div>
<?php endif; ?>
</div>
</div>
</div>
<script>
(function(){
// COPY FUNCTION
function mcss_copy_<?php echo esc_attr($uid); ?>() {
const text = "<?php echo esc_js($copy_addr); ?>";
navigator.clipboard.writeText(text).then(function() {
const toast = document.getElementById('mcss-toast-<?php echo esc_attr($uid); ?>');
if(toast) {
toast.style.opacity = '1';
setTimeout(function() { toast.style.opacity = '0'; }, 3000);
}
}).catch(function(err){
console.error('mcss copy error:', err);
});
}
// SET CLICK ON NAME
const nameEl = document.getElementById('mcss-name-<?php echo esc_attr($uid); ?>');
if(nameEl) nameEl.addEventListener('click', mcss_copy_<?php echo esc_attr($uid); ?>);
// CLOSE ANNOUNCEMENT FUNCTION
document.querySelectorAll('.mcss-announcement-close').forEach(function(btn){
btn.addEventListener('click', function(e){
e.preventDefault();
var banner = this.closest('.mcss-announcement');
if(banner) {
banner.style.transition = 'opacity 0.5s ease';
banner.style.opacity = '0';
setTimeout(function(){ banner.style.display = 'none'; }, 500);
}
});
});
// CACHE REFS
var statusTextEl = document.getElementById('mcss-status-text-<?php echo esc_js($uid); ?>');
var statusDotEl = document.getElementById('mcss-status-dot-<?php echo esc_js($uid); ?>');
var playerCountEl = document.getElementById('mcss-player-count-<?php echo esc_js($uid); ?>');
var versionEl = document.getElementById('mcss-version-<?php echo esc_js($uid); ?>');
var pingEl = document.getElementById('mcss-ping-<?php echo esc_js($uid); ?>');
var playerGridEl = document.getElementById('mcss-player-grid-<?php echo esc_js($uid); ?>');
// SAFELY update DOM from API response
function applyUpdate(d) {
if (!d) return;
// Status text + color
if (statusTextEl) {
statusTextEl.textContent = d.online ? 'Online' : 'Offline';
statusTextEl.style.color = d.online ? '#10b981' : '#ef4444';
}
// Status dot
if (statusDotEl) {
statusDotEl.style.background = d.online ? '#10b981' : '#ef4444';
statusDotEl.style.boxShadow = d.online ? '0 0 8px rgba(16,185,129,.7)' : '0 0 8px rgba(239,68,68,.7)';
statusDotEl.classList.toggle('mcss-status-online', !!d.online);
}
// Player count
if (playerCountEl) {
var count = (d.players && Array.isArray(d.players)) ? d.players.length : 0;
playerCountEl.textContent = count;
}
// Version + Ping
if (versionEl) versionEl.textContent = d.version ? d.version : '';
if (pingEl) pingEl.textContent = (typeof d.ping !== 'undefined') ? d.ping + ' ms' : '';
// Player grid
if (playerGridEl) {
var html = '';
if (d.players && Array.isArray(d.players) && d.players.length > 0) {
d.players.forEach(function(p){
html += '<div style="text-align:center;">' +
'<img src="' + (p.avatar ? p.avatar : '') + '" style="width:<?php echo $player_head_size; ?>;height:<?php echo $player_head_size; ?>;border-radius:4px;">' +
'<div style="font-size:0.75em;">' + (p.name ? p.name : '') + '</div>' +
'</div>';
});
} else {
html = '<div style="width:100%;text-align:center;font-size:0.9em;color:#999;margin-top:4px;">Keine Spieler Online</div>';
}
playerGridEl.innerHTML = html;
}
}
// POLL alle 2s
setInterval(function(){
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: 'action=mcss_fetch&server_id=<?php echo esc_js($srv['id']); ?>'
})
.then(r => r.json())
.then(d => { if(d) applyUpdate(d); })
.catch(err => console.error('mcss_fetch error:', err));
}, 2000);
})();
</script>
<?php return ob_get_clean();
}