Files
Minecraft-BungeeCord-Status/Minecraft-BungeeCord-Status/minecraft-bungeecord-status.php

877 lines
46 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: 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.1
* 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';
/* ---------------- HELPER: MINECRAFT COLORS ---------------- */
function mcss_format_minecraft_colors($text) {
if (empty($text)) return '';
// Minecraft Color Map
$color_map = [
'0' => '#000000', // Black
'1' => '#0000AA', // Dark Blue
'2' => '#00AA00', // Dark Green
'3' => '#00AAAA', // Dark Aqua
'4' => '#AA0000', // Dark Red
'5' => '#AA00AA', // Dark Purple
'6' => '#FFAA00', // Gold
'7' => '#AAAAAA', // Gray
'8' => '#555555', // Dark Gray
'9' => '#5555FF', // Blue
'a' => '#55FF55', // Green
'b' => '#55FFFF', // Aqua
'c' => '#FF5555', // Red
'd' => '#FF55FF', // Light Purple
'e' => '#FFFF55', // Yellow
'f' => '#FFFFFF' // White
];
// Base Wrapper
$formatted = '<span style="color:#AAAAAA;">'; // Standard Grau
// Ersetze Farben: &c -> </span><span style="color:#HEX">
foreach ($color_map as $char => $hex) {
$text = str_replace("&" . $char, "</span><span style=\"color:$hex;\">", $text);
}
// Formatierungen
$text = str_replace("&l", "</span><span style=\"font-weight:bold;\">", $text); // Bold
$text = str_replace("&o", "</span><span style=\"font-style:italic;\">", $text); // Italic
$text = str_replace("&n", "</span><span style=\"text-decoration:underline;\">", $text); // Underline
$text = str_replace("&m", "</span><span style=\"text-decoration:line-through;\">", $text); // Strikethrough
$text = str_replace("&r", "</span><span style=\"color:#AAAAAA;\">", $text); // Reset to Gray
$formatted .= $text . '</span>';
return $formatted;
}
/* ---------------- AUTO UPDATE (MCSS) ---------------- */
if ( ! class_exists( 'MCSS_Auto_Update' ) ) {
class MCSS_Auto_Update {
private $plugin_file;
private $repo_owner = 'M_Viper';
private $repo_name = 'Minecraft-BungeeCord-Status';
private $api_url;
private $transient_key;
public function __construct( $plugin_file ) {
$this->plugin_file = $plugin_file;
$this->api_url = 'https://git.viper.ipv64.net/api/v1/repos/' . rawurlencode( $this->repo_owner ) . '/' . rawurlencode( $this->repo_name ) . '/releases';
$this->transient_key = 'mcss_update_check_' . md5( $this->repo_owner . '/' . $this->repo_name );
if ( is_admin() ) {
add_action( 'admin_init', array( $this, 'check_for_update' ) );
}
}
public function check_for_update() {
if ( ! function_exists( 'get_file_data' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$current = get_file_data( $this->plugin_file, array( 'Version' => 'Version' ), 'plugin' );
$current_version = isset( $current['Version'] ) ? $current['Version'] : '0.0.0';
$latest = $this->get_latest_release_info();
if ( $latest && ! empty( $latest['version'] ) && ! empty( $latest['url'] ) ) {
if ( version_compare( $latest['version'], $current_version, '>' ) ) {
add_action( 'admin_notices', function() use ( $latest, $current_version ) {
?>
<div class="notice notice-warning is-dismissible">
<p>
<strong>Minecraft BungeeCord Status Update verfügbar</strong><br>
Neue Version: <strong><?php echo esc_html( $latest['version'] ); ?></strong><br>
Installiert: <strong><?php echo esc_html( $current_version ); ?></strong><br>
<a href="<?php echo esc_url( $latest['url'] ); ?>" class="button button-primary" target="_blank" rel="noreferrer noopener">Direkter Download (ZIP)</a>
<a href="<?php echo esc_url( 'https://git.viper.ipv64.net/' . rawurlencode( $this->repo_owner ) . '/' . rawurlencode( $this->repo_name ) . '/releases' ); ?>" class="button" target="_blank" style="margin-left:8px;" rel="noreferrer noopener">Releases ansehen</a>
</p>
</div>
<?php
} );
}
}
}
private function get_latest_release_info() {
$cached = get_transient( $this->transient_key );
if ( false !== $cached && is_array( $cached ) && ! empty( $cached['version'] ) ) {
return $cached;
}
$result = false;
$response = wp_remote_get( $this->api_url, array(
'timeout' => 8,
'headers' => array(
'Accept' => 'application/json',
'User-Agent' => 'MCSS-Update-Checker/1.0',
),
) );
if ( is_wp_error( $response ) ) {
set_transient( $this->transient_key, array( 'version' => '', 'url' => '' ), HOUR_IN_SECONDS );
return false;
}
$code = wp_remote_retrieve_response_code( $response );
if ( 200 !== (int) $code ) {
set_transient( $this->transient_key, array( 'version' => '', 'url' => '' ), HOUR_IN_SECONDS );
return false;
}
$body = wp_remote_retrieve_body( $response );
$json = json_decode( $body, true );
if ( ! is_array( $json ) || empty( $json ) ) {
set_transient( $this->transient_key, array( 'version' => '', 'url' => '' ), HOUR_IN_SECONDS );
return false;
}
foreach ( $json as $release ) {
$tag = '';
if ( ! empty( $release['tag_name'] ) ) {
$tag = ltrim( (string) $release['tag_name'], 'vV' );
} elseif ( ! empty( $release['name'] ) ) {
$tag = ltrim( (string) $release['name'], 'vV' );
}
if ( ! empty( $release['assets'] ) && is_array( $release['assets'] ) ) {
foreach ( $release['assets'] as $asset ) {
if ( empty( $asset['name'] ) || empty( $asset['browser_download_url'] ) ) {
continue;
}
if ( strtolower( $asset['name'] ) === 'minecraft-bungeecord-status.zip' ) {
$version = $tag ?: $this->extract_version_from_string( $asset['name'] . ' ' . ( $release['name'] ?? '' ) );
if ( $version ) {
$result = array(
'version' => $version,
'url' => $asset['browser_download_url'],
);
break 2;
} else {
$result = array(
'version' => ( $tag ?: (string) ( $release['name'] ?? '' ) ),
'url' => $asset['browser_download_url'],
);
break 2;
}
}
}
}
}
if ( $result ) {
set_transient( $this->transient_key, $result, 12 * HOUR_IN_SECONDS );
return $result;
}
set_transient( $this->transient_key, array( 'version' => '', 'url' => '' ), 6 * HOUR_IN_SECONDS );
return false;
}
private function extract_version_from_string( $str ) {
if ( preg_match( '/([0-9]+\.[0-9]+(?:\.[0-9]+)?)/', $str, $m ) ) {
return $m[1];
}
return '';
}
}
new MCSS_Auto_Update( __FILE__ );
}
/* ---------------- 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.8.1');
wp_enqueue_script('mcss-frontend-js', MCSS_URL . 'js/mcss-frontend.js', ['jquery'], '3.8.1', 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'),
'player_port_copy' => sanitize_text_field($srv['player_port_copy'] ?? ''),
'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'] ?? ''),
'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'),
'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_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]];
}
$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>Wichtig:</strong> Bitte das Plugin StatusAPI.jar im Bungeecord Installieren.
</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 = [];
foreach ($api_data['players'] as $player_data) {
if (is_array($player_data)) {
$name = $player_data['name'];
$prefix = $player_data['prefix'] ?? '';
} else {
// Fallback für alte API
$name = $player_data;
$prefix = '';
}
// 1. Prefix mit Farben konvertieren
$prefix_html = mcss_format_minecraft_colors($prefix);
// 2. Namen immer Schwarz darstellen (überschreibt eventuelle Farben aus dem Prefix)
$name_html = '<span style="color:black;">' . esc_html($name) . '</span>';
// 3. Zusammenbauen (Nur Abstand, wenn Prefix existiert)
if (!empty($prefix_html)) {
$display_html = $prefix_html . ' ' . $name_html;
} else {
$display_html = $name_html;
}
$players_info[] = [
'name' => $name,
'avatar' => "https://mc-heads.net/avatar/" . rawurlencode($name) . "/64",
'prefix' => $prefix,
'display_html' => $display_html,
'rank' => $prefix ?: 'Spieler',
'color' => '#566d8dff',
];
}
$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 = max(2, absint($srv['cache_ttl'] ?? 10));
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
$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>
<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;">
<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>
<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
$data = mcss_fetch_server_with_ranks($srv);
$uid = md5($srv['host']);
$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_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'];
}
$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>
<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;">
<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;">
<span style="color:#374151;" id="mcss-ip-<?php echo esc_attr($uid); ?>">
<?php echo esc_html($copy_addr); ?>
</span>
<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>
<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 -->
<?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 $p['display_html']; ?></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);
});
}
const nameEl = document.getElementById('mcss-name-<?php echo esc_attr($uid); ?>');
if(nameEl) nameEl.addEventListener('click', mcss_copy_<?php echo esc_attr($uid); ?>);
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){
var content = p.display_html ? p.display_html : p.name;
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;">' + content + '</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();
}