Version: current

      1 <?php
      2 /**
      3  * Routines for IP to country resolution.
      4  *
      5  * @package sapphire
      6  * @subpackage misc
      7  */
      8 class Geoip {
      9
     10     public static $default_country_code = false;
     11
     12     /**
     13      * ISO 3166 Country Codes
     14      *
     15      * Includes additional codes for Europe,
     16      * Asia Pacific Region,Anonymous Proxies
     17      * & Satellite Provider.
     18      */
     19     protected static $iso_3166_countryCodes = array(
     20         'A1' => "Anonymous Proxy",
     21         'A2' => "Satellite Provider",
     22         'A3' => "Internal Netwrok",
     23         'AD' => "Andorra",
     24         'AE' => "United Arab Emirates",
     25         'AF' => "Afghanistan",
     26         'AG' => "Antigua and Barbuda",
     27         'AI' => "Anguilla",
     28         'AL' => "Albania",
     29         'AM' => "Armenia",
     30         'AN' => "Netherlands Antilles",
     31         'AO' => "Angola",
     32         'AP' => "Asia/Pacific Region",
     33         'AQ' => "Antarctica",
     34         'AR' => "Argentina",
     35         'AS' => "American Samoa",
     36         'AT' => "Austria",
     37         'AU' => "Australia",
     38         'AW' => "Aruba",
     39         'AZ' => "Azerbaijan",
     40         'BA' => "Bosnia and Herzegovina",
     41         'BB' => "Barbados",
     42         'BD' => "Bangladesh",
     43         'BE' => "Belgium",
     44         'BF' => "Burkina Faso",
     45         'BG' => "Bulgaria",
     46         'BH' => "Bahrain",
     47         'BI' => "Burundi",
     48         'BJ' => "Benin",
     49         'BM' => "Bermuda",
     50         'BN' => "Brunei Darussalam",
     51         'BO' => "Bolivia",
     52         'BR' => "Brazil",
     53         'BS' => "Bahamas",
     54         'BT' => "Bhutan",
     55         'BV' => "Bouvet Island",
     56         'BW' => "Botswana",
     57         'BY' => "Belarus",
     58         'BZ' => "Belize",
     59         'CA' => "Canada",
     60         'CC' => "Cocos (Keeling) Islands",
     61         'CF' => "Central African Republic",
     62         'CG' => "Congo",
     63         'CH' => "Switzerland",
     64         'CI' => "Cote D'Ivoire",
     65         'CK' => "Cook Islands",
     66         'CL' => "Chile",
     67         'CM' => "Cameroon",
     68         'CN' => "China",
     69         'CO' => "Colombia",
     70         'CR' => "Costa Rica",
     71         'CU' => "Cuba",
     72         'CV' => "Cape Verde",
     73         'CX' => "Christmas Island",
     74         'CY' => "Cyprus",
     75         'CZ' => "Czech Republic",
     76         'DE' => "Germany",
     77         'DJ' => "Djibouti",
     78         'DK' => "Denmark",
     79         'DM' => "Dominica",
     80         'DO' => "Dominican Republic",
     81         'DZ' => "Algeria",
     82         'EC' => "Ecuador",
     83         'EE' => "Estonia",
     84         'EG' => "Egypt",
     85         'EH' => "Western Sahara",
     86         'ER' => "Eritrea",
     87         'ES' => "Spain",
     88         'ET' => "Ethiopia",
     89         'EU' => "Europe",
     90         'FI' => "Finland",
     91         'FJ' => "Fiji",
     92         'FK' => "Falkland Islands (Malvinas)",
     93         'FM' => "Micronesia - Federated States of",
     94         'FO' => "Faroe Islands",
     95         'FR' => "France",
     96         'FX' => "France (Metropolitan)",
     97         'GA' => "Gabon",
     98         'GB' => "United Kingdom",
     99         'GD' => "Grenada",
    100         'GE' => "Georgia",
    101         'GF' => "French Guiana",
    102         'GH' => "Ghana",
    103         'GI' => "Gibraltar",
    104         'GL' => "Greenland",
    105         'GM' => "Gambia",
    106         'GN' => "Guinea",
    107         'GP' => "Guadeloupe",
    108         'GQ' => "Equatorial Guinea",
    109         'GR' => "Greece",
    110         'GS' => "South Georgia and the South Sandwich Islands",
    111         'GT' => "Guatemala",
    112         'GU' => "Guam",
    113         'GW' => "Guinea-Bissau",
    114         'GY' => "Guyana",
    115         'HK' => "Hong Kong",
    116         'HM' => "Heard Island and McDonald Islands",
    117         'HN' => "Honduras",
    118         'HR' => "Croatia",
    119         'HT' => "Haiti",
    120         'HU' => "Hungary",
    121         'ID' => "Indonesia",
    122         'IE' => "Ireland",
    123         'IL' => "Israel",
    124         'IN' => "India",
    125         'IO' => "British Indian Ocean Territory",
    126         'IQ' => "Iraq",
    127         'IR' => "Iran - Islamic Republic of",
    128         'IS' => "Iceland",
    129         'IT' => "Italy",
    130         'JM' => "Jamaica",
    131         'JO' => "Jordan",
    132         'JP' => "Japan",
    133         'KE' => "Kenya",
    134         'KG' => "Kyrgyzstan",
    135         'KH' => "Cambodia",
    136         'KI' => "Kiribati",
    137         'KM' => "Comoros",
    138         'KN' => "Saint Kitts and Nevis",
    139         'KP' => "Korea - Democratic People's Republic of",
    140         'KR' => "Korea - Republic of",
    141         'KW' => "Kuwait",
    142         'KY' => "Cayman Islands",
    143         'KZ' => "Kazakhstan",
    144         'LA' => "Lao People's Democratic Republic",
    145         'LB' => "Lebanon",
    146         'LC' => "Saint Lucia",
    147         'LI' => "Liechtenstein",
    148         'LK' => "Sri Lanka",
    149         'LR' => "Liberia",
    150         'LS' => "Lesotho",
    151         'LT' => "Lithuania",
    152         'LU' => "Luxembourg",
    153         'LV' => "Latvia",
    154         'LY' => "Libyan Arab Jamahiriya",
    155         'MA' => "Morocco",
    156         'MC' => "Monaco",
    157         'MD' => "Moldova - Republic of",
    158         'MG' => "Madagascar",
    159         'MH' => "Marshall Islands",
    160         'MK' => "Macedonia - the Former Yugoslav Republic of",
    161         'ML' => "Mali",
    162         'MM' => "Myanmar",
    163         'MN' => "Mongolia",
    164         'MO' => "Macao",
    165         'MP' => "Northern Mariana Islands",
    166         'MQ' => "Martinique",
    167         'MR' => "Mauritania",
    168         'MS' => "Montserrat",
    169         'MT' => "Malta",
    170         'MU' => "Mauritius",
    171         'MV' => "Maldives",
    172         'MW' => "Malawi",
    173         'MX' => "Mexico",
    174         'MY' => "Malaysia",
    175         'MZ' => "Mozambique",
    176         'NA' => "Namibia",
    177         'NC' => "New Caledonia",
    178         'NE' => "Niger",
    179         'NF' => "Norfolk Island",
    180         'NG' => "Nigeria",
    181         'NI' => "Nicaragua",
    182         'NL' => "Netherlands",
    183         'NO' => "Norway",
    184         'NP' => "Nepal",
    185         'NR' => "Nauru",
    186         'NU' => "Niue",
    187         'NZ' => "New Zealand",
    188         'OM' => "Oman",
    189         'PA' => "Panama",
    190         'PE' => "Peru",
    191         'PF' => "French Polynesia",
    192         'PG' => "Papua New Guinea",
    193         'PH' => "Philippines",
    194         'PK' => "Pakistan",
    195         'PL' => "Poland",
    196         'PM' => "Saint Pierre and Miquelon",
    197         'PN' => "Pitcairn",
    198         'PR' => "Puerto Rico",
    199         'PS' => "Palestinian Territory - Occupied",
    200         'PT' => "Portugal",
    201         'PW' => "Palau",
    202         'PY' => "Paraguay",
    203         'QA' => "Qatar",
    204         'RE' => "Reunion",
    205         'RO' => "Romania",
    206         'RU' => "Russian Federation",
    207         'RW' => "Rwanda",
    208         'SA' => "Saudi Arabia",
    209         'SB' => "Solomon Islands",
    210         'SC' => "Seychelles",
    211         'SD' => "Sudan",
    212         'SE' => "Sweden",
    213         'SG' => "Singapore",
    214         'SH' => "Saint Helena",
    215         'SI' => "Slovenia",
    216         'SJ' => "Svalbard and Jan Mayen",
    217         'SK' => "Slovakia",
    218         'SL' => "Sierra Leone",
    219         'SM' => "San Marino",
    220         'SN' => "Senegal",
    221         'SO' => "Somalia",
    222         'SR' => "Suriname",
    223         'ST' => "Sao Tome and Principe",
    224         'SV' => "El Salvador",
    225         'SY' => "Syrian Arab Republic",
    226         'SZ' => "Swaziland",
    227         'TC' => "Turks and Caicos Islands",
    228         'TD' => "Chad",
    229         'TF' => "French Southern Territories",
    230         'TG' => "Togo",
    231         'TH' => "Thailand",
    232         'TJ' => "Tajikistan",
    233         'TK' => "Tokelau",
    234         'TL' => "East Timor",
    235         'TM' => "Turkmenistan",
    236         'TN' => "Tunisia",
    237         'TO' => "Tonga",
    238         'TR' => "Turkey",
    239         'TT' => "Trinidad and Tobago",
    240         'TV' => "Tuvalu",
    241         'TW' => "Taiwan (Province of China)",
    242         'TZ' => "Tanzania (United Republic of)",
    243         'UA' => "Ukraine",
    244         'UG' => "Uganda",
    245         'UM' => "United States Minor Outlying Islands",
    246         'US' => "United States",
    247         'UY' => "Uruguay",
    248         'UZ' => "Uzbekistan",
    249         'VA' => "Holy See (Vatican City State)",
    250         'VC' => "Saint Vincent and the Grenadines",
    251         'VE' => "Venezuela",
    252         'VG' => "Virgin Islands - British",
    253         'VI' => "Virgin Islands - U.S.",
    254         'VN' => "Vietnam",
    255         'VU' => "Vanuatu",
    256         'WF' => "Wallis and Futuna",
    257         'WS' => "Samoa",
    258         'YE' => "Yemen",
    259         'YT' => "Mayotte",
    260         'YU' => "Yugoslavia",
    261         'ZA' => "South Africa",
    262         'ZM' => "Zambia",
    263         'ZR' => "Zaire",
    264         'ZW' => "Zimbabwe"
    265     );
    266
    267     /**
    268      * Find the country for an IP address.
    269      *
    270      * By default, it will return an array, keyed by
    271      * the country code with a value of the country
    272      * name.
    273      *
    274      * To return the code only, pass in true for the
    275      * $codeOnly parameter.
    276      *
    277      * @param string $address The IP address to get the country of
    278      * @param boolean $codeOnly Returns just the country code
    279      */
    280     static function ip2country($address, $codeOnly = false) {
    281
    282         // Return if in CLI, or you'll get this error: "sh: geoiplookup: command not found"
    283         if(Director::is_cli() || !function_exists('exec')) return false;
    284
    285         $cmd = 'geoiplookup ' . escapeshellarg($address);
    286         exec($cmd, $result, $code);
    287         // Note: At time of writing, $result is always zero for this program
    288
    289         if($code == 127) return false;
    290         if($result == false) return false;
    291
    292         // Always returns one line of code, e.g. :
    293         // Geoip Country Edition: GB, United Kingdom
    294         // NZ
    295         $country = $result[0];
    296
    297         $start = strpos($country, ':');
    298         if($start) $start += 2;
    299         $code = substr($country, $start, 2); // skip space
    300
    301         if($code == 'IP' || $code == '--') {
    302              if(self::$default_country_code) {
    303                 $code = self::$default_country_code;
    304              } else {
    305                 return false;
    306              }
    307         }
    308
    309         if(!$codeOnly) {
    310             $name = substr($country, $start + 4);
    311             if(!$name) $name = $this->countryCode2name($code);
    312
    313             return array('code' => $code, 'name' => $name);
    314         } else {
    315             return $code;
    316         }
    317     }
    318
    319     /**
    320      * Returns the country code, for the current visitor
    321      */
    322     static function visitor_country() {
    323         if( ereg('^dev(\\.|$)', $_SERVER['HTTP_HOST']) && isset($_GET['country'])) return $_GET['country'];
    324         else if(isset($_SERVER['REMOTE_ADDR'])) return Geoip::ip2country($_SERVER['REMOTE_ADDR'], true);
    325     }
    326
    327     /**
    328      * Sanity Checker for this class, which helps us debug,
    329      * or ensure that its working as expected
    330      */
    331     static function ip2country_check() {
    332         global $ss_disableFeatures;
    333         if($ss_disableFeatures['geoip']) return;
    334         // sanity check for ip2country, to ensure that it is working as expected.
    335
    336         $checks = array(
    337             'www.paradise.net.nz' => array('code'=>'NZ','name'=>'New Zealand'),
    338             'news.com.au'            => array('code'=>'AU','name'=>'Australia'),
    339             'www.google.com'         => array('code'=>'US','name'=>'United States'),
    340             'a.b.c.d.e.f.g'          => false, // test failure :)
    341         );
    342
    343         $status = true;
    344
    345         foreach ($checks as $url => $expectedResponse) {
    346             $response = self::ip2country($url);
    347
    348             if(!$response && $expectedResponse) {
    349                 user_error("ip2country_check failed sanity check: ip2country($url) returned false. Expected code: '$expectedResponse'", E_USER_WARNING);
    350                 $status = false;
    351             } elseif ($response != $expectedResponse) {
    352                 user_error("ip2country_check failed sanity check: ip2country($url) returned code: '$response[code]/$response[name]'. Expected code: '$expectedResponse[code]/$expectedResponse[name]'", E_USER_WARNING);
    353                 $status = false;
    354             }
    355         }
    356
    357         return $status;
    358     }
    359
    360     /**
    361      * Returns the country name from the appropriate code.
    362      * @return null|string String if country found, null if none found
    363      */
    364     static function countryCode2name($code) {
    365         if (!isset(Geoip::$iso_3166_countryCodes[$code])) return null;
    366         $name = Geoip::$iso_3166_countryCodes[$code];
    367
    368         return _t("Country.$code", $name);
    369     }
    370
    371     /**
    372      * Returns an array of ISO Country Codes -> Country Names
    373      */
    374     static function getCountryDropDown() {
    375         $dropdown = Geoip::$iso_3166_countryCodes;
    376         unset($dropdown['A1']);
    377         unset($dropdown['A2']);
    378         unset($dropdown['A3']);
    379         foreach ($dropdown as $code => $name) {
    380             $dropdown[$code] = _t("Country.$code", $name);
    381         }
    382         asort($dropdown);
    383         return $dropdown;
    384     }
    385 }
    386 ?>
    387

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -