diff --git a/api/server.php b/api/server.php index cfe1072d..9ec213d8 100644 --- a/api/server.php +++ b/api/server.php @@ -159,6 +159,7 @@ $output->writeAttribute("port", $request['port']); $output->writeAttribute("aes-key", $request['aes_key']); $output->writeAttribute("aes-iv", $request['aes_iv']); + $output->writeAttribute("country-code", $request['country_code']); $output->endElement(); } $output->endElement(); diff --git a/api/user.php b/api/user.php index d3f2f488..52b9c3c9 100644 --- a/api/user.php +++ b/api/user.php @@ -56,6 +56,7 @@ $session = ClientSession::create($username, $password, $save_session === "true"); // Clear previous joined server if any $session->clearUserJoinedServer(); + $session->updateUserGeolocation(); $achievements_string = $session->getAchievements(); $output->startElement('connect'); @@ -87,6 +88,7 @@ // Clear previous joined server if any $session->clearUserJoinedServer(); $session->setOnline(); + $session->updateUserGeolocation(); User::updateLoginTime($session->getUser()->getId()); $output->startElement('saved-session'); diff --git a/include/ClientSession.class.php b/include/ClientSession.class.php index 78542c99..5896dd46 100644 --- a/include/ClientSession.class.php +++ b/include/ClientSession.class.php @@ -346,10 +346,12 @@ public function getServerConnectionRequests($ip, int $port, int $current_players // Get all connection requests 45 seconds before $timeout = time() - 45; $connection_requests = DBConnection::get()->query( - "SELECT `user_id`, `server_id`, `ip`, `port`, `aes_key`, `aes_iv`, `username` + "SELECT `user_id`, `server_id`, `ip`, `port`, `aes_key`, `aes_iv`, `username`, `country_code` FROM `{DB_VERSION}_server_conn` INNER JOIN `{DB_VERSION}_users` ON `{DB_VERSION}_server_conn`.user_id = `{DB_VERSION}_users`.id + INNER JOIN `{DB_VERSION}_client_sessions` + ON `{DB_VERSION}_server_conn`.user_id = `{DB_VERSION}_client_sessions`.uid WHERE `server_id` = :server_id AND `connected_since` > :timeout", DBConnection::FETCH_ALL, [ @@ -854,4 +856,39 @@ public function updateServerConfig($ip, int $port, int $new_difficulty, int $new throw new ServerException(_h("Failed to update server config.")); } } + /** + * Update the geolocation of user based on his IP, called when the user login. + */ + public function updateUserGeolocation() + { + $user_geolocation = Util::getIPGeolocationFromString(Util::getClientIp()); + try + { + DBConnection::get()->query( + "UPDATE `{DB_VERSION}_client_sessions` + SET `latitude` = :latitude, `longitude` = :longitude, `country_code` = :country_code + WHERE `uid`= :uid AND `cid` = :cid", + DBConnection::NOTHING, + [ + ':latitude' => $user_geolocation[0], + ':longitude' => $user_geolocation[1], + ':country_code' => $user_geolocation[2], + ':uid' => $this->user->getId(), + ':cid' => $this->getSessionID() + ], + [ + ':latitude' => DBConnection::PARAM_STR, + ':longitude' => DBConnection::PARAM_STR, + ':country_code' => DBConnection::PARAM_STR, + ':uid' => DBConnection::PARAM_STR, + ':cid' => DBConnection::PARAM_STR + ] + ); + } + catch (DBException $e) + { + throw new ClientSessionException($e->getMessage()); + } + } + } diff --git a/include/Server.class.php b/include/Server.class.php index 36c0293d..85d7c408 100644 --- a/include/Server.class.php +++ b/include/Server.class.php @@ -108,6 +108,12 @@ class Server implements IAsXML */ private $longitude; + /** + * 2-letter country code of server location + * @var string + */ + private $country_code; + /** * Current track playing in server * @var string @@ -143,6 +149,7 @@ private function __construct(array $data, array $player_info = []) $this->game_started = (int)$data["game_started"]; $this->latitude = $data["latitude"]; $this->longitude = $data["longitude"]; + $this->country_code = $data["country_code"]; $this->current_track = $data["current_track"]; $this->players_info = $player_info; } @@ -179,73 +186,6 @@ public function getMaxPlayers() return $this->max_players; } - /** - * Get the latitude and longitude of an IP - * @return array of latitude and longitude. - * If location does not exist it returns coordinates [0, 0] (null island) - * - * @param int $ip - */ - public static function getIPCoordinates($ip) - { - try - { - $result = DBConnection::get()->query( - "SELECT * FROM `{DB_VERSION}_ipv4_mapping` - WHERE `ip_start` <= :ip AND `ip_end` >= :ip ORDER BY `ip_start` DESC LIMIT 1;", - DBConnection::FETCH_FIRST, - [':ip' => $ip], - [":ip" => DBConnection::PARAM_INT] - ); - } - catch (DBException $e) - { - Debug::addException($e); - return [0.0, 0.0]; - } - - if (!$result) - { - return [0.0, 0.0]; - } - - return [$result["latitude"], $result["longitude"]]; - } - - /** - * Get the latitude and longitude of an IP represented as a string - * @return float[] of latitude and longitude in string. - * If location does not exist it returns coordinates [0, 0] (null island) - * - * @param string $ip_string eg: 127.0.0.1 - */ - public static function getIPCoordinatesFromString($ip_string) - { - try - { - $result = DBConnection::get()->query( - "SELECT * FROM `{DB_VERSION}_ipv4_mapping` - WHERE `ip_start` <= INET_ATON(:ip) AND `ip_end` >= INET_ATON(:ip) - ORDER BY `ip_start` DESC LIMIT 1;", - DBConnection::FETCH_FIRST, - [':ip' => $ip_string], - [":ip" => DBConnection::PARAM_STR] - ); - } - catch (DBException $e) - { - Debug::addException($e); - return [0.0, 0.0]; - } - - if (!$result) - { - return [0.0, 0.0]; - } - - return [$result["latitude"], $result["longitude"]]; - } - /** * Get server as xml output * @@ -253,8 +193,8 @@ public static function getIPCoordinatesFromString($ip_string) */ public function asXML() { - $client_coordinates = Server::getIPCoordinatesFromString(Util::getClientIp()); - return $this->asXMLFromClientLocation($client_coordinates[0], $client_coordinates[1]); + $client_geolocation = Util::getIPGeolocationFromString(Util::getClientIp()); + return $this->asXMLFromClientLocation($client_geolocation[0], $client_geolocation[1]); } /** @@ -283,6 +223,7 @@ public function asXMLFromClientLocation($client_latitude, $client_longitude) $server_xml->writeAttribute("password", $this->password); $server_xml->writeAttribute("version", $this->version); $server_xml->writeAttribute("game_started", $this->game_started); + $server_xml->writeAttribute("country_code", $this->country_code); $server_xml->writeAttribute("current_track", $this->current_track); $server_xml->writeAttribute( "distance", @@ -300,6 +241,7 @@ public function asXMLFromClientLocation($client_latitude, $client_longitude) $server_xml->writeAttribute("username", $player["username"]); $time_played = (float)(time() - (int)$player["connected_since"]) / 60.0; $server_xml->writeAttribute("time-played", $time_played); + $server_xml->writeAttribute("country-code", $player["player_country_code"]); if ($player["rank"] !== null) { $server_xml->writeAttribute("rank", $player["rank"]); @@ -375,14 +317,14 @@ public static function create( throw new ServerException(_('Specified server already exists.')); } - $server_coordinates = Server::getIPCoordinates($ip); + $server_geolocation = Util::getIPGeolocation($ip); $result = DBConnection::get()->query( "INSERT INTO `{DB_VERSION}_servers` (host_id, name, last_poll_time, ip, port, private_port, max_players, - difficulty, game_mode, password, version, latitude, longitude) + difficulty, game_mode, password, version, latitude, longitude, country_code) VALUES (:host_id, :name, :last_poll_time, :ip, :port, :private_port, :max_players, :difficulty, :game_mode, - :password, :version, :latitude, :longitude)", + :password, :version, :latitude, :longitude, :country_code)", DBConnection::ROW_COUNT, [ ':host_id' => $user_id, @@ -397,8 +339,9 @@ public static function create( ':game_mode' => $game_mode, ':password' => $password, ':version' => $version, - ':latitude' => $server_coordinates[0], - ':longitude' => $server_coordinates[1] + ':latitude' => $server_geolocation[0], + ':longitude' => $server_geolocation[1], + ':country_code' => $server_geolocation[2] ], [ ':host_id' => DBConnection::PARAM_INT, @@ -413,7 +356,8 @@ public static function create( ':password' => DBConnection::PARAM_INT, ':version' => DBConnection::PARAM_INT, ':latitude' => DBConnection::PARAM_STR, - ':longitude' => DBConnection::PARAM_STR + ':longitude' => DBConnection::PARAM_STR, + ':country_code' => DBConnection::PARAM_STR ] ); } @@ -527,9 +471,11 @@ public static function getAllServerConnectionsWithUsers(): array `{DB_VERSION}_servers`.max_players, `{DB_VERSION}_servers`.difficulty, `{DB_VERSION}_servers`.game_mode, `{DB_VERSION}_servers`.current_players, `{DB_VERSION}_servers`.password, `{DB_VERSION}_servers`.version, `{DB_VERSION}_servers`.game_started, `{DB_VERSION}_servers`.latitude, `{DB_VERSION}_servers`.longitude, + `{DB_VERSION}_servers`.country_code, `{DB_VERSION}_servers`.current_track, `{DB_VERSION}_server_conn`.user_id, `{DB_VERSION}_server_conn`.connected_since, `{DB_VERSION}_users`.username, rank, scores, max_scores, num_races_done, - UNIX_TIMESTAMP(`{DB_VERSION}_client_sessions`.`last-online`) AS online_since + UNIX_TIMESTAMP(`{DB_VERSION}_client_sessions`.`last-online`) AS online_since, + `{DB_VERSION}_client_sessions`.country_code AS player_country_code FROM `{DB_VERSION}_servers` LEFT JOIN `{DB_VERSION}_server_conn` ON `{DB_VERSION}_servers`.id = `{DB_VERSION}_server_conn`.server_id LEFT JOIN `{DB_VERSION}_users` ON `{DB_VERSION}_users`.id = `{DB_VERSION}_server_conn`.user_id @@ -566,7 +512,7 @@ public static function getAllServerConnectionsWithUsers(): array public static function getServersAsXML(int $skip_non_polled_clients_seconds = 180): string { $servers_with_users = static::getAllServerConnectionsWithUsers(); - $client_coordinates = static::getIPCoordinatesFromString(Util::getClientIp()); + $client_geolocation = Util::getIPGeolocationFromString(Util::getClientIp()); $servers = []; $users = [[]]; @@ -602,7 +548,7 @@ public static function getServersAsXML(int $skip_non_polled_clients_seconds = 18 { $server = new self($server_result, $users[$user_index]); $user_index++; - $partial_output->insert($server->asXMLFromClientLocation($client_coordinates[0], $client_coordinates[1])); + $partial_output->insert($server->asXMLFromClientLocation($client_geolocation[0], $client_geolocation[1])); } $partial_output->endElement(); diff --git a/include/Util.class.php b/include/Util.class.php index a2e1d45f..4defe8eb 100644 --- a/include/Util.class.php +++ b/include/Util.class.php @@ -821,4 +821,72 @@ public static function getDistance( return $angle * $earth_radius; } + + /** + * Get the latitude, longitude and 2-letter country code of an IP + * @return array of latitude, longitude and 2-letter country code. + * If location does not exist it returns coordinates [0, 0, ""] (null island) + * + * @param int $ip + */ + public static function getIPGeolocation($ip) + { + try + { + $result = DBConnection::get()->query( + "SELECT * FROM `{DB_VERSION}_ipv4_mapping` + WHERE `ip_start` <= :ip AND `ip_end` >= :ip ORDER BY `ip_start` DESC LIMIT 1;", + DBConnection::FETCH_FIRST, + [':ip' => $ip], + [":ip" => DBConnection::PARAM_INT] + ); + } + catch (DBException $e) + { + Debug::addException($e); + return [0.0, 0.0, ""]; + } + + if (!$result) + { + return [0.0, 0.0, ""]; + } + + return [$result["latitude"], $result["longitude"], $result["country_code"]]; + } + + /** + * Get the latitude, longitude and 2-letter country code of an IP represented as a string + * @return array of latitude, longitude and 2-letter country code. + * If location does not exist it returns coordinates [0, 0, ""] (null island) + * + * @param string $ip_string eg: 127.0.0.1 + */ + public static function getIPGeolocationFromString($ip_string) + { + try + { + $result = DBConnection::get()->query( + "SELECT * FROM `{DB_VERSION}_ipv4_mapping` + WHERE `ip_start` <= INET_ATON(:ip) AND `ip_end` >= INET_ATON(:ip) + ORDER BY `ip_start` DESC LIMIT 1;", + DBConnection::FETCH_FIRST, + [':ip' => $ip_string], + [":ip" => DBConnection::PARAM_STR] + ); + } + catch (DBException $e) + { + Debug::addException($e); + return [0.0, 0.0, ""]; + } + + if (!$result) + { + return [0.0, 0.0, ""]; + } + + return [$result["latitude"], $result["longitude"], $result["country_code"]]; + } + } diff --git a/install/install.sql b/install/install.sql index eb8ad9a4..4ac00001 100644 --- a/install/install.sql +++ b/install/install.sql @@ -299,6 +299,9 @@ CREATE TABLE IF NOT EXISTS `v3_client_sessions` ( `is_online` BOOL NOT NULL DEFAULT '1', `is_save` BOOL NOT NULL DEFAULT '0', `last-online` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `latitude` FLOAT NOT NULL DEFAULT '0.0', + `longitude` FLOAT NOT NULL DEFAULT '0.0', + `country_code` VARCHAR(2) NOT NULL DEFAULT '', PRIMARY KEY (`uid`), UNIQUE KEY `key_session` (`uid`, `cid`) ) @@ -327,6 +330,7 @@ CREATE TABLE IF NOT EXISTS `v3_servers` ( `game_started` TINYINT UNSIGNED NOT NULL DEFAULT '0', `latitude` FLOAT NOT NULL DEFAULT '0.0', `longitude` FLOAT NOT NULL DEFAULT '0.0', + `country_code` VARCHAR(2) NOT NULL DEFAULT '', `current_track` VARCHAR(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `key_hostid` (`host_id`),