diff --git a/mc-player-history.php b/mc-player-history.php new file mode 100644 index 0000000..7664f9c --- /dev/null +++ b/mc-player-history.php @@ -0,0 +1,492 @@ + 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' => '', '&1' => '', + '&2' => '', '&3' => '', + '&4' => '', '&5' => '', + '&6' => '', '&7' => '', + '&8' => '', '&9' => '', + '&a' => '', '&b' => '', + '&c' => '', '&d' => '', + '&e' => '', '&f' => '', + '&l' => '', '&m' => '', + '&n' => '', '&o' => '', + '&r' => '', + ); + foreach ( $map as $code => $html ) { + if ( $code === '&r' ) { + $text = str_replace( $code, $html . '', $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 '

Keine Spieler gefunden.

'; + } + + ob_start(); + echo '
'; + + $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 = 'Online'; + } 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 = 'Zuletzt: ' . $date_str . ''; + } + + // STRUKTUR: Bild -> Prefix -> Name -> Spacer -> Status + echo '
'; + echo '' . $username . ''; + + echo '
'; + + // 1. PREFIX + if ( ! empty( $row->prefix ) ) { + echo '' . $prefix . ''; + } + + // 2. NAME + echo '' . $username . ''; + + // 3. SPACER (Nimmt den verbleibenden Platz ein) + echo '
'; + + // 4. STATUS + echo '
' . $status_html . '
'; + + echo '
'; + echo '
'; + } + echo '
'; + + echo '
Aktualisiert: ' . current_time('H:i:s') . '
'; + + 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 '

Manueller Sync ausgeführt.

'; + } + ?> +
+

MC Player History Einstellungen

+
+ + + + + + +
StatusAPI URL + +

Bitte unbedingt mit http:// angeben.

+
+ +
+

Manueller Sync

+
+ + +
+
+ 500, + 'interval' => 2, + 'only_online' => 'false' + ), $atts ); + + $container_id = 'mc-player-wrapper-' . uniqid(); + + ob_start(); + + echo '
'; + echo mcph_generate_player_html( $atts['limit'], $atts['only_online'] === 'true', false ); + echo '
'; + + ?> + + + .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; } + } + '; + + return ob_get_clean(); +} \ No newline at end of file