Compare commits

2 Commits
1.1.0 ... main

Author SHA1 Message Date
7f5efe273d README.md aktualisiert 2026-01-08 15:16:10 +00:00
d6c3faa42c mc-player-history.php aktualisiert 2026-01-06 18:49:36 +00:00
2 changed files with 518 additions and 507 deletions

View File

@@ -11,12 +11,11 @@ Die Daten werden live über eine externe **StatusAPI** aus deinem Minecraft-Netz
### Pflichtvoraussetzung:
- Ein **BungeeCord-Server**
- Das Plugin **StatusAPI**
- Das Plugin **[StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)**
- **StatusAPI MUSS im BungeeCord-Ordner `/plugins` liegen**
**NICHT** auf Spigot / Paper / Velocity
**NUR** im **BungeeCord-Server unter `/plugins`**
```text
BungeeCord/
├─ plugins/
@@ -24,8 +23,12 @@ BungeeCord/
└─ config.yml
```
---
## 🔗 StatusAPI
**Download:** [https://git.viper.ipv64.net/M_Viper/StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)
Die **StatusAPI** stellt einen HTTP-Endpunkt bereit (z. B. `http://localhost:9191`),
der von diesem WordPress-Plugin **regelmäßig abgefragt** wird, um Spieler- und Online-Daten zu synchronisieren.
@@ -54,30 +57,30 @@ Die StatusAPI **MUSS** auf dem **BungeeCord-Server** installiert sein und sich i
## 📦 Installation (WordPress)
1. Plugin-Dateien hochladen nach:
```text
```text
wp-content/plugins/mc-player-history/
```
```
4. Plugin im WordPress-Admin aktivieren
2. Plugin im WordPress-Admin aktivieren
5. Unter **Einstellungen → MC Player History** die **StatusAPI URL** eintragen
3. Unter **Einstellungen → MC Player History** die **StatusAPI URL** eintragen
Beispiel:
```text
```text
http://localhost:9191
```
```
6. Einstellungen speichern
4. Einstellungen speichern
---
## 🔗 Verbindung zur StatusAPI
- Die **StatusAPI** läuft auf dem **BungeeCord-Server**
- Die **[StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)** läuft auf dem **BungeeCord-Server**
- WordPress ruft diese URL per **HTTP** ab
- Standard-Fallback:
```text
```text
http://localhost:9191
```
```
❗ Stelle sicher, dass:
@@ -88,7 +91,6 @@ Die StatusAPI **MUSS** auf dem **BungeeCord-Server** installiert sein und sich i
---
## 🧩 Shortcode
```shortcode
[mc_player_history]
```
@@ -102,15 +104,15 @@ Die StatusAPI **MUSS** auf dem **BungeeCord-Server** installiert sein und sich i
| only_online | Nur Online-Spieler anzeigen | false |
### Beispiel
```shortcode
[mc_player_history limit="50" interval="5" only_online="true"]
```
---
## 🎨 Anzeige-Reihenfolge (fix)
Die Struktur ist **fest definiert** und kann **nicht verändert** werden:
```text
Avatar
Prefix
@@ -119,6 +121,8 @@ Name
Status (unten)
```
---
## 🛠 Cron & Caching
- WordPress-Cron läuft alle **2 Minuten**
@@ -131,7 +135,7 @@ Status (unten)
### ❌ Keine Spieler sichtbar?
- StatusAPI nicht erreichbar
- **[StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)** nicht erreichbar
- StatusAPI **nicht im `BungeeCord/plugins/`**
- Falsche URL (z. B. `http://` vergessen)
- Firewall blockiert den API-Port
@@ -141,3 +145,10 @@ Status (unten)
- StatusAPI liefert keine `players`
- BungeeCord-Server nicht gestartet
- Falscher API-Port konfiguriert
---
## 📚 Weitere Informationen
Für detaillierte Informationen zur Installation und Konfiguration der StatusAPI besuche das Repository:
**[https://git.viper.ipv64.net/M_Viper/StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)**

View File

@@ -1,492 +1,492 @@
<?php
/*
Plugin Name: MC Player History
Description: Strikte Reihenfolge: Bild -> Prefix -> Name -> Status (unten).
Version: 1.1.0
Author: Dein Name
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
global $mc_player_history_db_version;
$mc_player_history_db_version = '1.15.0';
// Hilfsfunktion für Logging
function mcph_log( $message ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
error_log( '[MC Player History] ' . $message );
} else {
error_log( '[MC Player History] ' . $message );
}
}
}
/**
* Hilfsfunktion: Minecraft Color Codes umwandeln
*/
function mcph_parse_minecraft_colors( $text ) {
if ( empty( $text ) ) return '';
$map = array(
'&0' => '<span style="color: #000000">', '&1' => '<span style="color: #0000AA">',
'&2' => '<span style="color: #00AA00">', '&3' => '<span style="color: #00AAAA">',
'&4' => '<span style="color: #AA0000">', '&5' => '<span style="color: #AA00AA">',
'&6' => '<span style="color: #FFAA00">', '&7' => '<span style="color: #AAAAAA">',
'&8' => '<span style="color: #555555">', '&9' => '<span style="color: #5555FF">',
'&a' => '<span style="color: #55FF55">', '&b' => '<span style="color: #55FFFF">',
'&c' => '<span style="color: #FF5555">', '&d' => '<span style="color: #FF55FF">',
'&e' => '<span style="color: #FFFF55">', '&f' => '<span style="color: #FFFFFF">',
'&l' => '<span style="font-weight: bold">', '&m' => '<span style="text-decoration: line-through">',
'&n' => '<span style="text-decoration: underline">', '&o' => '<span style="font-style: italic">',
'&r' => '</span>',
);
foreach ( $map as $code => $html ) {
if ( $code === '&r' ) {
$text = str_replace( $code, $html . '</span>', $text );
} else {
$text = str_replace( $code, $html, $text );
}
}
return $text;
}
/**
* 1. Tabelle beim Aktivieren anlegen
*/
register_activation_hook( __FILE__, 'mcph_install' );
function mcph_install() {
global $wpdb;
$table_name = $wpdb->prefix . 'mc_players';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
uuid varchar(64) NOT NULL,
username varchar(64) NOT NULL,
prefix varchar(255) DEFAULT NULL,
first_seen datetime NOT NULL,
last_seen datetime NOT NULL,
is_online tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY uuid (uuid),
KEY username (username),
KEY is_online (is_online)
) $charset_collate;";
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
dbDelta( $sql );
update_option( 'mc_player_history_db_version', $mc_player_history_db_version );
}
register_deactivation_hook( __FILE__, 'mcph_deactivate' );
function mcph_deactivate() {
wp_clear_scheduled_hook( 'mcph_sync_event' );
}
/**
* 2. Cron-Job
*/
add_action( 'init', 'mcph_setup_schedule' );
function mcph_setup_schedule() {
if ( ! wp_next_scheduled( 'mcph_sync_event' ) ) {
wp_schedule_event( time(), 'mcph_2min', 'mcph_sync_event' );
}
}
add_filter( 'cron_schedules', 'mcph_add_cron_interval' );
function mcph_add_cron_interval( $schedules ) {
$schedules['mcph_2min'] = array( 'interval' => 2 * MINUTE_IN_SECONDS, 'display' => 'Alle 2 Minuten' );
return $schedules;
}
add_action( 'mcph_sync_event', 'mcph_sync_from_statusapi' );
function mcph_sync_from_statusapi() {
$api_url = get_option( 'mcph_statusapi_url' );
if ( ! empty( $api_url ) && strpos( $api_url, 'http' ) !== 0 ) { $api_url = 'http://' . $api_url; }
if ( empty( $api_url ) ) { $api_url = 'http://localhost:9191'; }
$response = wp_remote_get( $api_url, array( 'timeout' => 5 ) );
if ( is_wp_error( $response ) ) { return; }
$json = json_decode( wp_remote_retrieve_body( $response ) );
if ( ! $json ) { return; }
global $wpdb;
$table_name = $wpdb->prefix . 'mc_players';
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_name'" ) != $table_name ) { return; }
$wpdb->query( "UPDATE $table_name SET is_online = 0" );
$players = isset( $json->players ) && is_array( $json->players ) ? $json->players : array();
foreach ( $players as $p ) {
$name = isset( $p->name ) ? sanitize_text_field( $p->name ) : '';
$prefix = isset( $p->prefix ) ? sanitize_text_field( $p->prefix ) : '';
$uuid = 'legacy-' . md5( strtolower( trim( $name ) ) );
if ( empty( $name ) ) continue;
$exists = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table_name WHERE uuid = %s", $uuid ) );
$now = current_time( 'mysql' );
if ( $exists ) {
$wpdb->update( $table_name, array( 'username' => $name, 'prefix' => $prefix, 'last_seen' => $now, 'is_online' => 1 ), array( 'uuid' => $uuid ), array( '%s', '%s', '%s', '%d' ), array( '%s' ) );
} else {
$wpdb->insert( $table_name, array( 'uuid' => $uuid, 'username' => $name, 'prefix' => $prefix, 'first_seen' => $now, 'last_seen' => $now, 'is_online' => 1 ), array( '%s', '%s', '%s', '%s', '%s', '%d' ) );
}
}
}
/**
* 3. AJAX Handler
*/
add_action( 'wp_ajax_mcph_refresh', 'mcph_ajax_refresh' );
add_action( 'wp_ajax_nopriv_mcph_refresh', 'mcph_ajax_refresh' );
function mcph_ajax_refresh() {
$limit = isset( $_POST['limit'] ) ? intval( $_POST['limit'] ) : 500;
$only_online = isset( $_POST['only_online'] ) && $_POST['only_online'] === 'true';
$html = mcph_generate_player_html( $limit, $only_online, true );
wp_send_json_success( array( 'html' => $html ) );
}
/**
* 4. HTML Generator (Strikte Reihenfolge)
*/
function mcph_generate_player_html( $limit = 500, $only_online = false, $is_ajax = false ) {
global $wpdb;
$table_name = $wpdb->prefix . 'mc_players';
// --- LIVE STATUS CHECK MIT CACHE ---
$cache_key = 'mcph_api_cache_data';
$api_response = get_transient( $cache_key );
if ( false === $api_response ) {
$api_url = get_option( 'mcph_statusapi_url' );
if ( ! empty( $api_url ) && strpos( $api_url, 'http' ) !== 0 ) { $api_url = 'http://' . $api_url; }
if ( empty( $api_url ) ) { $api_url = 'http://localhost:9191'; }
$response = wp_remote_get( $api_url, array( 'timeout' => 2 ) );
if ( ! is_wp_error( $response ) ) {
$body = wp_remote_retrieve_body( $response );
$json = json_decode( $body );
if ( $json && isset( $json->players ) && is_array( $json->players ) ) {
set_transient( $cache_key, $json->players, 5 );
$api_response = $json->players;
}
}
}
$live_online_uuids = array();
$has_live_data = false;
if ( is_array( $api_response ) && ! empty( $api_response ) ) {
$has_live_data = true;
foreach ( $api_response as $p ) {
if ( isset( $p->name ) ) {
$uuid = 'legacy-' . md5( strtolower( trim( $p->name ) ) );
$live_online_uuids[ $uuid ] = true;
}
}
}
$sql_limit = $only_online ? ($limit * 2) : $limit;
$rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table_name ORDER BY last_seen DESC LIMIT %d", $sql_limit ) );
if ( empty( $rows ) ) {
return '<p style="text-align:center; color:#777;">Keine Spieler gefunden.</p>';
}
ob_start();
echo '<div class="mc-grid">';
$displayed_count = 0;
foreach ( $rows as $row ) {
$is_online = false;
if ( $has_live_data ) {
$is_online = isset( $live_online_uuids[ $row->uuid ] );
} else {
$is_online = ( $row->is_online == 1 );
}
if ( $only_online && ! $is_online ) {
continue;
}
if ( $displayed_count >= $limit ) {
break;
}
$displayed_count++;
$username = esc_html( $row->username );
$prefix = mcph_parse_minecraft_colors( $row->prefix );
$avatar = 'https://minotar.net/avatar/' . $username . '/80';
$anim_style = '';
if ( ! $is_ajax ) {
$anim_style = 'animation: fadeInUp 0.5s ease forwards; opacity: 0; animation-delay: ' . ($displayed_count * 0.05) . 's;';
}
if ( $is_online ) {
$status_html = '<span class="mc-status mc-online">Online</span>';
} else {
$time = strtotime( $row->last_seen );
$date_str = ( date('d.m.Y', $time) == date('d.m.Y') ) ? date('H:i', $time) : date('d.m.Y H:i', $time);
$status_html = '<span class="mc-status mc-offline">Zuletzt: ' . $date_str . '</span>';
}
// STRUKTUR: Bild -> Prefix -> Name -> Spacer -> Status
echo '<div class="mc-player-card" style="' . $anim_style . '">';
echo '<img src="' . $avatar . '" class="mc-avatar" alt="' . $username . '">';
echo '<div class="mc-info-stack">';
// 1. PREFIX
if ( ! empty( $row->prefix ) ) {
echo '<span class="mc-prefix">' . $prefix . '</span>';
}
// 2. NAME
echo '<span class="mc-name">' . $username . '</span>';
// 3. SPACER (Nimmt den verbleibenden Platz ein)
echo '<div class="mc-spacer"></div>';
// 4. STATUS
echo '<div class="mc-status-line">' . $status_html . '</div>';
echo '</div>';
echo '</div>';
}
echo '</div>';
echo '<div class="mc-update-time">Aktualisiert: ' . current_time('H:i:s') . '</div>';
return ob_get_clean();
}
/**
* 5. Admin Menü
*/
add_action( 'admin_menu', 'mcph_admin_menu' );
function mcph_admin_menu() {
add_options_page( 'MC Player History', 'MC Player History', 'manage_options', 'mc_player_history', 'mcph_options_page' );
}
add_action( 'admin_init', 'mcph_settings_init' );
function mcph_settings_init() {
register_setting( 'mcph_plugin_options', 'mcph_statusapi_url' );
}
function mcph_options_page() {
if ( isset( $_POST['mcph_manual_sync'] ) && check_admin_referer( 'mcph_manual_sync_action' ) ) {
mcph_sync_from_statusapi();
delete_transient( 'mcph_api_cache_data' );
echo '<div class="notice notice-success"><p>Manueller Sync ausgeführt.</p></div>';
}
?>
<div class="wrap">
<h1>MC Player History Einstellungen</h1>
<form method="post" action="options.php">
<?php settings_fields( 'mcph_plugin_options' ); do_settings_sections( 'mcph_plugin_options' ); ?>
<table class="form-table">
<tr valign="top">
<th scope="row">StatusAPI URL</th>
<td>
<input type="text" name="mcph_statusapi_url" value="<?php echo esc_attr( get_option( 'mcph_statusapi_url' ) ); ?>" class="regular-text" placeholder="http://localhost:9191" />
<p class="description">Bitte unbedingt mit http:// angeben.</p>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
<h2>Manueller Sync</h2>
<form method="post">
<?php wp_nonce_field( 'mcph_manual_sync_action' ); ?>
<input type="submit" name="mcph_manual_sync" class="button" value="Jetzt synchronisieren" />
</form>
</div>
<?php
}
/**
* 6. Shortcode + CSS
*/
add_shortcode( 'mc_player_history', 'mcph_shortcode' );
function mcph_shortcode( $atts ) {
$atts = shortcode_atts( array(
'limit' => 500,
'interval' => 2,
'only_online' => 'false'
), $atts );
$container_id = 'mc-player-wrapper-' . uniqid();
ob_start();
echo '<div id="' . $container_id . '" class="mc-player-list">';
echo mcph_generate_player_html( $atts['limit'], $atts['only_online'] === 'true', false );
echo '</div>';
?>
<script>
(function() {
const intervalSeconds = <?php echo intval($atts['interval']); ?>;
const wrapperId = "<?php echo $container_id; ?>";
const limit = <?php echo intval($atts['limit']); ?>;
const onlyOnline = "<?php echo ($atts['only_online'] === 'true') ? 'true' : 'false'; ?>";
function refreshList() {
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
body: 'action=mcph_refresh&limit=' + limit + '&only_online=' + onlyOnline
})
.then(response => response.json())
.then(data => {
if (data.success) {
const wrapper = document.getElementById(wrapperId);
if (wrapper) {
wrapper.innerHTML = data.data.html;
}
}
})
.catch(error => console.error('MC Player History Error:', error));
}
setInterval(refreshList, intervalSeconds * 1000);
})();
</script>
<?php
echo '<style>
.mc-player-list { width: 100%; }
.mc-grid {
display: grid;
gap: 20px;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
justify-content: center;
align-items: stretch;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* KARTE */
.mc-player-card {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px; /* Weniger Abstand dazwischen */
padding: 20px 10px;
background: #fff;
border: 1px solid #eee;
border-radius: 8px;
transition: all 0.3s ease;
cursor: default;
position: relative; /* Fallback */
}
.mc-player-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
border-color: #ddd;
}
.mc-avatar {
border-radius: 4px;
border: 3px solid #ddd;
background: #fff;
width: 60px;
height: 60px;
transition: border-color 0.3s;
flex-shrink: 0;
}
.mc-player-card:hover .mc-avatar {
border-color: #bbb;
}
/* INFOS BEREICH */
.mc-info-stack {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
flex-grow: 1; /* Füllt den Rest der Karte */
position: relative; /* Referenz für absoluten Status */
padding-bottom: 35px; /* Platz für Status unten */
justify-content: flex-start; /* Oben ausrichten */
}
/* 1. PREFIX */
.mc-prefix {
font-size: 0.9em;
display: block;
width: 100%;
text-align: center;
margin-bottom: 4px;
line-height: 1.2;
}
/* 2. NAME */
.mc-name {
font-weight: bold;
font-size: 1.1em;
color: #333;
word-break: break-word;
display: block;
width: 100%;
text-align: center;
}
/* 3. SPACER */
.mc-spacer {
flex-grow: 1; /* Nimmt ALLEN leeren Platz zwischen Name und Status ein */
}
/* 4. STATUS (UNTEN) */
.mc-status-line {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
margin: 0;
padding-bottom: 5px;
}
.mc-status {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8em;
font-weight: 600;
}
.mc-online { background-color: #e6fffa; color: #28a745; border: 1px solid #b2f5ea; }
.mc-offline { background-color: #fff5f5; color: #e53e3e; border: 1px solid #feb2b2; }
.mc-update-time {
font-size: 0.8em;
color: #999;
text-align: center;
margin-top: 15px;
font-style: italic;
}
@media (min-width: 1200px) {
.mc-grid { grid-template-columns: repeat(6, 1fr); }
}
@media (max-width: 600px) {
.mc-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
.mc-avatar { width: 50px; height: 50px; }
.mc-name { font-size: 1em; }
.mc-player-card { padding: 10px 5px; }
}
</style>';
return ob_get_clean();
<?php
/*
Plugin Name: MC Player History
Description: Spielerverlauf deines Minecraft Servers.
Version: 1.1.0
Author: Dein Name
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
global $mc_player_history_db_version;
$mc_player_history_db_version = '1.15.0';
// Hilfsfunktion für Logging
function mcph_log( $message ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
error_log( '[MC Player History] ' . $message );
} else {
error_log( '[MC Player History] ' . $message );
}
}
}
/**
* Hilfsfunktion: Minecraft Color Codes umwandeln
*/
function mcph_parse_minecraft_colors( $text ) {
if ( empty( $text ) ) return '';
$map = array(
'&0' => '<span style="color: #000000">', '&1' => '<span style="color: #0000AA">',
'&2' => '<span style="color: #00AA00">', '&3' => '<span style="color: #00AAAA">',
'&4' => '<span style="color: #AA0000">', '&5' => '<span style="color: #AA00AA">',
'&6' => '<span style="color: #FFAA00">', '&7' => '<span style="color: #AAAAAA">',
'&8' => '<span style="color: #555555">', '&9' => '<span style="color: #5555FF">',
'&a' => '<span style="color: #55FF55">', '&b' => '<span style="color: #55FFFF">',
'&c' => '<span style="color: #FF5555">', '&d' => '<span style="color: #FF55FF">',
'&e' => '<span style="color: #FFFF55">', '&f' => '<span style="color: #FFFFFF">',
'&l' => '<span style="font-weight: bold">', '&m' => '<span style="text-decoration: line-through">',
'&n' => '<span style="text-decoration: underline">', '&o' => '<span style="font-style: italic">',
'&r' => '</span>',
);
foreach ( $map as $code => $html ) {
if ( $code === '&r' ) {
$text = str_replace( $code, $html . '</span>', $text );
} else {
$text = str_replace( $code, $html, $text );
}
}
return $text;
}
/**
* 1. Tabelle beim Aktivieren anlegen
*/
register_activation_hook( __FILE__, 'mcph_install' );
function mcph_install() {
global $wpdb;
$table_name = $wpdb->prefix . 'mc_players';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
uuid varchar(64) NOT NULL,
username varchar(64) NOT NULL,
prefix varchar(255) DEFAULT NULL,
first_seen datetime NOT NULL,
last_seen datetime NOT NULL,
is_online tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY uuid (uuid),
KEY username (username),
KEY is_online (is_online)
) $charset_collate;";
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
dbDelta( $sql );
update_option( 'mc_player_history_db_version', $mc_player_history_db_version );
}
register_deactivation_hook( __FILE__, 'mcph_deactivate' );
function mcph_deactivate() {
wp_clear_scheduled_hook( 'mcph_sync_event' );
}
/**
* 2. Cron-Job
*/
add_action( 'init', 'mcph_setup_schedule' );
function mcph_setup_schedule() {
if ( ! wp_next_scheduled( 'mcph_sync_event' ) ) {
wp_schedule_event( time(), 'mcph_2min', 'mcph_sync_event' );
}
}
add_filter( 'cron_schedules', 'mcph_add_cron_interval' );
function mcph_add_cron_interval( $schedules ) {
$schedules['mcph_2min'] = array( 'interval' => 2 * MINUTE_IN_SECONDS, 'display' => 'Alle 2 Minuten' );
return $schedules;
}
add_action( 'mcph_sync_event', 'mcph_sync_from_statusapi' );
function mcph_sync_from_statusapi() {
$api_url = get_option( 'mcph_statusapi_url' );
if ( ! empty( $api_url ) && strpos( $api_url, 'http' ) !== 0 ) { $api_url = 'http://' . $api_url; }
if ( empty( $api_url ) ) { $api_url = 'http://localhost:9191'; }
$response = wp_remote_get( $api_url, array( 'timeout' => 5 ) );
if ( is_wp_error( $response ) ) { return; }
$json = json_decode( wp_remote_retrieve_body( $response ) );
if ( ! $json ) { return; }
global $wpdb;
$table_name = $wpdb->prefix . 'mc_players';
if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_name'" ) != $table_name ) { return; }
$wpdb->query( "UPDATE $table_name SET is_online = 0" );
$players = isset( $json->players ) && is_array( $json->players ) ? $json->players : array();
foreach ( $players as $p ) {
$name = isset( $p->name ) ? sanitize_text_field( $p->name ) : '';
$prefix = isset( $p->prefix ) ? sanitize_text_field( $p->prefix ) : '';
$uuid = 'legacy-' . md5( strtolower( trim( $name ) ) );
if ( empty( $name ) ) continue;
$exists = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table_name WHERE uuid = %s", $uuid ) );
$now = current_time( 'mysql' );
if ( $exists ) {
$wpdb->update( $table_name, array( 'username' => $name, 'prefix' => $prefix, 'last_seen' => $now, 'is_online' => 1 ), array( 'uuid' => $uuid ), array( '%s', '%s', '%s', '%d' ), array( '%s' ) );
} else {
$wpdb->insert( $table_name, array( 'uuid' => $uuid, 'username' => $name, 'prefix' => $prefix, 'first_seen' => $now, 'last_seen' => $now, 'is_online' => 1 ), array( '%s', '%s', '%s', '%s', '%s', '%d' ) );
}
}
}
/**
* 3. AJAX Handler
*/
add_action( 'wp_ajax_mcph_refresh', 'mcph_ajax_refresh' );
add_action( 'wp_ajax_nopriv_mcph_refresh', 'mcph_ajax_refresh' );
function mcph_ajax_refresh() {
$limit = isset( $_POST['limit'] ) ? intval( $_POST['limit'] ) : 500;
$only_online = isset( $_POST['only_online'] ) && $_POST['only_online'] === 'true';
$html = mcph_generate_player_html( $limit, $only_online, true );
wp_send_json_success( array( 'html' => $html ) );
}
/**
* 4. HTML Generator (Strikte Reihenfolge)
*/
function mcph_generate_player_html( $limit = 500, $only_online = false, $is_ajax = false ) {
global $wpdb;
$table_name = $wpdb->prefix . 'mc_players';
// --- LIVE STATUS CHECK MIT CACHE ---
$cache_key = 'mcph_api_cache_data';
$api_response = get_transient( $cache_key );
if ( false === $api_response ) {
$api_url = get_option( 'mcph_statusapi_url' );
if ( ! empty( $api_url ) && strpos( $api_url, 'http' ) !== 0 ) { $api_url = 'http://' . $api_url; }
if ( empty( $api_url ) ) { $api_url = 'http://localhost:9191'; }
$response = wp_remote_get( $api_url, array( 'timeout' => 2 ) );
if ( ! is_wp_error( $response ) ) {
$body = wp_remote_retrieve_body( $response );
$json = json_decode( $body );
if ( $json && isset( $json->players ) && is_array( $json->players ) ) {
set_transient( $cache_key, $json->players, 5 );
$api_response = $json->players;
}
}
}
$live_online_uuids = array();
$has_live_data = false;
if ( is_array( $api_response ) && ! empty( $api_response ) ) {
$has_live_data = true;
foreach ( $api_response as $p ) {
if ( isset( $p->name ) ) {
$uuid = 'legacy-' . md5( strtolower( trim( $p->name ) ) );
$live_online_uuids[ $uuid ] = true;
}
}
}
$sql_limit = $only_online ? ($limit * 2) : $limit;
$rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table_name ORDER BY last_seen DESC LIMIT %d", $sql_limit ) );
if ( empty( $rows ) ) {
return '<p style="text-align:center; color:#777;">Keine Spieler gefunden.</p>';
}
ob_start();
echo '<div class="mc-grid">';
$displayed_count = 0;
foreach ( $rows as $row ) {
$is_online = false;
if ( $has_live_data ) {
$is_online = isset( $live_online_uuids[ $row->uuid ] );
} else {
$is_online = ( $row->is_online == 1 );
}
if ( $only_online && ! $is_online ) {
continue;
}
if ( $displayed_count >= $limit ) {
break;
}
$displayed_count++;
$username = esc_html( $row->username );
$prefix = mcph_parse_minecraft_colors( $row->prefix );
$avatar = 'https://minotar.net/avatar/' . $username . '/80';
$anim_style = '';
if ( ! $is_ajax ) {
$anim_style = 'animation: fadeInUp 0.5s ease forwards; opacity: 0; animation-delay: ' . ($displayed_count * 0.05) . 's;';
}
if ( $is_online ) {
$status_html = '<span class="mc-status mc-online">Online</span>';
} else {
$time = strtotime( $row->last_seen );
$date_str = ( date('d.m.Y', $time) == date('d.m.Y') ) ? date('H:i', $time) : date('d.m.Y H:i', $time);
$status_html = '<span class="mc-status mc-offline">Zuletzt: ' . $date_str . '</span>';
}
// STRUKTUR: Bild -> Prefix -> Name -> Spacer -> Status
echo '<div class="mc-player-card" style="' . $anim_style . '">';
echo '<img src="' . $avatar . '" class="mc-avatar" alt="' . $username . '">';
echo '<div class="mc-info-stack">';
// 1. PREFIX
if ( ! empty( $row->prefix ) ) {
echo '<span class="mc-prefix">' . $prefix . '</span>';
}
// 2. NAME
echo '<span class="mc-name">' . $username . '</span>';
// 3. SPACER (Nimmt den verbleibenden Platz ein)
echo '<div class="mc-spacer"></div>';
// 4. STATUS
echo '<div class="mc-status-line">' . $status_html . '</div>';
echo '</div>';
echo '</div>';
}
echo '</div>';
echo '<div class="mc-update-time">Aktualisiert: ' . current_time('H:i:s') . '</div>';
return ob_get_clean();
}
/**
* 5. Admin Menü
*/
add_action( 'admin_menu', 'mcph_admin_menu' );
function mcph_admin_menu() {
add_options_page( 'MC Player History', 'MC Player History', 'manage_options', 'mc_player_history', 'mcph_options_page' );
}
add_action( 'admin_init', 'mcph_settings_init' );
function mcph_settings_init() {
register_setting( 'mcph_plugin_options', 'mcph_statusapi_url' );
}
function mcph_options_page() {
if ( isset( $_POST['mcph_manual_sync'] ) && check_admin_referer( 'mcph_manual_sync_action' ) ) {
mcph_sync_from_statusapi();
delete_transient( 'mcph_api_cache_data' );
echo '<div class="notice notice-success"><p>Manueller Sync ausgeführt.</p></div>';
}
?>
<div class="wrap">
<h1>MC Player History Einstellungen</h1>
<form method="post" action="options.php">
<?php settings_fields( 'mcph_plugin_options' ); do_settings_sections( 'mcph_plugin_options' ); ?>
<table class="form-table">
<tr valign="top">
<th scope="row">StatusAPI URL</th>
<td>
<input type="text" name="mcph_statusapi_url" value="<?php echo esc_attr( get_option( 'mcph_statusapi_url' ) ); ?>" class="regular-text" placeholder="http://localhost:9191" />
<p class="description">Bitte unbedingt mit http:// angeben.</p>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
<h2>Manueller Sync</h2>
<form method="post">
<?php wp_nonce_field( 'mcph_manual_sync_action' ); ?>
<input type="submit" name="mcph_manual_sync" class="button" value="Jetzt synchronisieren" />
</form>
</div>
<?php
}
/**
* 6. Shortcode + CSS
*/
add_shortcode( 'mc_player_history', 'mcph_shortcode' );
function mcph_shortcode( $atts ) {
$atts = shortcode_atts( array(
'limit' => 500,
'interval' => 2,
'only_online' => 'false'
), $atts );
$container_id = 'mc-player-wrapper-' . uniqid();
ob_start();
echo '<div id="' . $container_id . '" class="mc-player-list">';
echo mcph_generate_player_html( $atts['limit'], $atts['only_online'] === 'true', false );
echo '</div>';
?>
<script>
(function() {
const intervalSeconds = <?php echo intval($atts['interval']); ?>;
const wrapperId = "<?php echo $container_id; ?>";
const limit = <?php echo intval($atts['limit']); ?>;
const onlyOnline = "<?php echo ($atts['only_online'] === 'true') ? 'true' : 'false'; ?>";
function refreshList() {
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
body: 'action=mcph_refresh&limit=' + limit + '&only_online=' + onlyOnline
})
.then(response => response.json())
.then(data => {
if (data.success) {
const wrapper = document.getElementById(wrapperId);
if (wrapper) {
wrapper.innerHTML = data.data.html;
}
}
})
.catch(error => console.error('MC Player History Error:', error));
}
setInterval(refreshList, intervalSeconds * 1000);
})();
</script>
<?php
echo '<style>
.mc-player-list { width: 100%; }
.mc-grid {
display: grid;
gap: 20px;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
justify-content: center;
align-items: stretch;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* KARTE */
.mc-player-card {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px; /* Weniger Abstand dazwischen */
padding: 20px 10px;
background: #fff;
border: 1px solid #eee;
border-radius: 8px;
transition: all 0.3s ease;
cursor: default;
position: relative; /* Fallback */
}
.mc-player-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
border-color: #ddd;
}
.mc-avatar {
border-radius: 4px;
border: 3px solid #ddd;
background: #fff;
width: 60px;
height: 60px;
transition: border-color 0.3s;
flex-shrink: 0;
}
.mc-player-card:hover .mc-avatar {
border-color: #bbb;
}
/* INFOS BEREICH */
.mc-info-stack {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
flex-grow: 1; /* Füllt den Rest der Karte */
position: relative; /* Referenz für absoluten Status */
padding-bottom: 35px; /* Platz für Status unten */
justify-content: flex-start; /* Oben ausrichten */
}
/* 1. PREFIX */
.mc-prefix {
font-size: 0.9em;
display: block;
width: 100%;
text-align: center;
margin-bottom: 4px;
line-height: 1.2;
}
/* 2. NAME */
.mc-name {
font-weight: bold;
font-size: 1.1em;
color: #333;
word-break: break-word;
display: block;
width: 100%;
text-align: center;
}
/* 3. SPACER */
.mc-spacer {
flex-grow: 1; /* Nimmt ALLEN leeren Platz zwischen Name und Status ein */
}
/* 4. STATUS (UNTEN) */
.mc-status-line {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
margin: 0;
padding-bottom: 5px;
}
.mc-status {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8em;
font-weight: 600;
}
.mc-online { background-color: #e6fffa; color: #28a745; border: 1px solid #b2f5ea; }
.mc-offline { background-color: #fff5f5; color: #e53e3e; border: 1px solid #feb2b2; }
.mc-update-time {
font-size: 0.8em;
color: #999;
text-align: center;
margin-top: 15px;
font-style: italic;
}
@media (min-width: 1200px) {
.mc-grid { grid-template-columns: repeat(6, 1fr); }
}
@media (max-width: 600px) {
.mc-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
.mc-avatar { width: 50px; height: 50px; }
.mc-name { font-size: 1em; }
.mc-player-card { padding: 10px 5px; }
}
</style>';
return ob_get_clean();
}