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' );
?>
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 '

';
-
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
-
-
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';
?>
-
-
; ?>)
-
-
-
; ?>)
-
-
-
-
-
+
-
-