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