10] ); if ( ! is_wp_error($response) && 200 === wp_remote_retrieve_response_code($response) ) { $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); 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 ); } } else { 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; $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' ); ?>

MC Player History – Update verfügbar

Aktuelle Version:
Neueste Version:

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 */ 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; } /** * 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; $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, 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 ); } register_deactivation_hook( __FILE__, 'mcph_deactivate' ); function mcph_deactivate() { wp_clear_scheduled_hook( 'mcph_sync_event' ); } /** * Erweiterung der Datenbank beim Laden (falls Update) */ add_action( 'init', 'mcph_add_columns_v130' ); function mcph_add_columns_v130() { global $wpdb; $table_name = $wpdb->prefix . 'mc_players'; // 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" ); } } /** * 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; } // Alle Offline setzen $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 ) : ''; // 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; $exists = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table_name WHERE uuid = %s", $uuid ) ); // 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 ) { $update_data = array( 'uuid' => $uuid, 'last_seen' => current_time( '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', '%d', '%s', '%d', '%d', '%d', '%d', '%d' ); 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' ) ); continue; } } // 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 ) { $update_data = array( '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', '%d', '%s', '%d', '%d', '%d', '%d', '%d' ); if ( ! empty( $prefix ) ) { $update_data['prefix'] = $prefix; $update_format[] = '%s'; } $wpdb->update( $table_name, $update_data, array( 'uuid' => $uuid ), $update_format, array( '%s' ) ); } else { $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' ) ); } } } /** * 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 ) ); } /** * 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 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 = ( isset( $p->uuid ) && !empty( $p->uuid ) ) ? sanitize_text_field( $p->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 = $has_live_data ? isset( $live_online_uuids[ $row->uuid ] ) : ( $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 if ( strpos( $username, '.' ) !== false || ( isset($row->uuid) && strpos($row->uuid, 'xuid') === 0 ) ) { $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 { $avatar = 'https://mc-heads.net/head/' . $username . '/100'; } $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'; } 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 . ''; } echo '
'; echo '' . $username . ''; echo '
'; if ( ! empty( $row->prefix ) ) echo '' . $prefix . ''; echo '' . $username . ''; echo '
'; echo '
' . $status_html . '
'; 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_cleanup_duplicates'] ) && check_admin_referer( 'mcph_cleanup_action' ) ) { 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 ); 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' ); echo '

Manueller Sync ausgeführt.

'; } ?>

MC Player History Einstellungen

StatusAPI URL

Bitte unbedingt mit http:// angeben.

Duplikate bereinigen

Entfernt doppelte Spieler-Einträge aus der Datenbank.

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 '
'; $profile_id = 'mc-profile-' . substr( $container_id, -6 ); echo ''; ?> @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; } .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 { position: relative; 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::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 { 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; } @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; } } /* ===== 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; } /* 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; } .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-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 ================= */ 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.' ) ); } public static function format_duration( $seconds ) { 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'; } } private static function parse_minecraft_color_codes_inline( $text ) { 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); $parts = preg_split('/(&[0-9a-frk-or])/i', $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $out = ''; $open = false; foreach ( $parts as $part ) { if ( preg_match('/^&([0-9a-frk-orA-FK-OR])$/', $part, $m) ) { $code = strtolower( $m[1] ); 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; } } $out .= esc_html( $part ); } if ( $open ) $out .= ''; return $out; } 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_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 ); $raw_prefix = isset( $row->prefix ) ? $row->prefix : ''; $prefix_html = self::parse_minecraft_color_codes_inline( $raw_prefix ); $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; ?>