How to track users location / region in PHP - php

I'm trying to get the country from which the user is browsing the website so I can work out what currency to show on the website. I have tried using the GET scripts available from: http://api.hostip.info but they just return XX when I test it.
If anyone knows any better methods please share.
Thanks.

I use this:
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
$ip = $_SESSION['ip'];
$try1 = "http://ipinfodb.com/ip_query.php?ip=".$ip."&output=xml";
$try2 = "http://backup.ipinfodb.com/ip_query.php?ip=".$ip."&output=xml";
$XML = #simplexml_load_file($try1,NULL,TRUE);
if(!$XML) { $XML = #simplexml_load_file($try2,NULL,TRUE); }
if(!$XML) { return false; }
//Retrieve location, set time
if($XML->City=="") { $loc = "Localhost / Unknown"; }
else { $loc = $XML->City.", ".$XML->RegionName.", ".$XML->CountryName; }
$_SESSION['loc'] = $loc;

Try these:
http://ip-to-country.webhosting.info/
http://www.ip2location.com/
Both are IP address-to-country databases, which allow you to look up the country of origin of a given IP address.
However it's important to note that these databases are not 100% accurate. They're a good guide, but you will get false results for a variety of reasons.
Many people use proxying to get around country-specific blocks and filters.
Many IP ranges are assigned to companies with large geographic spread; you'll just get the country where they're based, not where the actual machine is (this always used to be a big problem for tracking AOL users, because they were all apparently living in Virginia)
Control of IP ranges are sometimes transferred between countries, so you may get false results from that (especially for smaller/less well-connected countries)
Keeping your database up-to-date will mitigate some of these issues, but won't resolve them entirely (especially the proxying issue), so you should always allow for the fact that you will get false results.

You should use the geoip library.
Maxmind provides free databases and commercial databases, with a difference in the date of last update and precision, the commercial being of better quality.
See http://www.maxmind.com/app/geolitecountry for the free database.
I think it should be sufficient for basic needs.

You can use Geolocation to get the Coordinates and then some Service to get the Country from that, but the geolocation API is browser based so you can only access it via JavaScript and then have to pass theese Informations to PHP somehow, i wrote something on the JS Part once:
http://www.lautr.com/utilizing-html5-geolocation-api-and-yahoo-placefinder-example
When it comes to getting the Location via the IP, there are a bazillion Services out there who offer databases for that, some free, some for charge, some with a lot of IP's stored and much data, some with less, for example the one you mentioned, works just fine:
http://api.hostip.info/?ip=192.0.32.10
So You can ether go with the Geolocation API which is pretty neat, but requires the users permission, works via JS and doesnt work in IE (so far) or have to look for a IPÜ Location Service that fits your needs :)

Try these:
$key="9dcde915a1a065fbaf14165f00fcc0461b8d0a6b43889614e8acdb8343e2cf15";
$ip= "198.168.1230.122";
$url = "http://api.ipinfodb.com/v3/ip-city/?key=$key&ip=$ip&format=xml";
// load xml file
$xml = simplexml_load_file($url);
// print the name of the first element
echo $xml->getName() . "";
// create a loop to print the element name and data for each node
foreach($xml->children() as $child)
{
echo $child->getName() . ": " . $child . "<br />";
}

There are many ways to do it as suggested by those earlier. But I suggest you take a look at the IP2 PHP library available at https://github.com/ip2iq/ip2-lib-php which we developed.
You can use it like below:
<?php
require_once("Ip2.php");
$ip2 = new \ip2iq\Ip2();
$country_code = $ip2->country('8.8.8.8');
//$country_code === 'US'
?>
It doesn't need any SQL or web service lookup just a local data file. It is faster than almost all other methods out there. The database is updated monthly you can download it for free.
The only thing you will need to do for now if you need the country name in your language is map it to an associative array from something like this https://gist.github.com/DHS/1340150

Related

Recursive Geocoding in PHP

I am analyzing a relative big amount of coordinates in PHP within a for cycle and I make a reverse geocoding calling the following function:
function getLocation( $coordinates )
{
$url = 'http://maps.googleapis.com/maps/api/geocode/json?latlng=' . trim($coordinates) . '&sensor=false';
$json = #file_get_contents($url);
$data = json_decode($json);
$status = $data->status;
//print_r ( $data->results[ 0 ]);
if ($status == "OK")
{
return $data->results[ 0 ]->address_components[ 1 ]->long_name . ", " . $data->results[ 0 ]->address_components[ 2 ]->long_name;
}
else
{
return false;
}
}
The problem arised is that quite often Google (logically) answers me back with a null json_array and I don't have the reverse geocoding of each coordinate.
how can I bypass this issue? Do you know any other service?
I saw many libraries to reverse geocode but I would prefer to use them as last resource in order not to make heavier my small project.
Ok, well google will normally give you a reason why it failed. I know from past experience that it will fail if you give it too many in a certain time so hence you need to put sleep(500) or something so you don't overload google.
The docs say that it has a tolerance - of perhaps it's giving you an address but those fields aren't populated. Some debug code would help here.
Equally there is some rules you can add e.g
https://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&location_type=ROOFTOP&result_type=street_address&key=YOUR_API_KEY
Would limit you results down and may give you a better address.
It's probably a good idea to store any addresses you have.

How can I open a specific frame, using cURL, while also giving a parameter

I'm currently making a site with three frames. Two columns, with the second having two rows.
Frame 1 shows a list of countries from a database, in the form of URLs which open in Frame 3 along with the country's id, so that only regions in that country are selected.
Now what I'm trying to do is have the header also get the country's id, so that I can have it say 'Welcome to $country'. I tried using cURL to open the header, but couldn't figure out how to do this.
My current PHP to retrieve the id(which works) and open the frame(which doesn't work) looks like this:
if(isset($_GET['land']) ){
$country_id = $_GET['land'];
}
else {
$country_id = "1";
}
$ch = curl_init('header.php?land='.$country_id.'target=\'header\'');
curl_exec($ch);
Meanwhile, my header's code looks like following
if(isset($_GET['land']) ){
$country_id = $_GET['land'];
}
else {
$country_id = "1";
}
echo $country_id;
I searched Google, SO and some other sites, but couldn't figure out how to do it. Thanks for either the time to read or respond.
Frames are a web technology from the 80th. You sure you want to use such old stuff? You can, but frames open a whole pothole of problems...
These days one can use client side scripting (ECMA/javascript) for dynamic layout and content manipulation. This gives you much more freedom and you do not get these hard boundaries where your logic is separated.

Get Computer Unique ID from PHP

I've created an application using PHP and I'm going to sell it to my local market. I will personally be going to their locations to install/configure Apache & MySQL as well as installing my own code.
I would like a security system so that if anyone attempts to copy my code to an unauthorized machine, it won't run.
I know no one can prevent reverse engineering an application. even .exe (binary) files are cracked and with PHP (source code) anyone can do.
In my country those reverse engineers are really hard to find, so I would like to propose minimal security options like:
1) Create class (say, Navigation) which identifies system information like CPU ID, Computer name or any combination of hardware ID to make a UNIQUE_ID and matches with my given UNIQUE_ID (to the individual to whom I sold the application). If it's valid, it returns the navigation menu. Otherwise it will simply destroy the database and halt the execution by throwing an exception, maybe like:
class Navigation {
public function d() {
return current system UNIQUE_ID;
}
public function get() {
$a = file_get_contents('hash');
$c = $this->d();
if (crypt($c) != $a) {
//destory database
throw new Exception('');
} else {
return "<ul><li><a>home</a></li></ul>"; //navigation menu
}
}
}
2) Then during the installation process I'll change system UNIQUE_ID in "hash" file, create an object, and save it into a file (nav.obj):
(install.php)
<?php
$a=new Navigation;
$out=serialize($a);
file_put_contents('nav.obj', $out);
3) in header.php (which gets included in every file):
<?php
$menu=file_get_contents('nav.obj');
$menu=unserialize($a);
echo $menu->get();
?>
I know this method isn't full proof, but I'm pretty sure that around 60% of PHP developers won't be able to crack it!
Now I only need to get current system UNIQUE_ID.
I have created this function to get an unique ID based on hardware (Hard disk UUID). It is possible to use different resources like machine names, domains or even hard disk size to get a better approach depending on your needs.
function UniqueMachineID($salt = "") {
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$temp = sys_get_temp_dir().DIRECTORY_SEPARATOR."diskpartscript.txt";
if(!file_exists($temp) && !is_file($temp)) file_put_contents($temp, "select disk 0\ndetail disk");
$output = shell_exec("diskpart /s ".$temp);
$lines = explode("\n",$output);
$result = array_filter($lines,function($line) {
return stripos($line,"ID:")!==false;
});
if(count($result)>0) {
$result = array_shift(array_values($result));
$result = explode(":",$result);
$result = trim(end($result));
} else $result = $output;
} else {
$result = shell_exec("blkid -o value -s UUID");
if(stripos($result,"blkid")!==false) {
$result = $_SERVER['HTTP_HOST'];
}
}
return md5($salt.md5($result));
}
echo UniqueMachineID();
As per http://man7.org/linux/man-pages/man5/machine-id.5.html
$machineId = trim(shell_exec('cat /etc/machine-id 2>/dev/null'));
EDIT for Tito:
[ekerner#**** ~]$ ls -l /etc/machine-id
-r--r--r--. 1 root root 33 Jul 8 2016 /etc/machine-id
EDIT 2 for Tito: Some things to consider and scenarios:
Is the user allowed to get a new machine? Id guess yes.
Or run on multiple devices?
Sounds like the machine could be irrelevant in your case?
If its user only (no machine restrictions) then Id go for a licencing service (relies on network).
There are many services for this:
Google Play (for Android apps) is a good example: https://developer.android.com/google/play/licensing/index.html
MS and Apple have similar services.
However just search the web for the term "Software Licensing Service" or "Cloud Based Software Licensing Service".
If its user + single device, then youll need to pass up the device id to whatever service you use or make, then allow the machine id to be updated, but not allow revert to previous machine id (would mean multiple devices).
However said services will give you the client code which should take care of that if its a requirement.
Two scenarios from experience:
1: User on any device: we simply made an API in the cloud (in a website) and a login screen in the app, when the user logged in it authenticated via the API and kept a token, and whenever the device was connected to the net the app would query the API and update the login and/or token.
You could alternatively have the login screen in the purchase (like maybe they already logged into a site to purchase), generate a key and pack it with or bind it into the app.
2: User plus machine:
Same thing except when the API is queried the machine id is passed up. The machine ID can change as many times as the user updates their device, but we kept a record of machine ids and made to ban rule on: if we saw an old (previously used) machine id then a certain amount of time had to have passed. Thus allowed the user to break their machine and pull out an old one.
Also to consider if you make one, how will you stop the app from working? Ppl are pretty clever it will need to be core compiled.
However that all being said, the various licensing services are pro at this and can cater for most needs. Plus in their experience theyve already overcome the security pitfalls. Id name one that I like except its yours to search out.
Nice if you can come on back with and positive or negative outcomes from your trails.
function getMachineId() {
$fingerprint = [php_uname(), disk_total_space('.'), filectime('/'), phpversion()];
return hash('sha256', json_encode($fingerprint));
}
This will get a probably-unique id based on a hash of:
The server's OS, OS version, hostname, and architecture.
The total space (not free space) on the drive where the php script is.
The Unix timestamp creation time of the computer's root file system.
The currently installed PHP version.
Unlike the other answers it doesn't depend on shell_exec() being enabled.

Block and/or Redirect Large List of IPs using PHP and MySQL

I have an external list of IPs I would like to block (or, better yet, redirect) on my website. I first was taking the .htaccess approach but then realized that it might be easier to do this with PHP. But now I'm not sure... Seems like PHP and MySQL together is the best option.
I have a table in MySQL that holds the CIDR blocks. I've been struggling with trying to compare the user's IP to the table.
What's the best method?
I was thinking of checking the blocked IP in some way, something like this (PHP and psuedocode):
$user_ip = $_SERVER['REMOTE_ADDR'];
loop through returned rows {
if ($user_ip in returned row from MySQL table) {
forward to another domain
}
else // do something else...
}
But I'm dealing with groups of IPs; not single IPs. I've been working with some CIDR scripts and ip2long to disassemble and parse the groups. But this has proven difficult.
Based on a suggestion from the comments, if I stored the IPs in the database, I could compare directly. But the entire block I'm dealing with is huge and I'm not sure about doing it this way. I'd be dealing with millions of IPs on the comparison...
Any suggestions on how best to accomplish this?
I was able to ge this sorted out finally. Here is what I did...
First, I created a script that injected my huge list of IP CIDR blocks into MySQL.
Then, I wrote a script that first queried the database, fetched the IP blocks,
collected them into an array, and then used functions found herein ( Find whether a given IP exists in CIDR or not ) to compare the user's IP to the CIDR blocks from the database.
// initialize array
$cidr_block = array();
// query mysql database for all id and ip_cidr values
$query = "SELECT id, ip_cidr FROM ip_blocks";
// use mysqli class to query database
if ($result = $mysqli -> query($query)) {
// build array for testing with IP_vs_CIDR class
while ($row = $result -> fetch_assoc()) {
$cidr_block[] = $row['ip_cidr'];
}
// run IP_vs_CIDR on user ip address and cidr block array from database
$cidr_result = $ip_vs_cidr -> test_user_ip($user_ip, $cidr_block);
// test ip against the array values and send elswhere if bad
if ($cidr_result) {
header('Location: http://www.google.com/');
}
// free up result set
$result -> close();
}
Then, I redirected based on this result. Seems to be working nicely, as several tests with fixed IPs for user IP (eg, $user_ip = '75.150.41.15') tested correctly and redirected as expected. I tested IPs from the black list and not, to cover both bases.
Thanks to those for your comments - they were helpful.

help with php and xml for yahoo service

I am working on a PHP code for my social network, what I am planning to do is on user signup or profile update
I will take there zipcode that they enter and use yahoo to lookup there lattitude and logitude from there zipcode
I will then save these value to there user mysql table, this will be used later on to find users located within X amount
of miles from them.
Here is my code so far to get this info before storing into DB
<?PHP
function yahoo_geo($location) {
$q = 'http://api.local.yahoo.com/MapsService/V1/geocode';
q .= '?appid=rlerdorf&location='.rawurlencode($location);
echo $q;
libxml_use_internal_errors(true);
$xml = simplexml_load_file($q);
if(!is_object($xml)) return false;
$ret['precision'] = (string)$xml->Result['precision'];
foreach($xml->Result->children() as $key=>$val) {
if(strlen($val)){
$ret[(string)$key] = (string)$val;
}
return $ret;
}
$_REQUEST['location'] = 32744; // my zip code
$a = yahoo_geo($_REQUEST['location']);
// set retunred data to some variables for storage into MySQL
$latitude = $a[Latitude];
$longitude = $a[Longitude];
// MySQL code will go here
// Output for demo
echo 'Lattitude' .$latitude. '<BR><BR>';
echo 'Longitude' .$longitude. '<BR><BR>';
?>
This works great except if a long. and lat. is not found it returns an error to the browser
Warning: simplexml_load_file(http://api.local.yahoo.com/MapsService/V1/geocode?appid=rlerdorf&location=some non zipcode) [function.simplexml-load-file]: failed to open stream: HTTP request failed! HTTP/1.1 400 Bad Request in C:\webserver\htdocs\test\geolocation\index.php on line 16
Note; I could make sure that zipcode is numeric only but I would rather not as yahoo will let me search with other fields as well, so If a user does not enter a zipcode I will make it use a city or state or even country at the worse case so every user should have some sort of longitude and lat. saved
Is there a good way to avoid the error showing or even better is there a way to detect id there is an error and if an error occurs I could insert a default value for that user?
Not exactly sure what your question is, but I think you're looking for the error control operator #
xml = #simplexml_load_file($q);
Your function will then return false as normal.
You need to check that later though (and add some quotes):
if (!$a)
{
// Do something
}
else
{
$latitude = $a['Latitude'];
$longitude = $a['Longitude'];
}
With the # operator :
you have to use it everytime you need it
when you want to debug, you have to remove it from everywhere (or use the scream extension -- which you cannot always install, depending on your hosting service)
if there is an error somewhere you didn't put #, it will still be displayed to the user.
Another solution would be to use something like this at the beginning of your script, when you are on the production server :
ini_set('display_errors', 'Off');
That way, no error is ever displayed to the end-user (they don't need to see those, and probably wouldn't understand), even in situations you didn't think about ; and it's easy to get all the errors displayed on your development machine : just on line to comment ;-)
Note : in production, if you can set the configuration like you want, there is the possibility to log errors to a file ; that way, you can check those, and not have them displayed.

Categories