From d358924f0a07a16e94c1d3670cadf957825a146c Mon Sep 17 00:00:00 2001 From: Git Manager GUI Date: Tue, 7 Apr 2026 19:59:16 +0200 Subject: [PATCH] Upload via Git Manager GUI --- mc-player-history.php | 1316 +++++++++++++++++++++++++---------------- 1 file changed, 800 insertions(+), 516 deletions(-) diff --git a/mc-player-history.php b/mc-player-history.php index 86c4bc4..3712bec 100644 --- a/mc-player-history.php +++ b/mc-player-history.php @@ -3,7 +3,7 @@ Plugin Name: MC Player History Plugin URI: https://git.viper.ipv64.net/M_Viper/MC-Player-History---WordPress-Plugin Description: Spielerverlauf deines Minecraft Servers. -Version: 1.1.3 +Version: 1.3.0 Author: M_Viper Author URI: https://m-viper.de Requires at least: 6.8 @@ -31,7 +31,6 @@ function mcph_get_plugin_version() { if ( ! function_exists( 'get_plugin_data' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } - $plugin_data = get_plugin_data( __FILE__ ); return $plugin_data['Version'] ?? '0.0.0'; } @@ -69,14 +68,12 @@ function mcph_get_latest_release_info( $force_refresh = false ) { if ( $data && isset($data['tag_name']) ) { $tag = ltrim( $data['tag_name'], 'v' ); - $release_info = [ 'version' => $tag, 'download_url' => $data['zipball_url'] ?? '', 'notes' => $data['body'] ?? '', 'published_at' => $data['published_at'] ?? '', ]; - set_transient( $transient_key, $release_info, 6 * HOUR_IN_SECONDS ); } else { set_transient( $transient_key, [], HOUR_IN_SECONDS ); @@ -85,25 +82,18 @@ function mcph_get_latest_release_info( $force_refresh = false ) { set_transient( $transient_key, [], HOUR_IN_SECONDS ); } } - return $release_info; } // Admin-Update-Hinweis function mcph_show_update_notice() { - if ( ! current_user_can('manage_options') ) { - return; - } + if ( ! current_user_can('manage_options') ) return; $current_version = mcph_get_plugin_version(); $latest_release = mcph_get_latest_release_info(); if ( ! empty($latest_release['version']) && version_compare($current_version, $latest_release['version'], '<') ) { - - $refresh_url = wp_nonce_url( - admin_url('plugins.php?mcph_clear_cache=1'), - 'mcph_clear_cache_action' - ); + $refresh_url = wp_nonce_url( admin_url('plugins.php?mcph_clear_cache=1'), 'mcph_clear_cache_action' ); ?>

MC Player History – Update verfügbar

@@ -112,15 +102,9 @@ function mcph_show_update_notice() { Neueste Version:

- - Update herunterladen - - - Release Notes - - - Jetzt neu prüfen - + Update herunterladen + Release Notes + Jetzt neu prüfen

last_error ) ) { + mcph_log( 'LiteBans Verbindung fehlgeschlagen: ' . $lbdb->last_error ); + $connection = null; + return $connection; + } + + $connection = array( + 'db' => $lbdb, + 'prefix' => $prefix, + ); + + return $connection; +} + +/** + * Holt Bans/Mutes/Warns aus LiteBans für einen Spieler. + */ +function mcph_get_litebans_punishments( $uuid, $username ) { + static $count_cache = array(); + + $cache_key = (string) $uuid . '|' . strtolower( (string) $username ); + if ( isset( $count_cache[ $cache_key ] ) ) { + return $count_cache[ $cache_key ]; + } + + $conn = mcph_get_litebans_connection(); + if ( empty( $conn['db'] ) || empty( $conn['prefix'] ) ) { + $count_cache[ $cache_key ] = null; + return null; + } + + $lbdb = $conn['db']; + $prefix = $conn['prefix']; + + $search_uuid = ''; + if ( ! empty( $uuid ) && strpos( $uuid, 'legacy-' ) !== 0 ) { + $search_uuid = $uuid; + } + + if ( empty( $search_uuid ) && ! empty( $username ) ) { + $history_table = esc_sql( $prefix . 'history' ); + $search_uuid = $lbdb->get_var( + $lbdb->prepare( + "SELECT uuid FROM {$history_table} WHERE name = %s ORDER BY date DESC LIMIT 1", + $username + ) + ); + } + + if ( empty( $search_uuid ) ) { + $count_cache[ $cache_key ] = null; + return null; + } + + $bans_table = esc_sql( $prefix . 'bans' ); + $mutes_table = esc_sql( $prefix . 'mutes' ); + $warns_table = esc_sql( $prefix . 'warnings' ); + + $bans = intval( $lbdb->get_var( $lbdb->prepare( "SELECT COUNT(*) FROM {$bans_table} WHERE uuid = %s", $search_uuid ) ) ); + $mutes = intval( $lbdb->get_var( $lbdb->prepare( "SELECT COUNT(*) FROM {$mutes_table} WHERE uuid = %s", $search_uuid ) ) ); + $warns = intval( $lbdb->get_var( $lbdb->prepare( "SELECT COUNT(*) FROM {$warns_table} WHERE uuid = %s", $search_uuid ) ) ); + + if ( ! empty( $lbdb->last_error ) ) { + mcph_log( 'LiteBans Query Fehler: ' . $lbdb->last_error ); + $count_cache[ $cache_key ] = null; + return null; + } + + $count_cache[ $cache_key ] = array( + 'bans' => $bans, + 'mutes' => $mutes, + 'warns' => $warns, + ); + + return $count_cache[ $cache_key ]; +} + /** * Hilfsfunktion: Minecraft Color Codes umwandeln */ @@ -172,13 +263,60 @@ function mcph_parse_minecraft_colors( $text ) { } /** - * 1. Tabelle beim Aktivieren anlegen + Playtime Spalte + * Erkennt Bedrock-Spieler robust aus API-Objekt, UUID/XUID und Namen. + */ +function mcph_detect_bedrock_player( $player, $uuid = '', $name = '' ) { + if ( is_object( $player ) ) { + $flag_keys = array( 'isBedrock', 'is_bedrock', 'bedrock', 'floodgate' ); + foreach ( $flag_keys as $key ) { + if ( isset( $player->$key ) ) { + $value = $player->$key; + if ( is_bool( $value ) ) { + return $value ? 1 : 0; + } + if ( is_numeric( $value ) ) { + return intval( $value ) > 0 ? 1 : 0; + } + if ( is_string( $value ) ) { + $normalized = strtolower( trim( $value ) ); + if ( in_array( $normalized, array( '1', 'true', 'yes', 'y', 'on' ), true ) ) { + return 1; + } + if ( in_array( $normalized, array( '0', 'false', 'no', 'n', 'off' ), true ) ) { + return 0; + } + } + } + } + + if ( isset( $player->xuid ) && ! empty( $player->xuid ) ) { + return 1; + } + } + + $uuid = strtolower( (string) $uuid ); + if ( strpos( $uuid, 'xuid' ) === 0 || strpos( $uuid, 'floodgate' ) === 0 ) { + return 1; + } + + // Java-Namen enthalten keinen Punkt; viele Bedrock-Setups nutzen Punkt-Präfix. + $name = trim( (string) $name ); + if ( strpos( $name, '.' ) !== false ) { + return 1; + } + + return 0; +} + +/** + * 1. Tabelle beim Aktivieren anlegen + NEUE FELDER */ register_activation_hook( __FILE__, 'mcph_install' ); function mcph_install() { - global $wpdb, $mc_player_history_db_version; // FIX: Variable global verfügbar machen + global $wpdb, $mc_player_history_db_version; $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, @@ -188,12 +326,21 @@ function mcph_install() { last_seen datetime NOT NULL, is_online tinyint(1) NOT NULL DEFAULT 0, playtime_seconds INT NOT NULL DEFAULT 0, + kills INT NOT NULL DEFAULT 0, + deaths INT NOT NULL DEFAULT 0, + balance DOUBLE NOT NULL DEFAULT 0, + is_bedrock tinyint(1) NOT NULL DEFAULT 0, + joins INT NOT NULL DEFAULT 0, + bans INT NOT NULL DEFAULT 0, + mutes INT NOT NULL DEFAULT 0, + warns INT NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY uuid (uuid), KEY username (username), KEY is_online (is_online), KEY playtime_seconds (playtime_seconds) ) $charset_collate;"; + require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); dbDelta( $sql ); update_option( 'mc_player_history_db_version', $mc_player_history_db_version ); @@ -205,19 +352,49 @@ function mcph_deactivate() { } /** - * NEU: Playtime Spalte hinzufügen (wird beim Laden geprüft) + * Erweiterung der Datenbank beim Laden (falls Update) */ -add_action( 'init', 'mcph_add_playtime_column' ); -function mcph_add_playtime_column() { +add_action( 'init', 'mcph_add_columns_v130' ); +function mcph_add_columns_v130() { global $wpdb; $table_name = $wpdb->prefix . 'mc_players'; - // Prüfen, ob Spalte existiert - $column_exists = $wpdb->get_var( "SHOW COLUMNS FROM $table_name LIKE 'playtime_seconds'" ); - - if ( empty( $column_exists ) ) { + // Playtime + if ( ! $wpdb->get_var( "SHOW COLUMNS FROM $table_name LIKE 'playtime_seconds'" ) ) { $wpdb->query( "ALTER TABLE $table_name ADD COLUMN playtime_seconds INT NOT NULL DEFAULT 0 AFTER last_seen" ); } + // Kills + if ( ! $wpdb->get_var( "SHOW COLUMNS FROM $table_name LIKE 'kills'" ) ) { + $wpdb->query( "ALTER TABLE $table_name ADD COLUMN kills INT NOT NULL DEFAULT 0 AFTER playtime_seconds" ); + } + // Deaths + if ( ! $wpdb->get_var( "SHOW COLUMNS FROM $table_name LIKE 'deaths'" ) ) { + $wpdb->query( "ALTER TABLE $table_name ADD COLUMN deaths INT NOT NULL DEFAULT 0 AFTER kills" ); + } + // Balance + if ( ! $wpdb->get_var( "SHOW COLUMNS FROM $table_name LIKE 'balance'" ) ) { + $wpdb->query( "ALTER TABLE $table_name ADD COLUMN balance DOUBLE NOT NULL DEFAULT 0 AFTER deaths" ); + } + // Is Bedrock + if ( ! $wpdb->get_var( "SHOW COLUMNS FROM $table_name LIKE 'is_bedrock'" ) ) { + $wpdb->query( "ALTER TABLE $table_name ADD COLUMN is_bedrock tinyint(1) NOT NULL DEFAULT 0 AFTER balance" ); + } + // Joins + if ( ! $wpdb->get_var( "SHOW COLUMNS FROM $table_name LIKE 'joins'" ) ) { + $wpdb->query( "ALTER TABLE $table_name ADD COLUMN joins INT NOT NULL DEFAULT 0 AFTER is_bedrock" ); + } + // Bans + if ( ! $wpdb->get_var( "SHOW COLUMNS FROM $table_name LIKE 'bans'" ) ) { + $wpdb->query( "ALTER TABLE $table_name ADD COLUMN bans INT NOT NULL DEFAULT 0 AFTER joins" ); + } + // Mutes + if ( ! $wpdb->get_var( "SHOW COLUMNS FROM $table_name LIKE 'mutes'" ) ) { + $wpdb->query( "ALTER TABLE $table_name ADD COLUMN mutes INT NOT NULL DEFAULT 0 AFTER bans" ); + } + // Warns + if ( ! $wpdb->get_var( "SHOW COLUMNS FROM $table_name LIKE 'warns'" ) ) { + $wpdb->query( "ALTER TABLE $table_name ADD COLUMN warns INT NOT NULL DEFAULT 0 AFTER mutes" ); + } } /** @@ -250,79 +427,134 @@ function mcph_sync_from_statusapi() { $table_name = $wpdb->prefix . 'mc_players'; if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_name'" ) != $table_name ) { return; } - // 1. Alle Spieler auf Offline setzen (Reset) + // Alle Offline setzen $wpdb->query( "UPDATE $table_name SET is_online = 0" ); $players = isset( $json->players ) && is_array( $json->players ) ? $json->players : array(); - // 2. Spieler durchgehen foreach ( $players as $p ) { $name = isset( $p->name ) ? sanitize_text_field( $p->name ) : ''; $prefix = isset( $p->prefix ) ? sanitize_text_field( $p->prefix ) : ''; - // UUID aus API übernehmen, falls vorhanden, sonst legacy-Hash + // Basis Stats + $playtime = isset( $p->playtime ) ? intval( $p->playtime ) : 0; + + // Economy + $balance = 0; + if ( isset( $p->economy ) && is_object( $p->economy ) && isset( $p->economy->balance ) ) { + $balance = floatval( $p->economy->balance ); + } + + // Kills/Deaths (Fallback wenn nicht in API) + $kills = isset( $p->kills ) ? intval( $p->kills ) : 0; + $deaths = isset( $p->deaths ) ? intval( $p->deaths ) : 0; + + // NEU: Weitere Stats + $joins = isset( $p->joins ) ? intval( $p->joins ) : 0; + + // UUID if ( isset( $p->uuid ) && !empty( $p->uuid ) ) { $uuid = sanitize_text_field( $p->uuid ); } else { $uuid = 'legacy-' . md5( strtolower( trim( $name ) ) ); } + + $is_bedrock = mcph_detect_bedrock_player( $p, $uuid, $name ); + + $bans = 0; + $mutes = 0; + $warns = 0; + + // Primär aus StatusAPI lesen (falls geliefert). + if ( isset( $p->punishments ) && is_object( $p->punishments ) ) { + $bans = isset( $p->punishments->bans ) ? intval( $p->punishments->bans ) : 0; + $mutes = isset( $p->punishments->mutes ) ? intval( $p->punishments->mutes ) : 0; + $warns = isset( $p->punishments->warns ) ? intval( $p->punishments->warns ) : 0; + } + + // Falls LiteBans konfiguriert ist, dortige Werte bevorzugen. + $litebans_punishments = mcph_get_litebans_punishments( $uuid, $name ); + if ( is_array( $litebans_punishments ) ) { + $bans = intval( $litebans_punishments['bans'] ?? 0 ); + $mutes = intval( $litebans_punishments['mutes'] ?? 0 ); + $warns = intval( $litebans_punishments['warns'] ?? 0 ); + } + if ( empty( $name ) ) continue; - // Prüfe ob Spieler bereits existiert (nach UUID) $exists = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table_name WHERE uuid = %s", $uuid ) ); - // Fallback: Falls nicht per UUID gefunden, prüfe nach Username + // Fallback alte Einträge if ( ! $exists ) { $old_entry = $wpdb->get_row( $wpdb->prepare( "SELECT id, uuid FROM $table_name WHERE username = %s", $name ) ); if ( $old_entry ) { - // FIX: Basis-Daten für Update (ohne Prefix) $update_data = array( 'uuid' => $uuid, 'last_seen' => current_time( 'mysql' ), - 'is_online' => 1 + 'is_online' => 1, + 'playtime_seconds' => $playtime, + 'balance' => $balance, + 'is_bedrock' => $is_bedrock, + 'joins' => $joins, + 'bans' => $bans, + 'mutes' => $mutes, + 'warns' => $warns ); - $update_format = array( '%s', '%s', '%d' ); + $update_format = array( '%s', '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d' ); - // FIX: Prefix nur aktualisieren, wenn API einen Wert liefert (nicht leer) if ( ! empty( $prefix ) ) { $update_data['prefix'] = $prefix; $update_format[] = '%s'; } - - $wpdb->update( - $table_name, - $update_data, - array( 'id' => $old_entry->id ), - $update_format, - array( '%d' ) - ); + $wpdb->update( $table_name, $update_data, array( 'id' => $old_entry->id ), $update_format, array( '%d' ) ); continue; } } - - // NEU: Playtime erhöhen (2 Minuten = 120 Sekunden pro Sync) - $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET playtime_seconds = playtime_seconds + 120 WHERE uuid = %s", $uuid ) ); - $now = current_time( 'mysql' ); + // Zeitkonvertierung + $last_seen_time = isset( $p->last_seen ) ? intval( $p->last_seen ) : time(); + $last_seen_mysql = date( 'Y-m-d H:i:s', $last_seen_time ); + + $first_seen_mysql = current_time('mysql'); + if ( isset( $p->first_seen ) && ! $exists ) { + $first_seen_mysql = date( 'Y-m-d H:i:s', intval( $p->first_seen ) ); + } if ( $exists ) { - // FIX: Basis-Daten für Update (ohne Prefix) $update_data = array( - 'username' => $name, - 'last_seen' => $now, - 'is_online' => 1 + 'username' => $name, + 'last_seen' => $last_seen_mysql, + 'is_online' => 1, + 'playtime_seconds' => $playtime, + 'balance' => $balance, + 'is_bedrock' => $is_bedrock, + 'joins' => $joins, + 'bans' => $bans, + 'mutes' => $mutes, + 'warns' => $warns ); - $update_format = array( '%s', '%s', '%d' ); + $update_format = array( '%s', '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d' ); - // FIX: Prefix nur aktualisieren, wenn API einen Wert liefert (nicht leer) if ( ! empty( $prefix ) ) { $update_data['prefix'] = $prefix; $update_format[] = '%s'; } - $wpdb->update( $table_name, $update_data, array( 'uuid' => $uuid ), $update_format, array( '%s' ) ); } else { - // Bei neuen Spielern fügen wir das Prefix ein (auch wenn es leer ist) - $wpdb->insert( $table_name, array( 'uuid' => $uuid, 'username' => $name, 'prefix' => $prefix, 'first_seen' => $now, 'last_seen' => $now, 'is_online' => 1, 'playtime_seconds' => 0 ), array( '%s', '%s', '%s', '%s', '%s', '%d', '%d' ) ); + $wpdb->insert( $table_name, array( + 'uuid' => $uuid, + 'username' => $name, + 'prefix' => $prefix, + 'first_seen' => $first_seen_mysql, + 'last_seen' => $last_seen_mysql, + 'is_online' => 1, + 'playtime_seconds' => $playtime, + 'balance' => $balance, + 'is_bedrock' => $is_bedrock, + 'joins' => $joins, + 'bans' => $bans, + 'mutes' => $mutes, + 'warns' => $warns + ), array( '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d' ) ); } } } @@ -332,7 +564,6 @@ function mcph_sync_from_statusapi() { */ 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'; @@ -341,46 +572,164 @@ function mcph_ajax_refresh() { } /** - * 4. HTML Generator (Strikte Reihenfolge) + * Holt StatusAPI-Spieler (optional mit Refresh). + */ +function mcph_fetch_statusapi_players( $force_refresh = false ) { + $cache_key = 'mcph_api_cache_data'; + if ( $force_refresh ) { + delete_transient( $cache_key ); + } + + $api_response = get_transient( $cache_key ); + if ( false !== $api_response ) { + return $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 ) ) { + return false; + } + + $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, 2 ); + return $json->players; + } + + return false; +} + +/** + * 3b. AJAX Handler: Spielerprofil (Erweitert) + */ +/** + * Forum-Banner: Holt Forumsdaten anhand der MC-UUID. + * Erfordert WP Business Forum Plugin (WBF_DB, WBF_Levels). + */ +function mcph_get_forum_banner( $uuid ) { + if ( ! class_exists( 'WBF_DB' ) || ! class_exists( 'WBF_Levels' ) ) return null; + global $wpdb; + $user_id = $wpdb->get_var( $wpdb->prepare( + "SELECT user_id FROM {$wpdb->prefix}forum_user_meta WHERE meta_key = 'mc_uuid' AND meta_value = %s LIMIT 1", + $uuid + ) ); + if ( ! $user_id ) return null; + $forum_user = WBF_DB::get_user( (int) $user_id ); + if ( ! $forum_user ) return null; + $post_count = intval( $forum_user->post_count ); + return array( + 'display_name' => $forum_user->display_name, + 'avatar_url' => $forum_user->avatar_url, + 'banner_url' => $forum_user->banner_url ?? '', + 'role' => $forum_user->role, + 'post_count' => $post_count, + 'registered' => $forum_user->registered, + 'last_active' => $forum_user->last_active, + 'bio' => $forum_user->bio, + 'level_badge_html' => WBF_Levels::is_enabled() ? WBF_Levels::badge( $post_count ) : '', + 'level_progress_html' => WBF_Levels::is_enabled() ? WBF_Levels::progress_bar( $post_count ) : '', + ); +} + +add_action( 'wp_ajax_mcph_player_profile', 'mcph_ajax_player_profile' ); +add_action( 'wp_ajax_nopriv_mcph_player_profile', 'mcph_ajax_player_profile' ); +function mcph_ajax_player_profile() { + $uuid = isset( $_POST['uuid'] ) ? sanitize_text_field( $_POST['uuid'] ) : ''; + if ( empty( $uuid ) ) { wp_send_json_error( 'Kein UUID angegeben.' ); return; } + + global $wpdb; + $table_name = $wpdb->prefix . 'mc_players'; + $player = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE uuid = %s", $uuid ) ); + + if ( ! $player ) { wp_send_json_error( 'Spieler nicht gefunden.' ); return; } + + // Live-Status prüfen + $is_online = (bool) $player->is_online; + $is_bedrock = (bool) $player->is_bedrock; + $balance_value = floatval( $player->balance ); + $api_response = mcph_fetch_statusapi_players( true ); + if ( is_array( $api_response ) ) { + $live_uuids = array(); + $matched_live_player = null; + foreach ( $api_response as $p ) { + $candidate_uuid = ''; + if ( isset( $p->uuid ) && ! empty( $p->uuid ) ) { + $candidate_uuid = sanitize_text_field( $p->uuid ); + } elseif ( isset( $p->name ) ) { + $candidate_uuid = 'legacy-' . md5( strtolower( trim( $p->name ) ) ); + } + + if ( ! empty( $candidate_uuid ) ) { + $live_uuids[ $candidate_uuid ] = true; + if ( $candidate_uuid === $player->uuid ) { + $matched_live_player = $p; + } + } + + if ( ! $matched_live_player && isset( $p->name ) && strtolower( trim( (string) $p->name ) ) === strtolower( trim( (string) $player->username ) ) ) { + $matched_live_player = $p; + } + } + $is_online = isset( $live_uuids[ $player->uuid ] ); + + // Live-Daten bevorzugen, falls verfügbar. + if ( $matched_live_player ) { + $is_bedrock = (bool) mcph_detect_bedrock_player( $matched_live_player, $player->uuid, $player->username ); + if ( isset( $matched_live_player->economy ) && is_object( $matched_live_player->economy ) && isset( $matched_live_player->economy->balance ) ) { + $balance_value = floatval( $matched_live_player->economy->balance ); + } + } + } + + $kd = ( $player->deaths > 0 ) ? round( $player->kills / $player->deaths, 2 ) : ( $player->kills > 0 ? $player->kills : 0 ); + + wp_send_json_success( array( + 'uuid' => $player->uuid, + 'username' => $player->username, + 'prefix' => $player->prefix ?? '', + 'first_seen' => $player->first_seen, + 'last_seen' => $player->last_seen, + 'is_online' => $is_online, + 'playtime_seconds' => intval( $player->playtime_seconds ), + 'kills' => intval( $player->kills ), + 'deaths' => intval( $player->deaths ), + 'kd' => $kd, + 'balance' => $balance_value, + 'is_bedrock' => $is_bedrock, + 'joins' => intval( $player->joins ), + 'bans' => intval( $player->bans ), + 'mutes' => intval( $player->mutes ), + 'warns' => intval( $player->warns ), + 'forum_banner' => mcph_get_forum_banner( $player->uuid ), + ) ); +} + +/** + * 4. HTML Generator (Karten-Ansicht) */ 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 Cache + $api_response = mcph_fetch_statusapi_players( $is_ajax ); $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 aus API übernehmen, falls vorhanden, sonst legacy-Hash - if ( isset( $p->uuid ) && !empty( $p->uuid ) ) { - $uuid = sanitize_text_field( $p->uuid ); - } else { - $uuid = 'legacy-' . md5( strtolower( trim( $p->name ) ) ); - } + $uuid = ( isset( $p->uuid ) && !empty( $p->uuid ) ) ? sanitize_text_field( $p->uuid ) : 'legacy-' . md5( strtolower( trim( $p->name ) ) ); $live_online_uuids[ $uuid ] = true; } } @@ -395,45 +744,25 @@ function mcph_generate_player_html( $limit = 500, $only_online = false, $is_ajax 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 ); - } + $is_online = $has_live_data ? isset( $live_online_uuids[ $row->uuid ] ) : ( $row->is_online == 1 ); + if ( $only_online && ! $is_online ) continue; + if ( $displayed_count >= $limit ) break; - 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-Logik: Moderne 3D-Köpfe (alle in gleiche Richtung) - $avatar = ''; + // Avatar if ( strpos( $username, '.' ) !== false || ( isset($row->uuid) && strpos($row->uuid, 'xuid') === 0 ) ) { - // Bedrock: mc-heads.net mit 3D Head - $avatar = isset($row->uuid) && !empty($row->uuid) - ? 'https://mc-heads.net/head/' . esc_attr($row->uuid) . '/100' - : 'https://mc-heads.net/head/' . $username . '/100'; + $avatar = isset($row->uuid) && !empty($row->uuid) ? 'https://mc-heads.net/head/' . esc_attr($row->uuid) . '/100' : 'https://mc-heads.net/head/' . $username . '/100'; } else { - // Java: mc-heads.net mit 3D Head (gleiche Richtung wie Bedrock) $avatar = 'https://mc-heads.net/head/' . $username . '/100'; } - $anim_style = ''; - if ( ! $is_ajax ) { - $anim_style = 'animation: fadeInUp 0.5s ease forwards; opacity: 0; animation-delay: ' . ( $displayed_count * 0.05 ) . 's;'; - } + $anim_style = ! $is_ajax ? 'animation: fadeInUp 0.5s ease forwards; opacity: 0; animation-delay: ' . ( $displayed_count * 0.05 ) . 's;' : ''; if ( $is_online ) { $status_html = 'Online'; @@ -443,33 +772,17 @@ function mcph_generate_player_html( $limit = 500, $only_online = false, $is_ajax $status_html = 'Zuletzt: ' . $date_str . ''; } - // STRUKTUR: Bild -> Prefix -> Name -> Spacer -> Status - echo '
'; + echo '
'; echo '' . $username . ''; - echo '
'; - - // 1. PREFIX - if ( ! empty( $row->prefix ) ) { - echo '' . $prefix . ''; - } - - // 2. NAME + if ( ! empty( $row->prefix ) ) echo '' . $prefix . ''; echo '' . $username . ''; - - // 3. SPACER (Nimmt den verbleibenden Platz ein) echo '
'; - - // 4. STATUS echo '
' . $status_html . '
'; - - echo '
'; - echo '
'; + echo '
'; } echo ''; - echo '
Aktualisiert: ' . current_time('H:i:s') . '
'; - return ob_get_clean(); } @@ -485,33 +798,16 @@ function mcph_settings_init() { register_setting( 'mcph_plugin_options', 'mcph_statusapi_url' ); } function mcph_options_page() { - // Duplikate bereinigen if ( isset( $_POST['mcph_cleanup_duplicates'] ) && check_admin_referer( 'mcph_cleanup_action' ) ) { - global $wpdb; - $table_name = $wpdb->prefix . 'mc_players'; - - // Finde Duplikate (gleicher Username, verschiedene UUIDs) - $duplicates = $wpdb->get_results( - "SELECT username, COUNT(*) as count, GROUP_CONCAT(id ORDER BY last_seen DESC) as ids - FROM $table_name - GROUP BY username - HAVING count > 1" - ); - + global $wpdb; $table_name = $wpdb->prefix . 'mc_players'; + $duplicates = $wpdb->get_results( "SELECT username, COUNT(*) as count, GROUP_CONCAT(id ORDER BY last_seen DESC) as ids FROM $table_name GROUP BY username HAVING count > 1" ); $cleaned = 0; foreach ( $duplicates as $dup ) { - $ids = explode( ',', $dup->ids ); - // Behalte den neuesten (ersten), lösche die anderen - array_shift( $ids ); - foreach ( $ids as $id ) { - $wpdb->delete( $table_name, array( 'id' => $id ), array( '%d' ) ); - $cleaned++; - } + $ids = explode( ',', $dup->ids ); array_shift( $ids ); + foreach ( $ids as $id ) { $wpdb->delete( $table_name, array( 'id' => $id ), array( '%d' ) ); $cleaned++; } } - echo '

' . $cleaned . ' Duplikate wurden entfernt.

'; } - if ( isset( $_POST['mcph_manual_sync'] ) && check_admin_referer( 'mcph_manual_sync_action' ) ) { mcph_sync_from_statusapi(); delete_transient( 'mcph_api_cache_data' ); @@ -533,17 +829,13 @@ function mcph_options_page() { -

Duplikate bereinigen

-
- +

Entfernt doppelte Spieler-Einträge aus der Datenbank.

-

Manueller Sync

-
- +
@@ -551,467 +843,470 @@ function mcph_options_page() { } /** - * 6. Shortcode + CSS + * 6. Shortcode + CSS + JS (Profil erweitert) */ add_shortcode( 'mc_player_history', 'mcph_shortcode' ); - function mcph_shortcode( $atts ) { - $atts = shortcode_atts( array( - 'limit' => 500, - 'interval' => 2, - 'only_online' => 'false' - ), $atts ); - + $atts = shortcode_atts( array( 'limit' => 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 '
'; - + $profile_id = 'mc-profile-' . substr( $container_id, -6 ); + 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; + @import url("https://fonts.googleapis.com/css2?family=Rajdhani:wght@500;600;700&family=Barlow:wght@400;500;600&display=swap"); + + .mc-player-list, .mc-profile-panel { + --mc-bg: #1f232a; + --mc-surface: #2a2f37; + --mc-surface-2: #242a33; + --mc-ink: #e6f1ff; + --mc-muted: #9aa7b2; + --mc-border: #3a4450; + --mc-accent: #18c7ff; + --mc-accent-2: #00e5ff; + --mc-glow: 0 0 24px rgba(24, 199, 255, 0.28); + --mc-radius: 16px; } - @keyframes fadeInUp { - from { opacity: 0; transform: translateY(10px); } - to { opacity: 1; transform: translateY(0); } - } + .mc-player-list { width: 100%; font-family: "Barlow", "Segoe UI", Arial, sans-serif; } + .mc-grid { display: grid; gap: 18px; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); justify-content: center; align-items: stretch; } + @keyframes fadeInUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } } /* KARTE */ .mc-player-card { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; - padding: 20px 10px; - background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%); - border: 1px solid #e0e0e0; - border-radius: 12px; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - cursor: default; position: relative; - box-shadow: 0 2px 8px rgba(0,0,0,0.08); + display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 18px 12px; + background: linear-gradient(180deg, #2a2f37 0%, #242a33 100%); + border: 1px solid var(--mc-border); + border-radius: var(--mc-radius); + box-shadow: 0 10px 24px rgba(12, 16, 22, 0.35); + transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease; + cursor: pointer; overflow: hidden; } - - .mc-player-card:hover { - transform: translateY(-8px) scale(1.02); - box-shadow: 0 12px 24px rgba(0,0,0,0.15); - border-color: #667eea; - background: linear-gradient(145deg, #ffffff 0%, #f0f2ff 100%); + .mc-player-card::before { + content: ""; + position: absolute; inset: 0; + background: radial-gradient(220px 80px at 20% 0%, rgba(24,199,255,0.15), transparent 70%); + opacity: 0; transition: opacity 0.25s ease; } - + .mc-player-card:hover { transform: translateY(-6px); border-color: rgba(24,199,255,0.45); box-shadow: 0 16px 34px rgba(12,16,22,0.45); } + .mc-player-card:hover::before { opacity: 1; } .mc-avatar { - border-radius: 8px; - border: 3px solid #e0e0e0; - background: transparent; - width: 80px; - height: 80px; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - flex-shrink: 0; - box-shadow: 0 4px 12px rgba(0,0,0,0.15); - object-fit: contain; - padding: 0; + width: 82px; height: 82px; border-radius: 12px; background: #11151b; padding: 4px; + border: 1px solid rgba(24,199,255,0.4); + box-shadow: 0 8px 18px rgba(0, 0, 0, 0.35); + transition: transform 0.25s ease; object-fit: contain; } + .mc-player-card:hover .mc-avatar { transform: translateY(-4px) scale(1.05); } + .mc-info-stack { display: flex; flex-direction: column; align-items: center; width: 100%; flex-grow: 1; position: relative; padding-bottom: 34px; justify-content: flex-start; z-index: 1; } + .mc-prefix { font-size: 0.88em; display: block; width: 100%; text-align: center; margin-bottom: 4px; line-height: 1.2; } + .mc-name { font-weight: 700; font-size: 1.05em; color: var(--mc-ink); word-break: break-word; display: block; width: 100%; text-align: center; } + .mc-spacer { flex-grow: 1; } + .mc-status-line { position: absolute; bottom: 0; left: 0; width: 100%; text-align: center; margin: 0; padding-bottom: 5px; } + .mc-status { display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; border-radius: 999px; font-size: 0.78em; font-weight: 700; letter-spacing: 0.2px; background: #1f2937; color: #e2e8f0; border: 1px solid #334155; } + .mc-online { background: rgba(24,199,255,0.15); color: #baf1ff; border: 1px solid rgba(24,199,255,0.5); box-shadow: var(--mc-glow); } + .mc-offline { background: rgba(248,113,113,0.14); color: #fecaca; border: 1px solid rgba(248,113,113,0.4); } + .mc-update-time { font-size: 0.82em; color: var(--mc-muted); text-align: center; margin-top: 20px; font-style: italic; padding: 8px; border: 1px dashed #3a4450; border-radius: 12px; } - .mc-player-card:hover .mc-avatar { - border-color: #667eea; - transform: scale(1.15) rotateY(10deg); - box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4); - } + @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: 12px; } .mc-avatar { width: 70px; height: 70px; } .mc-name { font-size: 0.98em; } .mc-player-card { padding: 14px 8px; } } - /* INFOS BEREICH */ - .mc-info-stack { - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - flex-grow: 1; - position: relative; - padding-bottom: 35px; - justify-content: flex-start; + /* ===== SPIELERPROFIL ===== */ + .mc-profile-panel { width: 100%; font-family: "Barlow", "Segoe UI", Arial, sans-serif; } + .mc-profile-inner { + background: #2a2f37; + border: 1px solid #3a4450; + border-radius: 18px; padding: 26px; + box-shadow: 0 18px 40px rgba(10, 12, 16, 0.55); } + .mc-back-btn { + display: inline-flex; align-items: center; gap: 6px; background: #1f232a; + border: 1.5px solid var(--mc-accent); color: #baf1ff; font-size: 0.9em; font-weight: 700; + padding: 6px 14px; border-radius: 999px; cursor: pointer; margin-bottom: 20px; transition: all 0.2s ease; + box-shadow: var(--mc-glow); + } + .mc-back-btn:hover { background: rgba(24,199,255,0.18); color: #e6f9ff; transform: translateX(-2px); } + .mc-profile-header { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; position: relative; overflow: hidden; border-radius: 14px; } + .mc-profile-header > * { position: relative; z-index: 1; } + .mc-profile-header::before { + content: ""; position: absolute; inset: 0; + background: linear-gradient(120deg, rgba(24,199,255,0.15), rgba(0,0,0,0.25)); + opacity: 0.7; z-index: 0; + } + .mc-profile-header-banner { background-size: cover; background-position: center; padding: 20px 22px; border-radius: 14px; border: 1px solid rgba(24,199,255,0.3); } + .mc-profile-header-overlay { position: absolute; inset: 0; background: linear-gradient(90deg, rgba(8,12,18,0.45), rgba(8,12,18,0.7)); z-index: 0; } + .mc-profile-header-left { flex: 1; display: flex; flex-direction: column; gap: 6px; } + .mc-profile-prefix { font-size: 1em; font-weight: 600; line-height: 1.2; } + .mc-profile-name { font-size: 2.05em; font-weight: 700; color: #e6f1ff; line-height: 1.05; letter-spacing: 0.2px; font-family: "Rajdhani", "Segoe UI", Arial, sans-serif; } + .mc-profile-uuid { font-size: 0.72em; color: var(--mc-muted); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; word-break: break-all; margin-top: 2px; } + .mc-profile-status-row { margin-top: 8px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } + .mc-platform-badge { font-size: 0.85em; background: rgba(24,199,255,0.12); color: #baf1ff; padding: 4px 10px; border-radius: 999px; font-weight: 700; border: 1px solid rgba(24,199,255,0.3); display: inline-flex; align-items: center; gap: 4px; } + .mc-profile-header-right { flex-shrink: 0; } + .mc-profile-avatar { width: 110px; height: auto; display: block; filter: drop-shadow(0 12px 22px rgba(0, 0, 0, 0.5)); transition: transform 0.3s ease; transform: scaleX(-1); } + .mc-profile-avatar:hover { transform: scale(1.04); } + .mc-profile-divider { height: 1px; background: linear-gradient(90deg, transparent, rgba(24,199,255,0.35), transparent); margin: 22px 0; } - /* 1. PREFIX */ - .mc-prefix { - font-size: 0.9em; - display: block; - width: 100%; - text-align: center; - margin-bottom: 4px; - line-height: 1.2; - text-shadow: 0 1px 2px rgba(0,0,0,0.1); + /* Grid: 4 Spalten fuer Desktop, 2 fuer Mobile */ + .mc-stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; } + .mc-stat-card { + background: #232832; border: 1px solid #34404c; border-radius: 12px; padding: 14px 10px; text-align: center; + box-shadow: 0 10px 22px rgba(8, 10, 14, 0.4); transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; } + .mc-stat-card:hover { transform: translateY(-3px); border-color: rgba(24,199,255,0.45); box-shadow: 0 16px 28px rgba(10, 12, 16, 0.55); } + .mc-stat-highlight { + background: linear-gradient(135deg, rgba(24,199,255,0.35) 0%, rgba(0,229,255,0.12) 100%); + border-color: rgba(24,199,255,0.55); + } + .mc-stat-highlight .mc-stat-icon, .mc-stat-highlight .mc-stat-value, .mc-stat-highlight .mc-stat-label { color: #e6f9ff !important; } + .mc-stat-icon { font-size: 1.4em; line-height: 1; margin-bottom: 8px; } + .mc-stat-value { font-size: 1.12em; font-weight: 700; color: var(--mc-ink); line-height: 1.2; word-break: break-word; } + .mc-stat-label { font-size: 0.7em; font-weight: 700; color: var(--mc-muted); margin-top: 4px; text-transform: uppercase; letter-spacing: 0.6px; } - /* 2. NAME */ - .mc-name { - font-weight: bold; - font-size: 1.1em; - color: #2d3748; - word-break: break-word; - display: block; - width: 100%; - text-align: center; - text-shadow: 0 1px 2px rgba(0,0,0,0.05); - } - - /* 3. SPACER */ - .mc-spacer { - flex-grow: 1; - } - - /* 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: 5px 14px; - border-radius: 20px; - font-size: 0.8em; - font-weight: 600; - transition: all 0.3s ease; - } - - .mc-online { - background: linear-gradient(135deg, #d4f8e8 0%, #b8f2d9 100%); - color: #0a7340; - border: 1px solid #81e6b8; - box-shadow: 0 2px 4px rgba(10, 115, 64, 0.1); - } - - .mc-offline { - background: linear-gradient(135deg, #fff5f5 0%, #ffe5e5 100%); - color: #c53030; - border: 1px solid #fbb6b6; - box-shadow: 0 2px 4px rgba(197, 48, 48, 0.1); - } - - .mc-player-card:hover .mc-status { - transform: scale(1.05); - } - - .mc-update-time { - font-size: 0.85em; - color: #718096; - text-align: center; - margin-top: 20px; - font-style: italic; - padding: 8px; - background: rgba(102, 126, 234, 0.05); - border-radius: 6px; - display: inline-block; - width: 100%; - } - - @media (min-width: 1200px) { - .mc-grid { grid-template-columns: repeat(6, 1fr); } - } + .mc-profile-loading { display: flex; align-items: center; justify-content: center; gap: 12px; padding: 60px 20px; color: var(--mc-muted); font-size: 0.95em; } + .mc-spinner { width: 22px; height: 22px; border: 3px solid #2e3642; border-top-color: var(--mc-accent); border-radius: 50%; animation: mc-spin 0.7s linear infinite; } + @keyframes mc-spin { to { transform: rotate(360deg); } } @media (max-width: 600px) { - .mc-grid { - grid-template-columns: repeat(2, 1fr); - gap: 12px; - } - .mc-avatar { - width: 70px; - height: 70px; - } - .mc-name { - font-size: 1em; - } - .mc-player-card { - padding: 15px 8px; - } + .mc-profile-inner { padding: 18px 14px; } + .mc-profile-name { font-size: 1.6em; } + .mc-profile-avatar { width: 82px; } + .mc-stats-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; } } - '; + @media (max-width: 600px) { .mc-profile-header-banner { padding: 12px; } } + '; return ob_get_clean(); } -/* ================= SIDEBAR WIDGET: TOP SPIELZEIT (1/2 Spalten + sichere Inline-Farben) ================= */ +/* ================= SIDEBAR WIDGET ================= */ class MC_Top_Playtime_Widget extends WP_Widget { - public function __construct() { - parent::__construct( - 'mc_top_playtime_widget', - 'MC Top Spieler', - array( 'description' => 'Zeigt die 6 Spieler mit der meisten Spielzeit an. Wahl: 1 oder 2 Spieler pro Zeile.' ) - ); + parent::__construct( 'mc_top_playtime_widget', 'MC Top Spieler', array( 'description' => 'Zeigt die 6 Spieler mit der meisten Spielzeit an.' ) ); } - - // Zeitformatierung public static function format_duration( $seconds ) { - if ( $seconds < 60 ) { - return $seconds . 's'; - } elseif ( $seconds < 3600 ) { - $minutes = floor( $seconds / 60 ); - return $minutes . 'm'; - } elseif ( $seconds < 86400 ) { - $hours = floor( $seconds / 3600 ); - $minutes = floor( ( $seconds % 3600 ) / 60 ); - return $hours . 'h ' . $minutes . 'm'; - } else { - $days = floor( $seconds / 86400 ); - $hours = floor( ( $seconds % 86400 ) / 3600 ); - return $days . 'd ' . $hours . 'h'; - } + if ( $seconds < 60 ) return $seconds . 's'; + elseif ( $seconds < 3600 ) return floor( $seconds / 60 ) . 'm'; + elseif ( $seconds < 86400 ) { $h = floor( $seconds / 3600 ); $m = floor( ( $seconds % 3600 ) / 60 ); return $h . 'h ' . $m . 'm'; } + else { $d = floor( $seconds / 86400 ); $h = floor( ( $seconds % 86400 ) / 3600 ); return $d . 'd ' . $h . 'h'; } } - - // Konvertiert Minecraft-Farbcodes (&0-&f, &r) in inline Text private static function parse_minecraft_color_codes_inline( $text ) { - if ( $text === null || $text === '' ) { - return ''; - } - - // Mapping Minecraft color codes -> HEX - $map = array( - '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', - ); - - // Ersetze alternative §-Codes durch & falls nötig + if ( $text === null || $text === '' ) return ''; + $map = array('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'); $text = str_replace('§', '&', $text); - - // Teile so, dass die Codes erhalten bleiben $parts = preg_split('/(&[0-9a-frk-or])/i', $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); - - $out = ''; - $open = false; - + $out = ''; $open = false; foreach ( $parts as $part ) { - // Ist es ein Code? if ( preg_match('/^&([0-9a-frk-orA-FK-OR])$/', $part, $m) ) { $code = strtolower( $m[1] ); - - // Reset - if ( $code === 'r' ) { - if ( $open ) { - $out .= ''; - $open = false; - } - continue; - } - - // Farbe - if ( isset( $map[$code] ) ) { - if ( $open ) { - $out .= ''; - $open = false; - } - $hex = $map[$code]; - $out .= ''; - $open = true; - continue; - } - - // Formatcodes: &l &o &n &m &k -> wir implementieren nur fett (&l) &o kursiv optional - if ( $code === 'l' ) { // fett - $out .= ''; - continue; - } - if ( $code === 'o' ) { // italic - $out .= ''; - continue; - } - if ( in_array( $code, array('m','n','k'), true) ) { - // durchgestrichen / underline / obfuscated - ignorieren oder erweitern falls gewünscht - continue; - } - - continue; + if ( $code === 'r' ) { if ( $open ) { $out .= ''; $open = false; } continue; } + if ( isset( $map[$code] ) ) { if ( $open ) { $out .= ''; $open = false; } $out .= ''; $open = true; continue; } + if ( $code === 'l' ) { $out .= ''; continue; } } - - // Normaler Text: escapen - $escaped = esc_html( $part ); - $out .= $escaped; + $out .= esc_html( $part ); } - - // Schließe eventuell offene Tags: zuerst format, dann color spans - // (Wir schlossen format tags nicht oben -> vereinfachung: entfernen ungeschlossene format-tags) - // Schließe color span falls offen - if ( $open ) { - $out .= ''; - $open = false; - } - - // Hinweis: Wir benutzten esc_html pro Textteil; das ist sicher. + if ( $open ) $out .= ''; return $out; } - - // Backend-Form public function form( $instance ) { $title = isset( $instance['title'] ) ? $instance['title'] : 'Top 6 Spielzeit'; $columns = isset( $instance['columns'] ) ? intval( $instance['columns'] ) : 1; ?>

- +

-

-

prefix . 'mc_players'; - - // Top 6 Spieler abfragen - $top_players = $wpdb->get_results( - "SELECT * FROM $table_name WHERE playtime_seconds > 0 ORDER BY playtime_seconds DESC LIMIT 6" - ); - + if ( ! empty( $title ) ) { echo $args['before_title'] . apply_filters( 'widget_title', $title ) . $args['after_title']; } + global $wpdb; $table_name = $wpdb->prefix . 'mc_players'; + $top_players = $wpdb->get_results( "SELECT * FROM $table_name WHERE playtime_seconds > 0 ORDER BY playtime_seconds DESC LIMIT 6" ); $container_class = 'mc-top-list' . ( $columns === 2 ? ' mc-two-columns' : ' mc-one-column' ); ?>

Noch keine Daten.

- username ); $time_string = self::format_duration( $row->playtime_seconds ); - - // Rohes Prefix aus DB (z.B. "&7Member") $raw_prefix = isset( $row->prefix ) ? $row->prefix : ''; $prefix_html = self::parse_minecraft_color_codes_inline( $raw_prefix ); - - // Avatar URL (UUID bevorzugt) - if ( isset( $row->uuid ) && ! empty( $row->uuid ) ) { - $avatar = 'https://mc-heads.net/head/' . esc_attr( $row->uuid ) . '/50'; - } else { - $avatar = 'https://mc-heads.net/head/' . rawurlencode( $username ) . '/50'; - } + $avatar = isset( $row->uuid ) && ! empty( $row->uuid ) ? 'https://mc-heads.net/head/' . esc_attr( $row->uuid ) . '/50' : 'https://mc-heads.net/head/' . rawurlencode( $username ) . '/50'; ?> -
- <?php echo $username; ?> -
- -
- +
-
-
<?php echo $username; ?> - - -
- - +
- -