492 lines
17 KiB
PHP
492 lines
17 KiB
PHP
<?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();
|
|
}
|