1162 lines
64 KiB
PHP
1162 lines
64 KiB
PHP
<?php
|
||
/**
|
||
* Plugin Name: Minecraft Server Status – Multi-Server Edition
|
||
* Description: Die ultimative Live-Status-Anzeige für deine Minecraft Server mit Echtzeit-Updates für beliebig viele Server.
|
||
* Tags: minecraft, server status, player list, multi-server, rcon, luckperms, widget, shortcode, gaming, customizable, live, ping, maintenance, gameserver
|
||
* Version: 2.0.5
|
||
* Author: M_Viper
|
||
* Plugin URI: https://git.viper.ipv64.net/M_Viper/Minecraft-Server-Status
|
||
* Author URI: https://m-viper.de
|
||
* Requires at least: 6.7.2
|
||
* Tested up to: 6.7.2
|
||
* Requires PHP: 7.4
|
||
* License: GPL2
|
||
* Text Domain: wp-multi-mc-server
|
||
* Support: [Telegram Support](https://t.me/M_Viper04)
|
||
*/
|
||
|
||
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';
|
||
|
||
/* ---------------- Enqueue 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'], '4.0.1', true);
|
||
}
|
||
});
|
||
|
||
add_action('wp_enqueue_scripts', function(){
|
||
wp_enqueue_style('mcss-style', MCSS_URL . 'css/style.css', [], '4.0.1');
|
||
wp_enqueue_script('mcss-frontend-js', MCSS_URL . 'js/mcss-frontend.js', ['jquery'], '4.0.1', true);
|
||
wp_localize_script('mcss-frontend-js', 'mcss_ajax_object', [
|
||
'ajax_url' => admin_url('admin-ajax.php'),
|
||
'refresh_interval' => max(10, intval(get_option('mcss_cache_ttl', 15))) * 1000
|
||
]);
|
||
});
|
||
|
||
/* ---------------- Settings ---------------- */
|
||
add_action('admin_menu', function(){
|
||
add_options_page('Minecraft Server', 'Minecraft Server', 'manage_options', 'mcss-settings', 'mcss_settings_page');
|
||
});
|
||
|
||
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'] ?? 'Server'),
|
||
'host' => sanitize_text_field($srv['host'] ?? ''),
|
||
'query_port' => absint($srv['query_port'] ?? 25565),
|
||
'rcon_port' => absint($srv['rcon_port'] ?? 25575),
|
||
'rcon_pass' => sanitize_text_field($srv['rcon_pass'] ?? ''),
|
||
'player_port' => sanitize_text_field($srv['player_port'] ?? ''),
|
||
'hide_port' => !empty($srv['hide_port']),
|
||
'show_motd' => !empty($srv['show_motd']),
|
||
'cache_ttl' => max(5, absint($srv['cache_ttl'] ?? 15)),
|
||
'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'] ?? '#1e293b'),
|
||
'name_size' => sanitize_text_field($srv['name_size'] ?? '1.8em'),
|
||
'maintenance_mode' => !empty($srv['maintenance_mode']),
|
||
'maintenance_message' => wp_kses_post($srv['maintenance_message'] ?? 'Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!'),
|
||
'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']),
|
||
'groups' => sanitize_text_field($r['groups'] ?? ''),
|
||
'color' => sanitize_text_field($r['color'] ?? '#6c5ce7')
|
||
];
|
||
}
|
||
return wp_json_encode($out);
|
||
}
|
||
|
||
/* ---------------- Settings Page – 100% wie dein Original ---------------- */
|
||
function mcss_settings_page() {
|
||
$servers = get_option('mcss_servers', []);
|
||
if (empty($servers)) {
|
||
$servers = [[
|
||
'id' => 'default', 'name' => 'Hauptserver', 'host' => '', 'query_port' => 25565,
|
||
'rcon_port' => 25575, 'cache_ttl' => 15, '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ß (Standard)','1.7em' => 'Extra groß','2em' => 'Riesig',
|
||
'2.5em' => 'Enorm','3em' => 'Gigantisch',
|
||
];
|
||
|
||
$announcement_types = [
|
||
'info' => 'Information (Blau)',
|
||
'warning' => 'Warnung (Orange)',
|
||
'success' => 'Erfolg (Grün)',
|
||
'error' => 'Fehler (Rot)',
|
||
];
|
||
?>
|
||
<div class="wrap">
|
||
<h1>Minecraft Server Einstellungen</h1>
|
||
<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_server_row($srv, $i, $font_sizes, $announcement_types); endforeach; ?>
|
||
</div>
|
||
<p><button type="button" class="button button-primary" id="mcss-add-server">+ Neuen Server hinzufügen</button></p>
|
||
<?php submit_button(); ?>
|
||
</form>
|
||
|
||
<h2>Tools</h2>
|
||
<p><a class="button" href="<?php echo esc_url(add_query_arg('mcss_test_connection','1', admin_url('options-general.php?page=mcss-settings'))); ?>">RCON testen</a></p>
|
||
<?php if (isset($_GET['mcss_test_connection']) && !empty($servers[0])): ?>
|
||
<pre style="background:#fff;padding:12px;border-radius:8px;"><?php echo esc_html(print_r(mcss_test_rcon_now($servers[0]), true)); ?></pre>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<script>
|
||
jQuery(function($){
|
||
let counter = <?php echo count($servers); ?>;
|
||
|
||
// Button-Event-Handler - direkt und einfach
|
||
$('#mcss-add-server').on('click', function(e){
|
||
e.preventDefault();
|
||
|
||
// Neue Server-Zeile erstellen
|
||
const newId = 'new_' + counter;
|
||
const newIndex = counter;
|
||
|
||
// HTML für neue Server-Zeile
|
||
const newServerHtml = `
|
||
<div class="mcss-server-row" style="border:2px solid #ddd;border-radius:12px;padding:20px;margin:20px 0;background:#fdfdfd;position:relative;">
|
||
<button type="button" class="notice-dismiss mcss-remove-server" style="position:absolute;top:10px;right:10px;"></button>
|
||
<h3>Server ${newIndex + 1}: Neuer Server</h3>
|
||
<table class="form-table">
|
||
<tr><th>Name (intern)</th><td><input name="mcss_servers[${newIndex}][name]" value="Neuer Server" class="regular-text server-name-input" required></td></tr>
|
||
<tr>
|
||
<th>Name – Schrift</th>
|
||
<td>
|
||
<label>Farbe:</label>
|
||
<input name="mcss_servers[${newIndex}][name_color]" value="#1e293b" class="mcss-color-picker" data-default-color="#1e293b" style="width:100px;margin-left:8px;" />
|
||
<label style="margin-left:20px;">Größe:</label>
|
||
<select name="mcss_servers[${newIndex}][name_size]" style="width:200px;margin-left:8px;">
|
||
<?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>Server Host</th><td><input name="mcss_servers[${newIndex}][host]" value="" class="regular-text" required></td></tr>
|
||
<tr><th>RCON Port</th><td><input name="mcss_servers[${newIndex}][rcon_port]" type="number" value="25575" class="small-text"></td></tr>
|
||
<tr><th>RCON Passwort</th><td><input name="mcss_servers[${newIndex}][rcon_pass]" type="password" value="" class="regular-text"></td></tr>
|
||
<tr><th>Cache TTL (Sekunden)</th><td><input name="mcss_servers[${newIndex}][cache_ttl]" type="number" value="15" class="small-text"></td></tr>
|
||
<tr><th>MOTD anzeigen?</th><td><input name="mcss_servers[${newIndex}][show_motd]" type="checkbox" value="1" checked /></td></tr>
|
||
<tr>
|
||
<th><label for="mcss_player_port_${newIndex}"><strong>Spieler-Port (für Kopieren)</strong></label></th>
|
||
<td>
|
||
<input name="mcss_servers[${newIndex}][player_port]" id="mcss_player_port_${newIndex}" type="text" value="" class="regular-text" style="width:180px;" placeholder="z. B. 25565" />
|
||
<p class="description" style="margin-top:8px;">Wird <strong>nicht angezeigt</strong>, aber beim Klick mitkopiert.<br>Leer = kein Port wird kopiert.</p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th>Port in Adresse ausblenden?</th>
|
||
<td><input name="mcss_servers[${newIndex}][hide_port]" type="checkbox" value="1" checked /> <label>Ja – zeige nur den Host an</label></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label>Server Logo</label></th>
|
||
<td>
|
||
<div id="mcss-logo-preview_${newIndex}" style="margin-bottom:12px;">
|
||
<img src="<?php echo MCSS_URL; ?>img/default-server-logo.png" style="max-width:150px;max-height:150px;border-radius:8px;" />
|
||
</div>
|
||
<input type="hidden" name="mcss_servers[${newIndex}][logo_id]" value="0">
|
||
<input type="text" name="mcss_servers[${newIndex}][logo_url]" value="" class="regular-text" placeholder="Oder direkte URL...">
|
||
<button type="button" class="button mcss-upload-logo-button" id="mcss_upload_logo_button_${newIndex}">Hochladen / auswählen</button>
|
||
<button type="button" class="button mcss-remove-logo-button" id="mcss_remove_logo_button_${newIndex}" style="display:none;">Entfernen</button>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label>Zusatztext unter der Adresse</label></th>
|
||
<td>
|
||
<input name="mcss_servers[${newIndex}][custom_text]" value="" class="large-text" placeholder="1.21 Survival • Whitelist aktiv • Discord" />
|
||
<p class="description">HTML + Emojis erlaubt.</p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th>IP-Adresse – Schrift</th>
|
||
<td>
|
||
<label>Farbe:</label>
|
||
<input name="mcss_servers[${newIndex}][ip_color]" value="#1f2937" class="mcss-color-picker" data-default-color="#1f2937" style="width:100px;margin-left:8px;" />
|
||
<label style="margin-left:20px;">Größe:</label>
|
||
<select name="mcss_servers[${newIndex}][ip_size]" style="width:200px;margin-left:8px;">
|
||
<?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>Zusatztext – Schrift</th>
|
||
<td>
|
||
<label>Farbe:</label>
|
||
<input name="mcss_servers[${newIndex}][ct_color]" value="#1e293b" class="mcss-color-picker" data-default-color="#1e293b" style="width:100px;margin-left:8px;" />
|
||
<label style="margin-left:20px;">Größe:</label>
|
||
<select name="mcss_servers[${newIndex}][ct_size]" style="width:200px;margin-left:8px;">
|
||
<?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><label>Wartungsmodus</label></th>
|
||
<td>
|
||
<input name="mcss_servers[${newIndex}][maintenance_mode]" type="checkbox" value="1" />
|
||
<label>Wartungsmodus aktivieren</label>
|
||
<p class="description" style="margin-top:8px;">Wenn aktiviert, wird eine Wartungsnachricht anstelle der Server-Informationen angezeigt.</p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label>Wartungsnachricht</label></th>
|
||
<td>
|
||
<textarea name="mcss_servers[${newIndex}][maintenance_message]" class="large-text" rows="4" placeholder="Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!">Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!</textarea>
|
||
<p class="description">HTML + Emojis erlaubt. Diese Nachricht wird angezeigt, wenn der Wartungsmodus aktiv ist.</p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label>Ankündigungen</label></th>
|
||
<td>
|
||
<input name="mcss_servers[${newIndex}][announcement_enabled]" type="checkbox" value="1" />
|
||
<label>Ankündigungen aktivieren</label>
|
||
<p class="description" style="margin-top:8px;">Wenn aktiviert, werden zeitlich begrenzte Ankündigungen über dem Widget angezeigt.</p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label>Ankündigungstext</label></th>
|
||
<td>
|
||
<textarea name="mcss_servers[${newIndex}][announcement_text]" class="large-text" rows="3" placeholder="🎉 Server-Event am Wochenende! Kommt vorbei und gewinnt tolle Preise!">🎉 Server-Event am Wochenende! Kommt vorbei und gewinnt tolle Preise!</textarea>
|
||
<p class="description">HTML + Emojis erlaubt. Diese Nachricht wird als Ankündigung angezeigt.</p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label>Ankündigungszeitraum</label></th>
|
||
<td>
|
||
<div style="display:flex;gap:10px;align-items:center;">
|
||
<div style="flex:1;">
|
||
<label for="mcss_announcement_start_${newIndex}">Startdatum:</label>
|
||
<input name="mcss_servers[${newIndex}][announcement_start]" id="mcss_announcement_start_${newIndex}" type="text" class="regular-text mcss-datepicker" placeholder="TT.MM.JJJJ HH:MM" />
|
||
</div>
|
||
<div style="flex:1;">
|
||
<label for="mcss_announcement_end_${newIndex}">Enddatum:</label>
|
||
<input name="mcss_servers[${newIndex}][announcement_end]" id="mcss_announcement_end_${newIndex}" type="text" class="regular-text mcss-datepicker" placeholder="TT.MM.JJJJ HH:MM" />
|
||
</div>
|
||
</div>
|
||
<p class="description" style="margin-top:8px;">Die Ankündigung wird nur im angegebenen Zeitraum angezeigt. Leerlassen für dauerhafte Anzeige.</p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label>Ankündigungstyp</label></th>
|
||
<td>
|
||
<select name="mcss_servers[${newIndex}][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>
|
||
<p class="description" style="margin-top:8px;">Bestimmt das Aussehen der Ankündigung.</p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
<h2>Ränge (LuckPerms Zuordnung)</h2>
|
||
<p>Die Ränge werden automatisch über LuckPerms per RCON abgerufen.</p>
|
||
<div id="mcss-ranks-editor_${newIndex}" class="mcss-ranks-editor"></div>
|
||
<textarea id="mcss_ranks_json_${newIndex}" name="mcss_servers[${newIndex}][ranks_json]" class="mcss-ranks-json" style="display:none;">[]</textarea>
|
||
<input type="hidden" name="mcss_servers[${newIndex}][id]" value="${newId}">
|
||
</div>
|
||
`;
|
||
|
||
// HTML zum DOM hinzufügen
|
||
$('#mcss-servers-repeater').append(newServerHtml);
|
||
counter++;
|
||
|
||
// Farbpicker für neue Zeile initialisieren
|
||
$('#mcss-servers-repeater .mcss-server-row:last .mcss-color-picker').wpColorPicker();
|
||
|
||
// Datepicker für neue Zeile initialisieren
|
||
$('#mcss-servers-repeater .mcss-server-row:last .mcss-datepicker').datepicker({
|
||
dateFormat: 'dd.mm.yy',
|
||
timeFormat: 'HH:mm'
|
||
});
|
||
});
|
||
|
||
// Event-Handler für das Entfernen von Servern
|
||
$(document).on('click', '.mcss-remove-server', function(){
|
||
$(this).closest('.mcss-server-row').remove();
|
||
});
|
||
|
||
// Farbpicker für bestehende Zeilen initialisieren
|
||
$('.mcss-color-picker').wpColorPicker();
|
||
|
||
// Datepicker für bestehende Zeilen initialisieren
|
||
$('.mcss-datepicker').datepicker({
|
||
dateFormat: 'dd.mm.yy',
|
||
timeFormat: 'HH:mm'
|
||
});
|
||
});
|
||
</script>
|
||
<?php
|
||
}
|
||
|
||
function mcss_render_server_row($srv, $i, $font_sizes, $announcement_types) {
|
||
$logo_id = $srv['logo_id'] ?? 0;
|
||
$logo_url = $srv['logo_url'] ?? '';
|
||
$current_logo = $logo_id ? wp_get_attachment_image_url($logo_id, 'medium') : $logo_url;
|
||
if (!$current_logo) $current_logo = MCSS_URL . 'img/default-server-logo.png';
|
||
?>
|
||
<div class="mcss-server-row" style="border:2px solid #ddd;border-radius:12px;padding:20px;margin:20px 0;background:#fdfdfd;position:relative;">
|
||
<button type="button" class="notice-dismiss mcss-remove-server" style="position:absolute;top:10px;right:10px;"></button>
|
||
<h3>Server <?php echo $i+1; ?>: <span class="server-name-display"><?php echo esc_html($srv['name'] ?? ''); ?></span></h3>
|
||
<table class="form-table">
|
||
<tr><th>Name (intern)</th><td><input name="mcss_servers[<?php echo $i; ?>][name]" value="<?php echo esc_attr($srv['name'] ?? ''); ?>" class="regular-text server-name-input" required></td></tr>
|
||
<tr>
|
||
<th>Name – Schrift</th>
|
||
<td>
|
||
<label>Farbe:</label>
|
||
<input name="mcss_servers[<?php echo $i; ?>][name_color]" value="<?php echo esc_attr($srv['name_color'] ?? '#1e293b'); ?>" class="mcss-color-picker" data-default-color="#1e293b" style="width:100px;margin-left:8px;" />
|
||
<label style="margin-left:20px;">Größe:</label>
|
||
<select name="mcss_servers[<?php echo $i; ?>][name_size]" style="width:200px;margin-left:8px;">
|
||
<?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'] ?? '1.8em'); ?>" <?php echo !isset($font_sizes[$srv['name_size'] ?? '']) ? 'selected' : ''; ?>>Benutzerdefiniert (<?php echo esc_html($srv['name_size'] ?? '1.8em'); ?>)</option>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
<tr><th>Server Host</th><td><input name="mcss_servers[<?php echo $i; ?>][host]" value="<?php echo esc_attr($srv['host'] ?? ''); ?>" class="regular-text" required></td></tr>
|
||
<tr><th>RCON Port</th><td><input name="mcss_servers[<?php echo $i; ?>][rcon_port]" type="number" value="<?php echo esc_attr($srv['rcon_port'] ?? 25575); ?>" class="small-text"></td></tr>
|
||
<tr><th>RCON Passwort</th><td><input name="mcss_servers[<?php echo $i; ?>][rcon_pass]" type="password" value="<?php echo esc_attr($srv['rcon_pass'] ?? ''); ?>" class="regular-text"></td></tr>
|
||
<tr><th>Cache TTL (Sekunden)</th><td><input name="mcss_servers[<?php echo $i; ?>][cache_ttl]" type="number" value="<?php echo esc_attr($srv['cache_ttl'] ?? 15); ?>" class="small-text"></td></tr>
|
||
<tr><th>MOTD anzeigen?</th><td><input name="mcss_servers[<?php echo $i; ?>][show_motd]" type="checkbox" value="1" <?php checked(!empty($srv['show_motd'])); ?> /></td></tr>
|
||
|
||
<tr>
|
||
<th><label for="mcss_player_port"><strong>Spieler-Port (für Kopieren)</strong></label></th>
|
||
<td>
|
||
<input name="mcss_servers[<?php echo $i; ?>][player_port]" id="mcss_player_port" type="text" value="<?php echo esc_attr($srv['player_port'] ?? ''); ?>" class="regular-text" style="width:180px;" placeholder="z. B. 25565" />
|
||
<p class="description" style="margin-top:8px;">Wird <strong>nicht angezeigt</strong>, aber beim Klick mitkopiert.<br>Leer = kein Port wird kopiert.</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th>Port in Adresse ausblenden?</th>
|
||
<td><input name="mcss_servers[<?php echo $i; ?>][hide_port]" type="checkbox" value="1" <?php checked(!empty($srv['hide_port'])); ?> /> <label>Ja – zeige nur den Host an</label></td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th><label>Server Logo</label></th>
|
||
<td>
|
||
<div id="mcss-logo-preview" style="margin-bottom:12px;">
|
||
<img src="<?php echo esc_url($current_logo); ?>" style="max-width:150px;max-height:150px;border-radius:8px;" />
|
||
</div>
|
||
<input type="hidden" name="mcss_servers[<?php echo $i; ?>][logo_id]" value="<?php echo esc_attr($logo_id); ?>">
|
||
<input type="text" name="mcss_servers[<?php echo $i; ?>][logo_url]" value="<?php echo esc_attr($logo_url); ?>" class="regular-text" placeholder="Oder direkte URL...">
|
||
<button type="button" class="button mcss-upload-logo-button" id="mcss_upload_logo_button">Hochladen / auswählen</button>
|
||
<button type="button" class="button mcss-remove-logo-button" id="mcss_remove_logo_button" <?php echo empty($logo_id) && empty($logo_url) ? 'style="display:none;"' : ''; ?>>Entfernen</button>
|
||
</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th><label>Zusatztext unter der Adresse</label></th>
|
||
<td>
|
||
<input name="mcss_servers[<?php echo $i; ?>][custom_text]" value="<?php echo esc_attr($srv['custom_text'] ?? ''); ?>" class="large-text" placeholder="1.21 Survival • Whitelist aktiv • Discord" />
|
||
<p class="description">HTML + Emojis erlaubt.</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th>IP-Adresse – Schrift</th>
|
||
<td>
|
||
<label>Farbe:</label>
|
||
<input name="mcss_servers[<?php echo $i; ?>][ip_color]" value="<?php echo esc_attr($srv['ip_color'] ?? '#1f2937'); ?>" class="mcss-color-picker" data-default-color="#1f2937" style="width:100px;margin-left:8px;" />
|
||
<label style="margin-left:20px;">Größe:</label>
|
||
<select name="mcss_servers[<?php echo $i; ?>][ip_size]" style="width:200px;margin-left:8px;">
|
||
<?php foreach ($font_sizes as $value => $label): ?>
|
||
<option value="<?php echo esc_attr($value); ?>" <?php selected($srv['ip_size'] ?? '1.5em', $value); ?>><?php echo esc_html($label); ?></option>
|
||
<?php endforeach; ?>
|
||
<option value="<?php echo esc_attr($srv['ip_size'] ?? '1.5em'); ?>" <?php echo !isset($font_sizes[$srv['ip_size'] ?? '']) ? 'selected' : ''; ?>>Benutzerdefiniert (<?php echo esc_html($srv['ip_size'] ?? '1.5em'); ?>)</option>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th>Zusatztext – Schrift</th>
|
||
<td>
|
||
<label>Farbe:</label>
|
||
<input name="mcss_servers[<?php echo $i; ?>][ct_color]" value="<?php echo esc_attr($srv['ct_color'] ?? '#1e293b'); ?>" class="mcss-color-picker" data-default-color="#1e293b" style="width:100px;margin-left:8px;" />
|
||
<label style="margin-left:20px;">Größe:</label>
|
||
<select name="mcss_servers[<?php echo $i; ?>][ct_size]" style="width:200px;margin-left:8px;">
|
||
<?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'] ?? '1.05em'); ?>" <?php echo !isset($font_sizes[$srv['ct_size'] ?? '']) ? 'selected' : ''; ?>>Benutzerdefiniert (<?php echo esc_html($srv['ct_size'] ?? '1.05em'); ?>)</option>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label>Wartungsmodus</label></th>
|
||
<td>
|
||
<input name="mcss_servers[<?php echo $i; ?>][maintenance_mode]" type="checkbox" value="1" <?php checked(!empty($srv['maintenance_mode'])); ?> />
|
||
<label>Wartungsmodus aktivieren</label>
|
||
<p class="description" style="margin-top:8px;">Wenn aktiviert, wird eine Wartungsnachricht anstelle der Server-Informationen angezeigt.</p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label>Wartungsnachricht</label></th>
|
||
<td>
|
||
<textarea name="mcss_servers[<?php echo $i; ?>][maintenance_message]" class="large-text" rows="4" placeholder="Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!"><?php echo esc_textarea($srv['maintenance_message'] ?? 'Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!'); ?></textarea>
|
||
<p class="description">HTML + Emojis erlaubt. Diese Nachricht wird angezeigt, wenn der Wartungsmodus aktiv ist.</p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label>Ankündigungen</label></th>
|
||
<td>
|
||
<input name="mcss_servers[<?php echo $i; ?>][announcement_enabled]" type="checkbox" value="1" <?php checked(!empty($srv['announcement_enabled'])); ?> />
|
||
<label>Ankündigungen aktivieren</label>
|
||
<p class="description" style="margin-top:8px;">Wenn aktiviert, werden zeitlich begrenzte Ankündigungen über dem Widget angezeigt.</p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label>Ankündigungstext</label></th>
|
||
<td>
|
||
<textarea name="mcss_servers[<?php echo $i; ?>][announcement_text]" class="large-text" rows="3" placeholder="🎉 Server-Event am Wochenende! Kommt vorbei und gewinnt tolle Preise!"><?php echo esc_textarea($srv['announcement_text'] ?? '🎉 Server-Event am Wochenende! Kommt vorbei und gewinnt tolle Preise!'); ?></textarea>
|
||
<p class="description">HTML + Emojis erlaubt. Diese Nachricht wird als Ankündigung angezeigt.</p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label>Ankündigungszeitraum</label></th>
|
||
<td>
|
||
<div style="display:flex;gap:10px;align-items:center;">
|
||
<div style="flex:1;">
|
||
<label for="mcss_announcement_start_<?php echo $i; ?>">Startdatum:</label>
|
||
<input name="mcss_servers[<?php echo $i; ?>][announcement_start]" id="mcss_announcement_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_announcement_end_<?php echo $i; ?>">Enddatum:</label>
|
||
<input name="mcss_servers[<?php echo $i; ?>][announcement_end]" id="mcss_announcement_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>
|
||
<p class="description" style="margin-top:8px;">Die Ankündigung wird nur im angegebenen Zeitraum angezeigt. Leerlassen für dauerhafte Anzeige.</p>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label>Ankündigungstyp</label></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>
|
||
<p class="description" style="margin-top:8px;">Bestimmt das Aussehen der Ankündigung.</p>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<h2>Ränge (LuckPerms Zuordnung)</h2>
|
||
<p>Die Ränge werden automatisch über LuckPerms per RCON abgerufen.</p>
|
||
<div id="mcss-ranks-editor" class="mcss-ranks-editor"></div>
|
||
<textarea id="mcss_ranks_json" name="mcss_servers[<?php echo $i; ?>][ranks_json]" class="mcss-ranks-json" style="display:none;"><?php echo esc_textarea($srv['ranks_json'] ?? '[]'); ?></textarea>
|
||
|
||
<input type="hidden" name="mcss_servers[<?php echo $i; ?>][id]" value="<?php echo esc_attr($srv['id'] ?? ''); ?>">
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/* ---------------- Alle Original-Funktionen 1:1 ---------------- */
|
||
function mcss_test_rcon_now($srv) {
|
||
if (empty($srv['host']) || empty($srv['rcon_pass'])) return ['error' => 'Host oder Passwort nicht gesetzt'];
|
||
$rcon = new Rcon($srv['host'], $srv['rcon_port'], $srv['rcon_pass'], 3);
|
||
$out = ['connected' => false];
|
||
if ($rcon->connect()) {
|
||
$out['connected'] = true;
|
||
$out['list'] = $rcon->sendCommand('list');
|
||
$out['version_raw'] = $rcon->sendCommand('version');
|
||
$out['lp_listgroups'] = $rcon->sendCommand('lp listgroups');
|
||
$rcon->disconnect();
|
||
} else $out['error'] = 'RCON Verbindung fehlgeschlagen';
|
||
return $out;
|
||
}
|
||
|
||
function mcss_normalize_version($raw) {
|
||
$raw = trim((string)$raw);
|
||
if ($raw === '') return 'Unbekannt';
|
||
$raw = preg_replace('/^\s*v\s*/i', '', $raw);
|
||
$softwares = ['spigot','paper','bukkit'];
|
||
foreach ($softwares as $soft) {
|
||
if (stripos($raw, $soft) !== false) {
|
||
if (preg_match('/git-' . preg_quote($soft, '/') . '-(\d+\.\d+(?:\.\d+)?)/i', $raw, $m)) return ucfirst($soft) . ' ' . $m[1];
|
||
if (preg_match('/\b' . preg_quote($soft, '/') . '\b[^\d]{0,10}?(\d+\.\d+(?:\.\d+)?)/i', $raw, $m)) return ucfirst($soft) . ' ' . $m[1];
|
||
if (preg_match('/(\d+\.\d+(?:\.\d+)?)/', $raw, $m)) return ucfirst($soft) . ' ' . $m[1];
|
||
return ucfirst($soft) . ' Unbekannt';
|
||
}
|
||
}
|
||
if (preg_match('/\(mc:\s*(\d+\.\d+(?:\.\d+)?)\)/i', $raw, $m)) return 'Vanilla ' . $m[1];
|
||
if (preg_match('/minecraft\s+(\d+\.\d+(?:\.\d+)?)/i', $raw, $m)) return 'Vanilla ' . $m[1];
|
||
if (preg_match('/git-(spigot|paper|bukkit)-(\d+\.\d+(?:\.\d+)?)/i', $raw, $m)) return ucfirst($m[1]) . ' ' . $m[2];
|
||
if (preg_match('/(\d+\.\d+(?:\.\d+)?)/', $raw, $m)) return $m[1];
|
||
return trim($raw);
|
||
}
|
||
|
||
function mcss_fetch_luckperms_groups($srv) {
|
||
if (empty($srv['host']) || empty($srv['rcon_pass'])) return [];
|
||
$cache_key = 'mcss_lp_groups_' . md5($srv['host'].':'.$srv['rcon_port']);
|
||
$cached = get_transient($cache_key);
|
||
if ($cached !== false) return $cached;
|
||
|
||
$rcon = new Rcon($srv['host'], $srv['rcon_port'], $srv['rcon_pass'], 3);
|
||
if (!$rcon->connect()) return [];
|
||
|
||
$groups = [];
|
||
$out = $rcon->sendCommand('lp listgroups');
|
||
if ($out) {
|
||
if (preg_match('/(?:Groups?:\s*)(.+)/i', $out, $m)) {
|
||
$names = array_map('trim', explode(',', $m[1]));
|
||
foreach ($names as $n) if ($n !== '') $groups[] = $n;
|
||
} else {
|
||
$lines = preg_split("/\r?\n/", $out);
|
||
foreach ($lines as $ln) {
|
||
$ln = trim(preg_replace('/^[\-\d\.\s]+/', '', $ln));
|
||
if ($ln !== '') $groups[] = $ln;
|
||
}
|
||
}
|
||
}
|
||
if (empty($groups)) {
|
||
$out2 = $rcon->sendCommand('lp info');
|
||
if ($out2 && preg_match_all('/Groups:\s*(.+)/i', $out2, $mm)) {
|
||
$names = array_map('trim', explode(',', implode(',', $mm[1])));
|
||
foreach ($names as $n) if ($n !== '') $groups[] = $n;
|
||
}
|
||
}
|
||
$groups = array_values(array_unique(array_filter($groups)));
|
||
|
||
$group_infos = [];
|
||
foreach ($groups as $g) {
|
||
$info_raw = $rcon->sendCommand('lp group ' . $g . ' info');
|
||
$weight = 0; $prefix = null;
|
||
if ($info_raw) {
|
||
if (preg_match('/weight[:\s]*([\-]?\d+)/i', $info_raw, $wm)) $weight = intval($wm[1]);
|
||
elseif (preg_match('/priority[:\s]*([\-]?\d+)/i', $info_raw, $wm2)) $weight = intval($wm2[1]);
|
||
if (preg_match('/prefix[:\s]*([^\r\n]+)/i', $info_raw, $pm)) $prefix = trim($pm[1]);
|
||
elseif (preg_match('/display name[:\s]*([^\r\n]+)/i', $info_raw, $pm2)) $prefix = trim($pm2[1]);
|
||
}
|
||
$color = '#6c5ce7';
|
||
if ($prefix) { $c = mcss_color_from_prefix($prefix); if ($c) $color = $c; }
|
||
$group_infos[] = ['group'=>$g,'weight'=>$weight,'prefix'=>$prefix,'color'=>$color];
|
||
}
|
||
$rcon->disconnect();
|
||
usort($group_infos, fn($a,$b) => $b['weight'] <=> $a['weight']);
|
||
|
||
$ranks = [];
|
||
foreach ($group_infos as $gi) {
|
||
$ranks[] = ['name'=>$gi['group'],'groups'=>$gi['group'],'color'=>$gi['color'],'prefix'=>$gi['prefix']];
|
||
}
|
||
set_transient($cache_key, $ranks, 12 * HOUR_IN_SECONDS);
|
||
return $ranks;
|
||
}
|
||
|
||
function mcss_color_from_prefix($prefix) {
|
||
if (!$prefix) return null;
|
||
if (preg_match('/[§&]([0-9a-fk-or])/i', $prefix, $m)) {
|
||
$code = strtolower($m[1]);
|
||
$map = ['0'=>'#000000','1'=>'#0000AA','2'=>'#00AA00','3'=>'#00AAAA','4'=>'#AA0000','5'=>'#AA00AA','6'=>'#FFAA00',
|
||
'7'=>'#AAAAAA','8'=>'#555555','9'=>'#5555FF','a'=>'#55FF55','b'=>'#55FFFF','c'=>'#FF5555','d'=>'#FF55FF',
|
||
'e'=>'#FFFF55','f'=>'#FFFFFF'];
|
||
if (isset($map[$code])) return $map[$code];
|
||
}
|
||
if (preg_match('/#([0-9a-f]{6})/i', $prefix, $m2)) return '#' . $m2[1];
|
||
return null;
|
||
}
|
||
|
||
function mcss_get_uuid_from_name($name) {
|
||
if (empty($name) || !preg_match('/^[a-zA-Z0-9_]{3,16}$/', $name)) return false;
|
||
$key = 'mcss_uuid_' . strtolower($name);
|
||
$cached = get_transient($key);
|
||
if ($cached !== false && $cached !== 'invalid') return $cached;
|
||
|
||
$response = wp_remote_get("https://api.mojang.com/users/profiles/minecraft/" . rawurlencode($name), ['timeout'=>6]);
|
||
if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
|
||
set_transient($key, 'invalid', 60);
|
||
return false;
|
||
}
|
||
$body = json_decode(wp_remote_retrieve_body($response), true);
|
||
if (empty($body['id'])) {
|
||
set_transient($key, 'invalid', 86400);
|
||
return false;
|
||
}
|
||
$uuid = $body['id'];
|
||
set_transient($key, $uuid, 30*DAY_IN_SECONDS);
|
||
return $uuid;
|
||
}
|
||
|
||
function mcss_avatar($input, $size = 64) {
|
||
$size = intval($size);
|
||
if (preg_match('/^[0-9a-f]{32}$/i', $input) || preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $input)) {
|
||
$uuid = preg_replace('/-/', '', $input);
|
||
} else {
|
||
$uuid = mcss_get_uuid_from_name($input);
|
||
if (!$uuid) return "https://mc-heads.net/avatar/" . rawurlencode($input) . "/{$size}";
|
||
}
|
||
return "https://mc-heads.net/avatar/{$uuid}/{$size}";
|
||
}
|
||
|
||
/* ---------------- NEUE FUNKTION FÜR PING-MESSUNG ---------------- */
|
||
function mcss_ping_server($host, $port = 25565, $timeout = 2) {
|
||
$startTime = microtime(true);
|
||
|
||
// Versuche, eine TCP-Verbindung zum Server herzustellen
|
||
$socket = @fsockopen($host, $port, $errno, $errstr, $timeout);
|
||
|
||
if (!$socket) {
|
||
return false; // Server ist nicht erreichbar
|
||
}
|
||
|
||
// Schließe die Verbindung
|
||
fclose($socket);
|
||
|
||
// Berechne die Ping-Zeit in Millisekunden
|
||
$endTime = microtime(true);
|
||
$ping = round(($endTime - $startTime) * 1000);
|
||
|
||
return $ping;
|
||
}
|
||
|
||
/* ---------------- NEUE FUNKTIONEN FÜR ANKÜNDIGUNGEN ---------------- */
|
||
function mcss_should_show_announcement($srv) {
|
||
// Prüfen, ob Ankündigungen aktiviert sind
|
||
if (empty($srv['announcement_enabled'])) {
|
||
return false;
|
||
}
|
||
|
||
// Prüfen, ob ein Text vorhanden ist
|
||
if (empty($srv['announcement_text'])) {
|
||
return false;
|
||
}
|
||
|
||
// Wenn kein Startdatum angegeben ist, immer anzeigen
|
||
if (empty($srv['announcement_start'])) {
|
||
return true;
|
||
}
|
||
|
||
$current_time = current_time('timestamp');
|
||
$start_time = strtotime($srv['announcement_start']);
|
||
|
||
// Wenn das Startdatum in der Zukunft liegt, nicht anzeigen
|
||
if ($start_time > $current_time) {
|
||
return false;
|
||
}
|
||
|
||
// Wenn kein Enddatum angegeben ist, nach Startdatum immer anzeigen
|
||
if (empty($srv['announcement_end'])) {
|
||
return true;
|
||
}
|
||
|
||
$end_time = strtotime($srv['announcement_end']);
|
||
|
||
// Prüfen, ob das Enddatum noch nicht erreicht ist
|
||
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' => 'ℹ️'
|
||
];
|
||
}
|
||
}
|
||
|
||
/* ---------------- Fetch & AJAX – 100% wie dein Original ---------------- */
|
||
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));
|
||
}
|
||
}
|
||
wp_send_json(['online'=>false,'players'=>[],'address'=>'','version'=>'Unbekannt','ping'=>0,'motd'=>'']);
|
||
}
|
||
|
||
function mcss_fetch_server_with_ranks($srv) {
|
||
$cache_key = 'mcss_data_' . md5($srv['host'].':'.$srv['rcon_port']);
|
||
$cached = get_transient($cache_key);
|
||
if ($cached !== false) {
|
||
$cached['last_update'] = get_option($cache_key.'_time', time());
|
||
return $cached;
|
||
}
|
||
|
||
$result = ['online'=>false,'players'=>[],'address'=>$srv['host'].':'.$srv['rcon_port'],'version'=>'Unbekannt','ping'=>0,'motd'=>'','last_update'=>time()];
|
||
$raw_players = [];
|
||
|
||
if (!empty($srv['rcon_pass'])) {
|
||
$rcon = new Rcon($srv['host'], $srv['rcon_port'], $srv['rcon_pass'], 3);
|
||
if ($rcon->connect()) {
|
||
$list_raw = $rcon->sendCommand('list');
|
||
if (preg_match('/: (.*)$/', $list_raw, $m)) {
|
||
$entries = array_filter(array_map('trim', explode(',', $m[1])));
|
||
foreach ($entries as $entry) {
|
||
if (preg_match('/\s+([a-zA-Z0-9_]{3,16})$/', $entry, $n)) {
|
||
$clean_name = $n[1];
|
||
} else {
|
||
$clean_name = trim(preg_replace('/^[^a-zA-Z0-9_]*/', '', $entry));
|
||
$clean_name = preg_replace('/[^a-zA-Z0-9_].*$/', '', $clean_name);
|
||
}
|
||
if ($clean_name && preg_match('/^[a-zA-Z0-9_]{3,16}$/', $clean_name)) {
|
||
$raw_players[] = ['name' => $clean_name, 'raw_entry' => $entry];
|
||
}
|
||
}
|
||
}
|
||
$result['online'] = true;
|
||
$result['version'] = mcss_normalize_version($rcon->sendCommand('version') ?? '');
|
||
$rcon->disconnect();
|
||
}
|
||
}
|
||
$api_host = $srv['query_port'] != 25565 ? $srv['host'].':'.$srv['query_port'] : $srv['host'];
|
||
$resp = wp_remote_get('https://api.mcsrvstat.us/2/'.rawurlencode($api_host), ['timeout'=>7]);
|
||
if (!is_wp_error($resp) && wp_remote_retrieve_response_code($resp)==200) {
|
||
$body = json_decode(wp_remote_retrieve_body($resp), true);
|
||
if (!empty($body['online'])) {
|
||
$result['online'] = true;
|
||
if (empty($raw_players) && !empty($body['players']['list']) && is_array($body['players']['list'])) {
|
||
foreach ($body['players']['list'] as $pl) {
|
||
if (!is_array($pl)) continue;
|
||
$name = $pl['name'] ?? '';
|
||
$uuid = $pl['uuid'] ?? '';
|
||
if ($name === '') continue;
|
||
$raw_players[] = ['name' => $name, 'uuid' => $uuid, 'raw_entry' => $name];
|
||
}
|
||
}
|
||
if ($result['version'] === 'Unbekannt') {
|
||
$candidate = $body['version'] ?? $body['software'] ?? '';
|
||
if ($candidate) $result['version'] = mcss_normalize_version($candidate);
|
||
}
|
||
$result['ping'] = intval($body['debug']['ping'] ?? $body['ping'] ?? 0);
|
||
if (!empty($srv['show_motd']) && !empty($body['motd']['clean'])) {
|
||
$motd = is_array($body['motd']['clean']) ? implode(' ', $body['motd']['clean']) : $body['motd']['clean'];
|
||
$result['motd'] = $motd;
|
||
}
|
||
}
|
||
}
|
||
|
||
// PING-MESSUNG - VERWENDE UNSERE EIGENE FUNKTION
|
||
$ping_port = !empty($srv['player_port']) ? $srv['player_port'] : $srv['query_port'];
|
||
$ping_result = mcss_ping_server($srv['host'], $ping_port, 2);
|
||
if ($ping_result !== false) {
|
||
$result['ping'] = $ping_result;
|
||
} else {
|
||
// Wenn unsere Ping-Messung fehlschlägt, versuche den Ping aus der API zu verwenden
|
||
if (!empty($body['debug']['ping'])) {
|
||
$result['ping'] = intval($body['debug']['ping']);
|
||
} elseif (!empty($body['ping'])) {
|
||
$result['ping'] = intval($body['ping']);
|
||
}
|
||
}
|
||
|
||
$lp_groups = mcss_fetch_luckperms_groups($srv);
|
||
$players_info = [];
|
||
foreach ($raw_players as $p) {
|
||
$name = $p['name'];
|
||
$uuid = $p['uuid'] ?? mcss_get_uuid_from_name($name);
|
||
$raw_entry = $p['raw_entry'] ?? $name;
|
||
|
||
$prefix = '';
|
||
if (preg_match('/^(\[[^\]]+\])/', $raw_entry, $m)) {
|
||
$prefix = $m[1];
|
||
}
|
||
|
||
$rank = $prefix ?: 'Spieler';
|
||
$color = '#94a3b8';
|
||
foreach ($lp_groups as $g) {
|
||
if ($g['prefix'] && stripos($prefix, $g['prefix']) !== false) {
|
||
$color = $g['color'];
|
||
break;
|
||
}
|
||
}
|
||
|
||
$players_info[] = [
|
||
'name' => $name,
|
||
'avatar' => mcss_avatar($uuid ?: $name, 64),
|
||
'rank' => $rank,
|
||
'color' => $color,
|
||
];
|
||
}
|
||
|
||
$result['players'] = $players_info;
|
||
$result['version'] = preg_replace('/^\s*v/i', '', trim($result['version']));
|
||
set_transient($cache_key, $result, $srv['cache_ttl']);
|
||
update_option($cache_key.'_time', time());
|
||
return $result;
|
||
}
|
||
|
||
/* ---------------- Shortcode – 100% DEIN ORIGINAL-DESIGN ---------------- */
|
||
add_shortcode('minecraft_status', 'mcss_server_card_shortcode');
|
||
add_shortcode('minecraft_server_detail', 'mcss_server_card_shortcode');
|
||
|
||
function mcss_server_card_shortcode($atts = []) {
|
||
$atts = shortcode_atts(['id' => ''], $atts);
|
||
if (empty($atts['id'])) return 'Fehler: id fehlt';
|
||
|
||
$servers = get_option('mcss_servers', []);
|
||
$srv = null;
|
||
foreach ($servers as $s) {
|
||
if (($s['id'] ?? '') === $atts['id'] || ($s['name'] ?? '') === $atts['id']) {
|
||
$srv = $s; break;
|
||
}
|
||
}
|
||
if (!$srv) return '<p style="color:red;">Server nicht gefunden</p>';
|
||
|
||
// Prüfen, ob der Wartungsmodus aktiviert ist
|
||
$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!';
|
||
|
||
// Prüfen, ob eine Ankündigung angezeigt werden soll
|
||
$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);
|
||
|
||
// Wenn Wartungsmodus aktiv ist, nur die Wartungsansicht anzeigen
|
||
if ($maintenance_mode) {
|
||
$uid = md5($srv['host'] . '|' . ($srv['player_port'] ?? ''));
|
||
$logo = $srv['logo_id'] ? wp_get_attachment_image_url($srv['logo_id'], 'full') : ($srv['logo_url'] ?: MCSS_URL.'img/default-server-logo.png');
|
||
|
||
ob_start(); ?>
|
||
<style>
|
||
/* Pulsierende Animation für den Wartungs-Statuspunkt */
|
||
@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-server-widget-<?php echo esc_attr($uid); ?>" class="mcss-server-widget" style="max-width:920px;margin:30px auto;background:#fff;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;border:1px solid #e2e8f0;position:relative;">
|
||
<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:80px;height:80px;border-radius:16px;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 esc_attr($srv['name_size'] ?? '1.8em'); ?>;color:<?php echo esc_attr($srv['name_color'] ?? '#1e293b'); ?>;font-weight:800;"><?php echo esc_html($srv['name'] ?? 'Minecraft Server'); ?></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();
|
||
}
|
||
|
||
// Normaler Modus (keine Wartung)
|
||
$data = mcss_fetch_server_with_ranks($srv);
|
||
$uid = md5($srv['host'] . '|' . ($srv['player_port'] ?? ''));
|
||
$display_address = $srv['host'];
|
||
if (!$srv['hide_port'] && !empty($srv['player_port'])) $display_address .= ':'.$srv['player_port'];
|
||
$copy_address = $srv['host'] . (!empty($srv['player_port']) ? ':'.$srv['player_port'] : '');
|
||
$logo = $srv['logo_id'] ? wp_get_attachment_image_url($srv['logo_id'], 'full') : ($srv['logo_url'] ?: MCSS_URL.'img/default-server-logo.png');
|
||
|
||
$user_defined_ranks = json_decode($srv['ranks_json'] ?? '', true);
|
||
if (!is_array($user_defined_ranks)) {
|
||
$user_defined_ranks = [];
|
||
}
|
||
$lp_present = !empty($user_defined_ranks);
|
||
$show_player_count = empty($lp_present);
|
||
|
||
ob_start(); ?>
|
||
<style>
|
||
/* Pulsierende Animation für den Online-Statuspunkt */
|
||
@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;
|
||
}
|
||
|
||
/* Animation für die Ankündigung */
|
||
@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-server-widget-<?php echo esc_attr($uid); ?>" class="mcss-server-widget" style="max-width:920px;margin:30px auto;background:#fff;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;border:1px solid #e2e8f0;position:relative;">
|
||
<?php if ($show_announcement): ?>
|
||
<div class="mcss-announcement" style="padding:16px 24px;background:<?php echo esc_attr($announcement_style['bg']); ?>;border-bottom:1px solid <?php echo esc_attr($announcement_style['border']); ?>;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.05em;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; ?>
|
||
|
||
<div id="mcss-copy-toast-<?php echo esc_attr($uid); ?>" style="position:absolute;top:20px;right:20px;background:#10b981;color:#fff;padding:12px 24px;border-radius:12px;font-weight:600;font-size:0.95em;opacity:0;transform:translateY(-20px);transition:all .4s ease;z-index:100;box-shadow:0 8px 20px rgba(16,185,129,0.4);">Adresse kopiert!</div>
|
||
|
||
<div style="padding:28px 32px;background:#f8fafc;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;gap:20px;">
|
||
<img src="<?php echo esc_url($logo); ?>" alt="Logo" loading="lazy" style="width:80px;height:80px;border-radius:16px;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;">
|
||
<!-- HIER WIRD DER SERVERNAME JETZT MIT ANGEPASSTER FARBE UND GRÖßE ANGEZEIGT -->
|
||
<h2 style="margin:0 0 8px 0;font-size:<?php echo esc_attr($srv['name_size'] ?? '1.8em'); ?>;color:<?php echo esc_attr($srv['name_color'] ?? '#1e293b'); ?>;font-weight:800;"><?php echo esc_html($srv['name'] ?? 'Minecraft Server'); ?></h2>
|
||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px;">
|
||
<span id="mcss-address-<?php echo esc_attr($uid); ?>" class="mcss-address" style="font-weight:800;font-size:<?php echo esc_attr($srv['ip_size'] ?? '1.5em'); ?>;color:<?php echo esc_attr($srv['ip_color'] ?? '#1f2937'); ?>;cursor:pointer;user-select:none;" onclick="mcss_copy_address_<?php echo esc_attr($uid); ?>('<?php echo esc_js($copy_address); ?>')">
|
||
<?php echo esc_html($display_address); ?>
|
||
</span>
|
||
<span id="mcss-status-dot-<?php echo esc_attr($uid); ?>" class="<?php echo $data['online'] ? 'mcss-status-online' : ''; ?>" style="width:14px;height:14px;border-radius:50%;background:<?php echo $data['online']?'#10b981':'#ef4444'; ?>;box-shadow:0 0 16px <?php echo $data['online']?'rgba(16,185,129,0.5)':'rgba(239,68,68,0.5)'; ?>;"></span>
|
||
<span id="mcss-online-text-<?php echo esc_attr($uid); ?>" style="font-weight:600;color:#475569;font-size:1em;"><?php echo $data['online']?'Online':'Offline'; ?></span>
|
||
</div>
|
||
<?php if (!empty($srv['custom_text'])): ?>
|
||
<div id="mcss-custom-text-<?php echo esc_attr($uid); ?>" style="font-size:<?php echo esc_attr($srv['ct_size'] ?? '1.05em'); ?>;color:<?php echo esc_attr($srv['ct_color'] ?? '#1e293b'); ?>;font-weight:600;margin-top:6px;"><?php echo wp_kses_post($srv['custom_text']); ?></div>
|
||
<?php endif; ?>
|
||
<?php if (!empty($data['motd']) && !empty($srv['show_motd'])): ?>
|
||
<div id="mcss-motd-<?php echo esc_attr($uid); ?>" style="margin-top:10px;font-size:0.95em;color:#64748b;"><?php echo esc_html($data['motd']); ?></div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
<div style="padding:18px 32px;background:#f1f5f9;display:flex;justify-content:space-around;flex-wrap:wrap;gap:20px;font-size:0.98em;color:#374151;border-bottom:1px solid #e2e8f0;">
|
||
<div><strong>Version:</strong> <span id="mcss-version-<?php echo esc_attr($uid); ?>"><?php echo esc_html($data['version']); ?></span></div>
|
||
<?php if ($show_player_count): ?>
|
||
<div><strong>Spieler:</strong> <span id="mcss-players-count-<?php echo esc_attr($uid); ?>"><?php echo count($data['players']); ?></span></div>
|
||
<?php else: ?>
|
||
<div><strong>Rang:</strong> <span id="mcss-players-count-<?php echo esc_attr($uid); ?>"><?php echo esc_html($user_defined_ranks[0]['name'] ?? 'Spieler'); ?></span></div>
|
||
<?php endif; ?>
|
||
<div><strong>Ping:</strong> <span id="mcss-ping-<?php echo esc_attr($uid); ?>"><?php echo intval($data['ping']); ?></span> ms</div>
|
||
</div>
|
||
<div style="padding:24px 32px;">
|
||
<div style="margin-bottom:16px;font-weight:700;color:#1f2937;font-size:1.05em;">Spieler online:</div>
|
||
<div id="mcss-player-grid-<?php echo esc_attr($uid); ?>" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(80px,1fr));gap:16px;">
|
||
<?php if (!empty($data['players'])): foreach ($data['players'] as $p): ?>
|
||
<div style="text-align:center;">
|
||
<img src="<?php echo esc_url($p['avatar']); ?>" alt="<?php echo esc_attr($p['name']); ?>" loading="lazy" style="width:56px;height:56px;border-radius:12px;margin-bottom:8px;" onerror="this.src='<?php echo MCSS_URL.'img/default-server-logo.png'; ?>'" />
|
||
<div style="font-weight:600;font-size:0.88em;color:#1f2937;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;"><?php echo esc_html($p['name']); ?></div>
|
||
<div style="font-size:0.8em;color:<?php echo esc_attr($p['color']); ?>;margin-top:4px;"><?php echo esc_html($p['rank']); ?></div>
|
||
</div>
|
||
<?php endforeach; else: ?>
|
||
<div style="grid-column:1/-1;text-align:center;color:#94a3b8;padding:30px 0;font-size:1.1em;">Keine Spieler online</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
function mcss_copy_address_<?php echo esc_attr($uid); ?>(text) {
|
||
navigator.clipboard.writeText(text).then(function(){
|
||
var toast = document.getElementById('mcss-copy-toast-<?php echo esc_attr($uid); ?>');
|
||
if(!toast) return;
|
||
toast.style.opacity = '1';
|
||
toast.style.transform = 'translateY(0)';
|
||
setTimeout(function(){
|
||
toast.style.opacity = '0';
|
||
toast.style.transform = 'translateY(-20px)';
|
||
}, 2000);
|
||
}).catch(function(){});
|
||
}
|
||
|
||
(function(){
|
||
var uid = '<?php echo esc_js($uid); ?>';
|
||
var ajaxUrl = (typeof mcss_ajax_object !== 'undefined' && mcss_ajax_object.ajax_url) ? mcss_ajax_object.ajax_url : '<?php echo admin_url('admin-ajax.php'); ?>';
|
||
var interval = (typeof mcss_ajax_object !== 'undefined' && mcss_ajax_object.refresh_interval) ? parseInt(mcss_ajax_object.refresh_interval,10) : 15000;
|
||
interval = Math.max(10000, interval);
|
||
|
||
function updateWidgetData() {
|
||
var body = new URLSearchParams();
|
||
body.append('action', 'mcss_fetch');
|
||
body.append('server_id', '<?php echo esc_js($srv['id']); ?>');
|
||
|
||
fetch(ajaxUrl, {
|
||
method: 'POST',
|
||
headers: {'Content-Type':'application/x-www-form-urlencoded'},
|
||
body: body.toString()
|
||
})
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if(!data) return;
|
||
|
||
// Status-Dot + Online-Text
|
||
var dot = document.getElementById('mcss-status-dot-' + uid);
|
||
var onlineText = document.getElementById('mcss-online-text-' + uid);
|
||
if(dot) {
|
||
dot.style.background = data.online ? '#10b981' : '#ef4444';
|
||
dot.style.boxShadow = data.online ? '0 0 16px rgba(16,185,129,0.5)' : '0 0 16px rgba(239,68,68,0.5)';
|
||
|
||
// Animation basierend auf dem Online-Status hinzufügen/entfernen
|
||
if(data.online) {
|
||
dot.classList.add('mcss-status-online');
|
||
} else {
|
||
dot.classList.remove('mcss-status-online');
|
||
}
|
||
}
|
||
if(onlineText) onlineText.textContent = data.online ? 'Online' : 'Offline';
|
||
|
||
// Version
|
||
var verEl = document.getElementById('mcss-version-' + uid);
|
||
if(verEl) verEl.textContent = (data.version || '');
|
||
|
||
// Spieleranzahl / Rang
|
||
var playersCnt = document.getElementById('mcss-players-count-' + uid);
|
||
if(playersCnt) {
|
||
<?php if ($show_player_count): ?>
|
||
playersCnt.textContent = (data.players ? data.players.length : 0);
|
||
<?php else: ?>
|
||
playersCnt.textContent = data.players?.[0]?.rank ?? 'Spieler';
|
||
<?php endif; ?>
|
||
}
|
||
|
||
// Ping
|
||
var pingEl = document.getElementById('mcss-ping-' + uid);
|
||
if(pingEl) pingEl.textContent = (data.ping || 0);
|
||
|
||
// MOTD
|
||
var motdEl = document.getElementById('mcss-motd-' + uid);
|
||
if(motdEl) {
|
||
if(data.motd && data.motd.length) {
|
||
motdEl.style.display = '';
|
||
motdEl.textContent = data.motd;
|
||
} else {
|
||
motdEl.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// Spieler-Grid neu bauen
|
||
var grid = document.getElementById('mcss-player-grid-' + uid);
|
||
if(grid) {
|
||
if(data.players && data.players.length) {
|
||
var html = '';
|
||
data.players.forEach(function(p){
|
||
var safeAvatar = p.avatar || '';
|
||
var safeName = p.name || '';
|
||
var safeRank = p.rank || '';
|
||
var safeColor = p.color || '#6c5ce7';
|
||
html += '<div style="text-align:center;">' +
|
||
'<img src="'+ safeAvatar +'" alt="'+ safeName +'" loading="lazy" style="width:56px;height:56px;border-radius:12px;margin-bottom:8px;" onerror="this.src=\'<?php echo esc_js(MCSS_URL . 'img/default-server-logo.png'); ?>\'" />' +
|
||
'<div style="font-weight:600;font-size:0.88em;color:#1f2937;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">'+ escapeHtml(safeName) +'</div>' +
|
||
'<div style="font-size:0.8em;color:'+ safeColor +';margin-top:4px;">'+ escapeHtml(safeRank) +'</div>' +
|
||
'</div>';
|
||
});
|
||
grid.innerHTML = html;
|
||
} else {
|
||
grid.innerHTML = '<div style="grid-column:1/-1;text-align:center;color:#94a3b8;padding:30px 0;font-size:1.1em;">Keine Spieler online</div>';
|
||
}
|
||
}
|
||
})
|
||
.catch(() => {});
|
||
}
|
||
|
||
function escapeHtml(text) {
|
||
if(!text) return '';
|
||
return text.replace(/[&<>"'=\/]/g, function(s) {
|
||
return {'&':'&','<':'<','>':'>','"':'"',"'":''','/':'/','=':'=','':'`'}[s];
|
||
});
|
||
}
|
||
|
||
setInterval(updateWidgetData, interval);
|
||
setTimeout(updateWidgetData, 700);
|
||
})();
|
||
</script>
|
||
<?php
|
||
return ob_get_clean();
|
||
}
|