fixed incorrect line endings
authorPetr Skoda <skodak@moodle.org>
Tue, 28 Sep 2010 13:19:13 +0000 (13:19 +0000)
committerPetr Skoda <skodak@moodle.org>
Tue, 28 Sep 2010 13:19:13 +0000 (13:19 +0000)
lib/pear/Net/GeoIP.php
lib/pear/Net/GeoIP/DMA.php
lib/pear/Net/GeoIP/Location.php

index f2cb3e7..f3c48c6 100644 (file)
-<?php\r
-/**\r
- * +----------------------------------------------------------------------+\r
- * | PHP version 5                                                        |\r
- * +----------------------------------------------------------------------+\r
- * | Copyright (C) 2004 MaxMind LLC                                       |\r
- * +----------------------------------------------------------------------+\r
- * | This library is free software; you can redistribute it and/or        |\r
- * | modify it under the terms of the GNU Lesser General Public           |\r
- * | License as published by the Free Software Foundation; either         |\r
- * | version 2.1 of the License, or (at your option) any later version.   |\r
- * |                                                                      |\r
- * | This library is distributed in the hope that it will be useful,      |\r
- * | but WITHOUT ANY WARRANTY; without even the implied warranty of       |\r
- * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |\r
- * | Lesser General Public License for more details.                      |\r
- * |                                                                      |\r
- * | You should have received a copy of the GNU Lesser General Public     |\r
- * | License along with this library; if not, write to the Free Software  |\r
- * | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 |\r
- * | USA, or view it online at http://www.gnu.org/licenses/lgpl.txt.      |\r
- * +----------------------------------------------------------------------+\r
- * | Authors: Jim Winstead <jimw@apache.org> (original Maxmind version)   |\r
- * |          Hans Lellelid <hans@xmpl.org>                               |\r
- * +----------------------------------------------------------------------+\r
- *\r
- * @category Net\r
- * @package  Net_GeoIP\r
- * @author   Jim Winstead <jimw@apache.org> (original Maxmind PHP API)\r
- * @author   Hans Lellelid <hans@xmpl.org>\r
- * @license  LGPL http://www.gnu.org/licenses/lgpl.txt\r
- * @link     http://pear.php.net/package/Net_GeoIp\r
- * $Id$\r
- */\r
-\r
-require_once 'PEAR/Exception.php';\r
-\r
-/**\r
- * GeoIP class provides an API for performing geo-location lookups based on IP\r
- * address.\r
- *\r
- * To use this class you must have a [binary version] GeoIP database. There is\r
- * a free GeoIP country database which can be obtained from Maxmind:\r
- * {@link http://www.maxmind.com/app/geoip_country}\r
- *\r
- *\r
- * <b>SIMPLE USE</b>\r
- *\r
- *\r
- * Create an instance:\r
- *\r
- * <code>\r
- * $geoip = Net_GeoIP::getInstance('/path/to/geoipdb.dat', Net_GeoIP::SHARED_MEMORY);\r
- * </code>\r
- *\r
- * Depending on which database you are using (free, or one of paid versions)\r
- * you must use appropriate lookup method:\r
- *\r
- * <code>\r
- * // for free country db:\r
- * $country_name = $geoip->lookupCountryName($_SERVER['REMOTE_ADDR']);\r
- * $country_code = $geoip->lookupCountryCode($_SERVER['REMOTE_ADDR']);\r
- *\r
- * // for [non-free] region db:\r
- * list($ctry_code, $region) = $geoip->lookupRegion($_SERVER['REMOTE_ADDR']);\r
- *\r
- * // for [non-free] city db:\r
- * $location = $geoip->lookupLocation($_SERVER['REMOTE_ADDR']);\r
- * print "city: " . $location->city . ", " . $location->region;\r
- * print "lat: " . $location->latitude . ", long: " . $location->longitude;\r
- *\r
- * // for organization or ISP db:\r
- * $org_or_isp_name = $geoip->lookupOrg($_SERVER['REMOTE_ADDR']);\r
- * </code>\r
- *\r
- *\r
- * <b>MULTIPLE INSTANCES</b>\r
- *\r
- *\r
- * You can have several instances of this class, one for each database file\r
- * you are using.  You should use the static getInstance() singleton method\r
- * to save on overhead of setting up database segments.  Note that only one\r
- * instance is stored per filename, and any flags will be ignored if an\r
- * instance already exists for the specifiedfilename.\r
- *\r
- * <b>Special note on using SHARED_MEMORY flag</b>\r
- *\r
- * If you are using SHARED_MEMORY (shmop) you can only use SHARED_MEMORY for\r
- * one (1) instance  (i.e. for one database). Any subsequent attempts to\r
- * instantiate using SHARED_MEMORY will read the same shared memory block\r
- * already initialized, and therefore will cause problems since the expected\r
- * database format won't match the database in the shared memory block.\r
- *\r
- * Note that there is no easy way to flag "nice errors" to prevent attempts\r
- * to create new instances using SHARED_MEMORY flag and it is also not posible\r
- * (in a safe way) to allow new instances to overwrite the shared memory block.\r
- *\r
- * In short, is you are using multiple databses, use the SHARED_MEMORY flag\r
- * with care.\r
- *\r
- *\r
- * <b>LOOKUPS ON HOSTNAMES</b>\r
- *\r
- *\r
- * Note that this PHP API does NOT support lookups on hostnames.  This is so\r
- * that the public API can be kept simple and so that the lookup functions\r
- * don't need to try name lookups if IP lookup fails (which would be the only\r
- * way to keep the API simple and support name-based lookups).\r
- *\r
- * If you do not know the IP address, you can convert an name to IP very\r
- * simply using PHP native functions or other libraries:\r
- *\r
- * <code>\r
- *     $geoip->lookupCountryName(gethostbyname('www.sunset.se'));\r
- * </code>\r
- *\r
- * Or, if you don't know whether an address is a name or ip address, use\r
- * application-level logic:\r
- *\r
- * <code>\r
- * if (ip2long($ip_or_name) === false) {\r
- *   $ip = gethostbyname($ip_or_name);\r
- * } else {\r
- *   $ip = $ip_or_name;\r
- * }\r
- * $ctry = $geoip->lookupCountryName($ip);\r
- * </code>\r
- *\r
- * @category Net\r
- * @package  Net_GeoIP\r
- * @author   Jim Winstead <jimw@apache.org> (original Maxmind PHP API)\r
- * @author   Hans Lellelid <hans@xmpl.org>\r
- * @license  LGPL http://www.gnu.org/licenses/lgpl.txt\r
- * @link     http://pear.php.net/package/Net_GeoIp\r
- */\r
-class Net_GeoIP\r
-{\r
-    /**\r
-     * Exception error code used for invalid IP address.\r
-     */\r
-    const ERR_INVALID_IP =  218624992; // crc32('Net_GeoIP::ERR_INVALID_IP')\r
-\r
-    /**\r
-     * Exception error code when there is a DB-format-related error.\r
-     */\r
-    const ERR_DB_FORMAT = 866184008; // crc32('Net_GeoIP::ERR_DB_FORMAT')\r
-\r
-    public static $COUNTRY_CODES = array(\r
-      "", "AP", "EU", "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AN", "AO", "AQ",\r
-      "AR", "AS", "AT", "AU", "AW", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH",\r
-      "BI", "BJ", "BM", "BN", "BO", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA",\r
-      "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU",\r
-      "CV", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG",\r
-      "EH", "ER", "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", "FX", "GA", "GB",\r
-      "GD", "GE", "GF", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT",\r
-      "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IN",\r
-      "IO", "IQ", "IR", "IS", "IT", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM",\r
-      "KN", "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS",\r
-      "LT", "LU", "LV", "LY", "MA", "MC", "MD", "MG", "MH", "MK", "ML", "MM", "MN",\r
-      "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA",\r
-      "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", "OM", "PA",\r
-      "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY",\r
-      "QA", "RE", "RO", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI",\r
-      "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "ST", "SV", "SY", "SZ", "TC", "TD",\r
-      "TF", "TG", "TH", "TJ", "TK", "TM", "TN", "TO", "TL", "TR", "TT", "TV", "TW",\r
-      "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN",\r
-      "VU", "WF", "WS", "YE", "YT", "RS", "ZA", "ZM", "ME", "ZW", "A1", "A2", "O1",\r
-      "AX", "GG", "IM", "JE", "BL", "MF"\r
-        );\r
-\r
-    public static $COUNTRY_CODES3 = array(\r
-    "","AP","EU","AND","ARE","AFG","ATG","AIA","ALB","ARM","ANT","AGO","AQ","ARG",\r
-    "ASM","AUT","AUS","ABW","AZE","BIH","BRB","BGD","BEL","BFA","BGR","BHR","BDI",\r
-    "BEN","BMU","BRN","BOL","BRA","BHS","BTN","BV","BWA","BLR","BLZ","CAN","CC",\r
-    "COD","CAF","COG","CHE","CIV","COK","CHL","CMR","CHN","COL","CRI","CUB","CPV",\r
-    "CX","CYP","CZE","DEU","DJI","DNK","DMA","DOM","DZA","ECU","EST","EGY","ESH",\r
-    "ERI","ESP","ETH","FIN","FJI","FLK","FSM","FRO","FRA","FX","GAB","GBR","GRD",\r
-    "GEO","GUF","GHA","GIB","GRL","GMB","GIN","GLP","GNQ","GRC","GS","GTM","GUM",\r
-    "GNB","GUY","HKG","HM","HND","HRV","HTI","HUN","IDN","IRL","ISR","IND","IO",\r
-    "IRQ","IRN","ISL","ITA","JAM","JOR","JPN","KEN","KGZ","KHM","KIR","COM","KNA",\r
-    "PRK","KOR","KWT","CYM","KAZ","LAO","LBN","LCA","LIE","LKA","LBR","LSO","LTU",\r
-    "LUX","LVA","LBY","MAR","MCO","MDA","MDG","MHL","MKD","MLI","MMR","MNG","MAC",\r
-    "MNP","MTQ","MRT","MSR","MLT","MUS","MDV","MWI","MEX","MYS","MOZ","NAM","NCL",\r
-    "NER","NFK","NGA","NIC","NLD","NOR","NPL","NRU","NIU","NZL","OMN","PAN","PER",\r
-    "PYF","PNG","PHL","PAK","POL","SPM","PCN","PRI","PSE","PRT","PLW","PRY","QAT",\r
-    "REU","ROU","RUS","RWA","SAU","SLB","SYC","SDN","SWE","SGP","SHN","SVN","SJM",\r
-    "SVK","SLE","SMR","SEN","SOM","SUR","STP","SLV","SYR","SWZ","TCA","TCD","TF",\r
-    "TGO","THA","TJK","TKL","TLS","TKM","TUN","TON","TUR","TTO","TUV","TWN","TZA",\r
-    "UKR","UGA","UM","USA","URY","UZB","VAT","VCT","VEN","VGB","VIR","VNM","VUT",\r
-    "WLF","WSM","YEM","YT","SRB","ZAF","ZMB","MNE","ZWE","A1","A2","O1",\r
-    "ALA","GGY","IMN","JEY","BLM","MAF"\r
-        );\r
-\r
-    public static $COUNTRY_NAMES = array(\r
-        "", "Asia/Pacific Region", "Europe", "Andorra", "United Arab Emirates",\r
-        "Afghanistan", "Antigua and Barbuda", "Anguilla", "Albania", "Armenia",\r
-        "Netherlands Antilles", "Angola", "Antarctica", "Argentina", "American Samoa",\r
-        "Austria", "Australia", "Aruba", "Azerbaijan", "Bosnia and Herzegovina",\r
-        "Barbados", "Bangladesh", "Belgium", "Burkina Faso", "Bulgaria", "Bahrain",\r
-        "Burundi", "Benin", "Bermuda", "Brunei Darussalam", "Bolivia", "Brazil",\r
-        "Bahamas", "Bhutan", "Bouvet Island", "Botswana", "Belarus", "Belize",\r
-        "Canada", "Cocos (Keeling) Islands", "Congo, The Democratic Republic of the",\r
-        "Central African Republic", "Congo", "Switzerland", "Cote D'Ivoire", "Cook Islands",\r
-        "Chile", "Cameroon", "China", "Colombia", "Costa Rica", "Cuba", "Cape Verde",\r
-        "Christmas Island", "Cyprus", "Czech Republic", "Germany", "Djibouti",\r
-        "Denmark", "Dominica", "Dominican Republic", "Algeria", "Ecuador", "Estonia",\r
-        "Egypt", "Western Sahara", "Eritrea", "Spain", "Ethiopia", "Finland", "Fiji",\r
-        "Falkland Islands (Malvinas)", "Micronesia, Federated States of", "Faroe Islands",\r
-        "France", "France, Metropolitan", "Gabon", "United Kingdom",\r
-        "Grenada", "Georgia", "French Guiana", "Ghana", "Gibraltar", "Greenland",\r
-        "Gambia", "Guinea", "Guadeloupe", "Equatorial Guinea", "Greece", "South Georgia and the South Sandwich Islands",\r
-        "Guatemala", "Guam", "Guinea-Bissau",\r
-        "Guyana", "Hong Kong", "Heard Island and McDonald Islands", "Honduras",\r
-        "Croatia", "Haiti", "Hungary", "Indonesia", "Ireland", "Israel", "India",\r
-        "British Indian Ocean Territory", "Iraq", "Iran, Islamic Republic of",\r
-        "Iceland", "Italy", "Jamaica", "Jordan", "Japan", "Kenya", "Kyrgyzstan",\r
-        "Cambodia", "Kiribati", "Comoros", "Saint Kitts and Nevis", "Korea, Democratic People's Republic of",\r
-        "Korea, Republic of", "Kuwait", "Cayman Islands",\r
-        "Kazakstan", "Lao People's Democratic Republic", "Lebanon", "Saint Lucia",\r
-        "Liechtenstein", "Sri Lanka", "Liberia", "Lesotho", "Lithuania", "Luxembourg",\r
-        "Latvia", "Libyan Arab Jamahiriya", "Morocco", "Monaco", "Moldova, Republic of",\r
-        "Madagascar", "Marshall Islands", "Macedonia",\r
-        "Mali", "Myanmar", "Mongolia", "Macau", "Northern Mariana Islands",\r
-        "Martinique", "Mauritania", "Montserrat", "Malta", "Mauritius", "Maldives",\r
-        "Malawi", "Mexico", "Malaysia", "Mozambique", "Namibia", "New Caledonia",\r
-        "Niger", "Norfolk Island", "Nigeria", "Nicaragua", "Netherlands", "Norway",\r
-        "Nepal", "Nauru", "Niue", "New Zealand", "Oman", "Panama", "Peru", "French Polynesia",\r
-        "Papua New Guinea", "Philippines", "Pakistan", "Poland", "Saint Pierre and Miquelon",\r
-        "Pitcairn Islands", "Puerto Rico", "Palestinian Territory",\r
-        "Portugal", "Palau", "Paraguay", "Qatar", "Reunion", "Romania",\r
-        "Russian Federation", "Rwanda", "Saudi Arabia", "Solomon Islands",\r
-        "Seychelles", "Sudan", "Sweden", "Singapore", "Saint Helena", "Slovenia",\r
-        "Svalbard and Jan Mayen", "Slovakia", "Sierra Leone", "San Marino", "Senegal",\r
-        "Somalia", "Suriname", "Sao Tome and Principe", "El Salvador", "Syrian Arab Republic",\r
-        "Swaziland", "Turks and Caicos Islands", "Chad", "French Southern Territories",\r
-        "Togo", "Thailand", "Tajikistan", "Tokelau", "Turkmenistan",\r
-        "Tunisia", "Tonga", "Timor-Leste", "Turkey", "Trinidad and Tobago", "Tuvalu",\r
-        "Taiwan", "Tanzania, United Republic of", "Ukraine",\r
-        "Uganda", "United States Minor Outlying Islands", "United States", "Uruguay",\r
-        "Uzbekistan", "Holy See (Vatican City State)", "Saint Vincent and the Grenadines",\r
-        "Venezuela", "Virgin Islands, British", "Virgin Islands, U.S.",\r
-        "Vietnam", "Vanuatu", "Wallis and Futuna", "Samoa", "Yemen", "Mayotte",\r
-        "Serbia", "South Africa", "Zambia", "Montenegro", "Zimbabwe",\r
-        "Anonymous Proxy","Satellite Provider","Other",\r
-        "Aland Islands","Guernsey","Isle of Man","Jersey","Saint Barthelemy","Saint Martin"\r
-        );\r
-\r
-    // storage / caching flags\r
-    const STANDARD = 0;\r
-    const MEMORY_CACHE = 1;\r
-    const SHARED_MEMORY = 2;\r
-\r
-    // Database structure constants\r
-    const COUNTRY_BEGIN = 16776960;\r
-    const STATE_BEGIN_REV0 = 16700000;\r
-    const STATE_BEGIN_REV1 = 16000000;\r
-\r
-    const STRUCTURE_INFO_MAX_SIZE = 20;\r
-    const DATABASE_INFO_MAX_SIZE = 100;\r
-    const COUNTRY_EDITION = 106;\r
-    const REGION_EDITION_REV0 = 112;\r
-    const REGION_EDITION_REV1 = 3;\r
-    const CITY_EDITION_REV0 = 111;\r
-    const CITY_EDITION_REV1 = 2;\r
-    const ORG_EDITION = 110;\r
-    const SEGMENT_RECORD_LENGTH = 3;\r
-    const STANDARD_RECORD_LENGTH = 3;\r
-    const ORG_RECORD_LENGTH = 4;\r
-    const MAX_RECORD_LENGTH = 4;\r
-    const MAX_ORG_RECORD_LENGTH = 300;\r
-    const FULL_RECORD_LENGTH = 50;\r
-\r
-    const US_OFFSET = 1;\r
-    const CANADA_OFFSET = 677;\r
-    const WORLD_OFFSET = 1353;\r
-    const FIPS_RANGE = 360;\r
-\r
-    // SHMOP memory address\r
-    const SHM_KEY = 0x4f415401;\r
-\r
-    /**\r
-     * @var int\r
-     */\r
-    private $flags = 0;\r
-\r
-    /**\r
-     * @var resource\r
-     */\r
-    private $filehandle;\r
-\r
-    /**\r
-     * @var string\r
-     */\r
-    private $memoryBuffer;\r
-\r
-    /**\r
-     * @var int\r
-     */\r
-    private $databaseType;\r
-\r
-    /**\r
-     * @var int\r
-     */\r
-    private $databaseSegments;\r
-\r
-    /**\r
-     * @var int\r
-     */\r
-    private $recordLength;\r
-\r
-    /**\r
-     * The memory addr "id" for use with SHMOP.\r
-     * @var int\r
-     */\r
-    private $shmid;\r
-\r
-    /**\r
-     * Support for singleton pattern.\r
-     * @var array\r
-     */\r
-    private static $instances = array();\r
-\r
-    /**\r
-     * Construct a Net_GeoIP instance.\r
-     * You should use the getInstance() method if you plan to use multiple databases or\r
-     * the same database from several different places in your script.\r
-     *\r
-     * @param string $filename Path to binary geoip database.\r
-     * @param int    $flags    Flags\r
-     *\r
-     * @see getInstance()\r
-     */\r
-    public function __construct($filename = null, $flags = null)\r
-    {\r
-        if ($filename !== null) {\r
-            $this->open($filename, $flags);\r
-        }\r
-        // store the instance, so that it will be returned by a call to\r
-        // getInstance() (with the same db filename).\r
-        self::$instances[$filename] = $this;\r
-    }\r
-\r
-    /**\r
-     * Calls the close() function to free any resources.\r
-     * @see close()\r
-     *\r
-     * COMMENTED OUT TO ADDRESS BUG IN PHP 5.0.4, 5.0.5dev.  THIS RESOURCE\r
-     * SHOULD AUTOMATICALLY BE FREED AT SCRIPT CLOSE, SO A DESTRUCTOR\r
-     * IS A GOOD IDEA BUT NOT NECESSARILY A NECESSITY.\r
-    public function __destruct()\r
-    {\r
-        $this->close();\r
-    }\r
-    */\r
-\r
-    /**\r
-     * Singleton method, use this to get an instance and avoid re-parsing the db.\r
-     *\r
-     * Unique instances are instantiated based on the filename of the db. The flags\r
-     * are ignored -- in that requests to for instance with same filename but different\r
-     * flags will return the already-instantiated instance.  For example:\r
-     * <code>\r
-     * // create new instance with memory_cache enabled\r
-     * $geoip = Net_GeoIP::getInstance('C:\mydb.dat', Net_GeoIP::MEMORY_CACHE);\r
-     * ....\r
-     *\r
-     * // later in code, request instance with no flags specified.\r
-     * $geoip = Net_GeoIP::getInstance('C:\mydb.dat');\r
-     *\r
-     * // Normally this means no MEMORY_CACHE but since an instance\r
-     * // with memory cache enabled has already been created for 'C:\mydb.dat', the\r
-     * // existing instance (with memory cache) will be returned.\r
-     * </code>\r
-     *\r
-     * NOTE: You can only use SHARED_MEMORY flag for one instance!  Any subsquent instances\r
-     * that attempt to use the SHARED_MEMORY will use the *same* shared memory, which will break\r
-     * your script.\r
-     *\r
-     * @param string $filename Filename\r
-     * @param int    $flags    Flags that control class behavior.\r
-     *          + Net_GeoIp::SHARED_MEMORY\r
-     *             Use SHMOP to share a db among multiple PHP instances.\r
-     *             NOTE: ONLY ONE GEOIP INSTANCE CAN USE SHARED MEMORY!!!\r
-     *          + Net_GeoIp::MEMORY_CACHE\r
-     *             Store the full contents of the database in memory for current script.\r
-     *             This is useful if you access the database several times in a script.\r
-     *          + Net_GeoIp::STANDARD\r
-     *             [default] standard no-cache version.\r
-     *\r
-     * @return Net_GeoIP\r
-     */\r
-    public static function getInstance($filename = null, $flags = null)\r
-    {\r
-        if (!isset(self::$instances[$filename])) {\r
-            self::$instances[$filename] = new Net_GeoIP($filename, $flags);\r
-        }\r
-        return self::$instances[$filename];\r
-    }\r
-\r
-    /**\r
-     * Opens geoip database at filename and with specified flags.\r
-     *\r
-     * @param string $filename File to open\r
-     * @param int    $flags    Flags\r
-     *\r
-     * @return void\r
-     *\r
-     * @throws PEAR_Exception if unable to open specified file or shared memory.\r
-     */\r
-    public function open($filename, $flags = null)\r
-    {\r
-        if ($flags !== null) {\r
-            $this->flags = $flags;\r
-        }\r
-        if ($this->flags & self::SHARED_MEMORY) {\r
-            $this->shmid = @shmop_open(self::SHM_KEY, "a", 0, 0);\r
-            if ($this->shmid === false) {\r
-                $this->loadSharedMemory($filename);\r
-                $this->shmid = @shmop_open(self::SHM_KEY, "a", 0, 0);\r
-                if ($this->shmid === false) { // should never be false as loadSharedMemory() will throw Exc if cannot create\r
-                    throw new PEAR_Exception("Unable to open shared memory at key: " . dechex(self::SHM_KEY));\r
-                }\r
-            }\r
-        } else {\r
-            $this->filehandle = fopen($filename, "rb");\r
-            if (!$this->filehandle) {\r
-                throw new PEAR_Exception("Unable to open file: $filename");\r
-            }\r
-            if ($this->flags & self::MEMORY_CACHE) {\r
-                $s_array = fstat($this->filehandle);\r
-                $this->memoryBuffer = fread($this->filehandle, $s_array['size']);\r
-            }\r
-        }\r
-        $this->setupSegments();\r
-    }\r
-\r
-    /**\r
-     * Loads the database file into shared memory.\r
-     *\r
-     * @param string $filename Path to database file to read into shared memory.\r
-     *\r
-     * @return void\r
-     *\r
-     * @throws PEAR_Exception     - if unable to read the db file.\r
-     */\r
-    protected function loadSharedMemory($filename)\r
-    {\r
-        $fp = fopen($filename, "rb");\r
-        if (!$fp) {\r
-            throw new PEAR_Exception("Unable to open file: $filename");\r
-        }\r
-        $s_array = fstat($fp);\r
-        $size = $s_array['size'];\r
-\r
-        if ($shmid = @shmop_open(self::SHM_KEY, "w", 0, 0)) {\r
-            shmop_delete($shmid);\r
-            shmop_close($shmid);\r
-        }\r
-\r
-        if ($shmid = @shmop_open(self::SHM_KEY, "c", 0644, $size)) {\r
-            $offset = 0;\r
-            while ($offset < $size) {\r
-                $buf = fread($fp, 524288);\r
-                shmop_write($shmid, $buf, $offset);\r
-                $offset += 524288;\r
-            }\r
-            shmop_close($shmid);\r
-        }\r
-\r
-        fclose($fp);\r
-    }\r
-\r
-    /**\r
-     * Parses the database file to determine what kind of database is being used and setup\r
-     * segment sizes and start points that will be used by the seek*() methods later.\r
-     *\r
-     * @return void\r
-     */\r
-    protected function setupSegments()\r
-    {\r
-\r
-        $this->databaseType = self::COUNTRY_EDITION;\r
-        $this->recordLength = self::STANDARD_RECORD_LENGTH;\r
-\r
-        if ($this->flags & self::SHARED_MEMORY) {\r
-\r
-            $offset = shmop_size($this->shmid) - 3;\r
-            for ($i = 0; $i < self::STRUCTURE_INFO_MAX_SIZE; $i++) {\r
-                $delim = shmop_read($this->shmid, $offset, 3);\r
-                $offset += 3;\r
-                if ($delim == (chr(255).chr(255).chr(255))) {\r
-                    $this->databaseType = ord(shmop_read($this->shmid, $offset, 1));\r
-                    $offset++;\r
-                    if ($this->databaseType === self::REGION_EDITION_REV0) {\r
-                        $this->databaseSegments = self::STATE_BEGIN_REV0;\r
-                    } elseif ($this->databaseType === self::REGION_EDITION_REV1) {\r
-                        $this->databaseSegments = self::STATE_BEGIN_REV1;\r
-                    } elseif (($this->databaseType === self::CITY_EDITION_REV0)\r
-                                || ($this->databaseType === self::CITY_EDITION_REV1)\r
-                                || ($this->databaseType === self::ORG_EDITION)) {\r
-                        $this->databaseSegments = 0;\r
-                        $buf = shmop_read($this->shmid, $offset, self::SEGMENT_RECORD_LENGTH);\r
-                        for ($j = 0; $j < self::SEGMENT_RECORD_LENGTH; $j++) {\r
-                            $this->databaseSegments += (ord($buf[$j]) << ($j * 8));\r
-                        }\r
-                        if ($this->databaseType === self::ORG_EDITION) {\r
-                            $this->recordLength = self::ORG_RECORD_LENGTH;\r
-                        }\r
-                    }\r
-                    break;\r
-                } else {\r
-                    $offset -= 4;\r
-                }\r
-            }\r
-            if ($this->databaseType == self::COUNTRY_EDITION) {\r
-                $this->databaseSegments = self::COUNTRY_BEGIN;\r
-            }\r
-\r
-        } else {\r
-\r
-            $filepos = ftell($this->filehandle);\r
-            fseek($this->filehandle, -3, SEEK_END);\r
-            for ($i = 0; $i < self::STRUCTURE_INFO_MAX_SIZE; $i++) {\r
-                $delim = fread($this->filehandle, 3);\r
-                if ($delim == (chr(255).chr(255).chr(255))) {\r
-                    $this->databaseType = ord(fread($this->filehandle, 1));\r
-                    if ($this->databaseType === self::REGION_EDITION_REV0) {\r
-                        $this->databaseSegments = self::STATE_BEGIN_REV0;\r
-                    } elseif ($this->databaseType === self::REGION_EDITION_REV1) {\r
-                        $this->databaseSegments = self::STATE_BEGIN_REV1;\r
-                    } elseif ($this->databaseType === self::CITY_EDITION_REV0\r
-                                || $this->databaseType === self::CITY_EDITION_REV1\r
-                                || $this->databaseType === self::ORG_EDITION) {\r
-                        $this->databaseSegments = 0;\r
-                        $buf = fread($this->filehandle, self::SEGMENT_RECORD_LENGTH);\r
-                        for ($j = 0; $j < self::SEGMENT_RECORD_LENGTH; $j++) {\r
-                            $this->databaseSegments += (ord($buf[$j]) << ($j * 8));\r
-                        }\r
-                        if ($this->databaseType === self::ORG_EDITION) {\r
-                            $this->recordLength = self::ORG_RECORD_LENGTH;\r
-                        }\r
-                    }\r
-                    break;\r
-                } else {\r
-                    fseek($this->filehandle, -4, SEEK_CUR);\r
-                }\r
-            }\r
-            if ($this->databaseType === self::COUNTRY_EDITION) {\r
-                $this->databaseSegments = self::COUNTRY_BEGIN;\r
-            }\r
-            fseek($this->filehandle, $filepos, SEEK_SET);\r
-\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Closes the geoip database.\r
-     *\r
-     * @return int Status of close command.\r
-     */\r
-    public function close()\r
-    {\r
-        if ($this->flags & self::SHARED_MEMORY) {\r
-            return shmop_close($this->shmid);\r
-        } else {\r
-            // right now even if file was cached in RAM the file was not closed\r
-            // so it's safe to expect no error w/ fclose()\r
-            return fclose($this->filehandle);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Get the country index.\r
-     *\r
-     * This method is called by the lookupCountryCode() and lookupCountryName()\r
-     * methods.  It lookups up the index ('id') for the country which is the key\r
-     * for the code and name.\r
-     *\r
-     * @param string $addr IP address (hostname not allowed)\r
-     *\r
-     * @throws PEAR_Exception  - if IP address is invalid.\r
-     *                         - if database type is incorrect\r
-     *\r
-     * @return string ID for the country\r
-     */\r
-    protected function lookupCountryId($addr)\r
-    {\r
-        $ipnum = ip2long($addr);\r
-        if ($ipnum === false) {\r
-            throw new PEAR_Exception("Invalid IP address: " . var_export($addr, true), self::ERR_INVALID_IP);\r
-        }\r
-        if ($this->databaseType !== self::COUNTRY_EDITION) {\r
-            throw new PEAR_Exception("Invalid database type; lookupCountry*() methods expect Country database.");\r
-        }\r
-        return $this->seekCountry($ipnum) - self::COUNTRY_BEGIN;\r
-    }\r
-\r
-    /**\r
-     * Returns 2-letter country code (e.g. 'CA') for specified IP address.\r
-     * Use this method if you have a Country database.\r
-     *\r
-     * @param string $addr IP address (hostname not allowed).\r
-     *\r
-     * @return string 2-letter country code\r
-     *\r
-     * @throws PEAR_Exception (see lookupCountryId())\r
-     * @see lookupCountryId()\r
-     */\r
-    public function lookupCountryCode($addr)\r
-    {\r
-        return self::$COUNTRY_CODES[$this->lookupCountryId($addr)];\r
-    }\r
-\r
-    /**\r
-     * Returns full country name for specified IP address.\r
-     * Use this method if you have a Country database.\r
-     *\r
-     * @param string $addr IP address (hostname not allowed).\r
-     *\r
-     * @return string Country name\r
-     * @throws PEAR_Exception (see lookupCountryId())\r
-     * @see lookupCountryId()\r
-     */\r
-    public function lookupCountryName($addr)\r
-    {\r
-        return self::$COUNTRY_NAMES[$this->lookupCountryId($addr)];\r
-    }\r
-\r
-    /**\r
-     * Using the record length and appropriate start points, seek to the country that corresponds\r
-     * to the converted IP address integer.\r
-     *\r
-     * @param int $ipnum Result of ip2long() conversion.\r
-     *\r
-     * @return int Offset of start of record.\r
-     * @throws PEAR_Exception - if fseek() fails on the file or no results after traversing the database (indicating corrupt db).\r
-     */\r
-    protected function seekCountry($ipnum)\r
-    {\r
-        $offset = 0;\r
-        for ($depth = 31; $depth >= 0; --$depth) {\r
-            if ($this->flags & self::MEMORY_CACHE) {\r
-                  $buf = substr($this->memoryBuffer, 2 * $this->recordLength * $offset, 2 * $this->recordLength);\r
-            } elseif ($this->flags & self::SHARED_MEMORY) {\r
-                $buf = shmop_read($this->shmid, 2 * $this->recordLength * $offset, 2 * $this->recordLength);\r
-            } else {\r
-                if (fseek($this->filehandle, 2 * $this->recordLength * $offset, SEEK_SET) !== 0) {\r
-                    throw new PEAR_Exception("fseek failed");\r
-                }\r
-                $buf = fread($this->filehandle, 2 * $this->recordLength);\r
-            }\r
-            $x = array(0,0);\r
-            for ($i = 0; $i < 2; ++$i) {\r
-                for ($j = 0; $j < $this->recordLength; ++$j) {\r
-                    $x[$i] += ord($buf[$this->recordLength * $i + $j]) << ($j * 8);\r
-                }\r
-            }\r
-            if ($ipnum & (1 << $depth)) {\r
-                if ($x[1] >= $this->databaseSegments) {\r
-                    return $x[1];\r
-                }\r
-                $offset = $x[1];\r
-            } else {\r
-                if ($x[0] >= $this->databaseSegments) {\r
-                    return $x[0];\r
-                }\r
-                $offset = $x[0];\r
-            }\r
-        }\r
-        throw new PEAR_Exception("Error traversing database - perhaps it is corrupt?");\r
-    }\r
-\r
-    /**\r
-     * Lookup the organization (or ISP) for given IP address.\r
-     * Use this method if you have an Organization/ISP database.\r
-     *\r
-     * @param string $addr IP address (hostname not allowed).\r
-     *\r
-     * @throws PEAR_Exception  - if IP address is invalid.\r
-     *                         - if database is of wrong type\r
-     *\r
-     * @return string The organization\r
-     */\r
-    public function lookupOrg($addr)\r
-    {\r
-        $ipnum = ip2long($addr);\r
-        if ($ipnum === false) {\r
-            throw new PEAR_Exception("Invalid IP address: " . var_export($addr, true), self::ERR_INVALID_IP);\r
-        }\r
-        if ($this->databaseType !== self::ORG_EDITION) {\r
-            throw new PEAR_Exception("Invalid database type; lookupOrg() method expects Org/ISP database.", self::ERR_DB_FORMAT);\r
-        }\r
-        return $this->getOrg($ipnum);\r
-    }\r
-\r
-    /**\r
-     * Lookup the region for given IP address.\r
-     * Use this method if you have a Region database.\r
-     *\r
-     * @param string $addr IP address (hostname not allowed).\r
-     *\r
-     * @return array Array containing country code and region: array($country_code, $region)\r
-     *\r
-     * @throws PEAR_Exception - if IP address is invalid.\r
-     */\r
-    public function lookupRegion($addr)\r
-    {\r
-        $ipnum = ip2long($addr);\r
-        if ($ipnum === false) {\r
-            throw new PEAR_Exception("Invalid IP address: " . var_export($addr, true), self::ERR_INVALID_IP);\r
-        }\r
-        if ($this->databaseType !== self::REGION_EDITION_REV0 && $this->databaseType !== self::REGION_EDITION_REV1) {\r
-            throw new PEAR_Exception("Invalid database type; lookupRegion() method expects Region database.", self::ERR_DB_FORMAT);\r
-        }\r
-        return $this->getRegion($ipnum);\r
-    }\r
-\r
-    /**\r
-     * Lookup the location record for given IP address.\r
-     * Use this method if you have a City database.\r
-     *\r
-     * @param string $addr IP address (hostname not allowed).\r
-     *\r
-     * @return Net_GeoIP_Location The full location record.\r
-     *\r
-     * @throws PEAR_Exception - if IP address is invalid.\r
-     */\r
-    public function lookupLocation($addr)\r
-    {\r
-        include_once 'Net/GeoIP/Location.php';\r
-        $ipnum = ip2long($addr);\r
-        if ($ipnum === false) {\r
-            throw new PEAR_Exception("Invalid IP address: " . var_export($addr, true), self::ERR_INVALID_IP);\r
-        }\r
-        if ($this->databaseType !== self::CITY_EDITION_REV0 && $this->databaseType !== self::CITY_EDITION_REV1) {\r
-            throw new PEAR_Exception("Invalid database type; lookupLocation() method expects City database.");\r
-        }\r
-        return $this->getRecord($ipnum);\r
-    }\r
-\r
-    /**\r
-     * Seek and return organization (or ISP) name for converted IP addr.\r
-     *\r
-     * @param int $ipnum Converted IP address.\r
-     *\r
-     * @return string The organization\r
-     */\r
-    protected function getOrg($ipnum)\r
-    {\r
-        $seek_org = $this->seekCountry($ipnum);\r
-        if ($seek_org == $this->databaseSegments) {\r
-            return null;\r
-        }\r
-        $record_pointer = $seek_org + (2 * $this->recordLength - 1) * $this->databaseSegments;\r
-        if ($this->flags & self::SHARED_MEMORY) {\r
-            $org_buf = shmop_read($this->shmid, $record_pointer, self::MAX_ORG_RECORD_LENGTH);\r
-        } else {\r
-            fseek($this->filehandle, $record_pointer, SEEK_SET);\r
-            $org_buf = fread($this->filehandle, self::MAX_ORG_RECORD_LENGTH);\r
-        }\r
-        $org_buf = substr($org_buf, 0, strpos($org_buf, 0));\r
-        return $org_buf;\r
-    }\r
-\r
-    /**\r
-     * Seek and return the region info (array containing country code and region name) for converted IP addr.\r
-     *\r
-     * @param int $ipnum Converted IP address.\r
-     *\r
-     * @return array Array containing country code and region: array($country_code, $region)\r
-     */\r
-    protected function getRegion($ipnum)\r
-    {\r
-        if ($this->databaseType == self::REGION_EDITION_REV0) {\r
-            $seek_region = $this->seekCountry($ipnum) - self::STATE_BEGIN_REV0;\r
-            if ($seek_region >= 1000) {\r
-                $country_code = "US";\r
-                $region = chr(($seek_region - 1000)/26 + 65) . chr(($seek_region - 1000)%26 + 65);\r
-            } else {\r
-                $country_code = self::$COUNTRY_CODES[$seek_region];\r
-                $region = "";\r
-            }\r
-            return array($country_code, $region);\r
-        } elseif ($this->databaseType == self::REGION_EDITION_REV1) {\r
-            $seek_region = $this->seekCountry($ipnum) - self::STATE_BEGIN_REV1;\r
-            //print $seek_region;\r
-            if ($seek_region < self::US_OFFSET) {\r
-                $country_code = "";\r
-                $region = "";\r
-            } elseif ($seek_region < self::CANADA_OFFSET) {\r
-                $country_code = "US";\r
-                $region = chr(($seek_region - self::US_OFFSET)/26 + 65) . chr(($seek_region - self::US_OFFSET)%26 + 65);\r
-            } elseif ($seek_region < self::WORLD_OFFSET) {\r
-                $country_code = "CA";\r
-                $region = chr(($seek_region - self::CANADA_OFFSET)/26 + 65) . chr(($seek_region - self::CANADA_OFFSET)%26 + 65);\r
-            } else {\r
-                $country_code = self::$COUNTRY_CODES[($seek_region - self::WORLD_OFFSET) / self::FIPS_RANGE];\r
-                $region = "";\r
-            }\r
-            return array ($country_code,$region);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Seek and populate Net_GeoIP_Location object for converted IP addr.\r
-     * Note: this\r
-     *\r
-     * @param int $ipnum Converted IP address.\r
-     *\r
-     * @return Net_GeoIP_Location\r
-     */\r
-    protected function getRecord($ipnum)\r
-    {\r
-        $seek_country = $this->seekCountry($ipnum);\r
-        if ($seek_country == $this->databaseSegments) {\r
-            return null;\r
-        }\r
-\r
-        $record_pointer = $seek_country + (2 * $this->recordLength - 1) * $this->databaseSegments;\r
-\r
-        if ($this->flags & self::SHARED_MEMORY) {\r
-            $record_buf = shmop_read($this->shmid, $record_pointer, self::FULL_RECORD_LENGTH);\r
-        } else {\r
-            fseek($this->filehandle, $record_pointer, SEEK_SET);\r
-            $record_buf = fread($this->filehandle, self::FULL_RECORD_LENGTH);\r
-        }\r
-\r
-        $record = new Net_GeoIP_Location();\r
-\r
-        $record_buf_pos = 0;\r
-        $char = ord(substr($record_buf, $record_buf_pos, 1));\r
-\r
-        $record->countryCode  = self::$COUNTRY_CODES[$char];\r
-        $record->countryCode3 = self::$COUNTRY_CODES3[$char];\r
-        $record->countryName  = self::$COUNTRY_NAMES[$char];\r
-        $record_buf_pos++;\r
-        $str_length = 0;\r
-\r
-        //get region\r
-        $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1));\r
-        while ($char != 0) {\r
-            $str_length++;\r
-            $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1));\r
-        }\r
-        if ($str_length > 0) {\r
-            $record->region = substr($record_buf, $record_buf_pos, $str_length);\r
-        }\r
-        $record_buf_pos += $str_length + 1;\r
-        $str_length = 0;\r
-\r
-        //get city\r
-        $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1));\r
-        while ($char != 0) {\r
-            $str_length++;\r
-            $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1));\r
-        }\r
-        if ($str_length > 0) {\r
-            $record->city = substr($record_buf, $record_buf_pos, $str_length);\r
-        }\r
-        $record_buf_pos += $str_length + 1;\r
-        $str_length = 0;\r
-\r
-        //get postal code\r
-        $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1));\r
-        while ($char != 0) {\r
-            $str_length++;\r
-            $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1));\r
-        }\r
-        if ($str_length > 0) {\r
-            $record->postalCode = substr($record_buf, $record_buf_pos, $str_length);\r
-        }\r
-        $record_buf_pos += $str_length + 1;\r
-        $str_length = 0;\r
-        $latitude   = 0;\r
-        $longitude  = 0;\r
-        for ($j = 0;$j < 3; ++$j) {\r
-            $char = ord(substr($record_buf, $record_buf_pos++, 1));\r
-            $latitude += ($char << ($j * 8));\r
-        }\r
-        $record->latitude = ($latitude/10000) - 180;\r
-\r
-        for ($j = 0;$j < 3; ++$j) {\r
-            $char = ord(substr($record_buf, $record_buf_pos++, 1));\r
-            $longitude += ($char << ($j * 8));\r
-        }\r
-        $record->longitude = ($longitude/10000) - 180;\r
-\r
-        if ($this->databaseType === self::CITY_EDITION_REV1) {\r
-            $dmaarea_combo = 0;\r
-            if ($record->countryCode == "US") {\r
-                for ($j = 0;$j < 3;++$j) {\r
-                    $char = ord(substr($record_buf, $record_buf_pos++, 1));\r
-                    $dmaarea_combo += ($char << ($j * 8));\r
-                }\r
-                $record->dmaCode = floor($dmaarea_combo/1000);\r
-                $record->areaCode = $dmaarea_combo%1000;\r
-            }\r
-        }\r
-\r
-        return $record;\r
-    }\r
-\r
-}\r
-\r
+<?php
+/**
+ * +----------------------------------------------------------------------+
+ * | PHP version 5                                                        |
+ * +----------------------------------------------------------------------+
+ * | Copyright (C) 2004 MaxMind LLC                                       |
+ * +----------------------------------------------------------------------+
+ * | This library is free software; you can redistribute it and/or        |
+ * | modify it under the terms of the GNU Lesser General Public           |
+ * | License as published by the Free Software Foundation; either         |
+ * | version 2.1 of the License, or (at your option) any later version.   |
+ * |                                                                      |
+ * | This library is distributed in the hope that it will be useful,      |
+ * | but WITHOUT ANY WARRANTY; without even the implied warranty of       |
+ * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |
+ * | Lesser General Public License for more details.                      |
+ * |                                                                      |
+ * | You should have received a copy of the GNU Lesser General Public     |
+ * | License along with this library; if not, write to the Free Software  |
+ * | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 |
+ * | USA, or view it online at http://www.gnu.org/licenses/lgpl.txt.      |
+ * +----------------------------------------------------------------------+
+ * | Authors: Jim Winstead <jimw@apache.org> (original Maxmind version)   |
+ * |          Hans Lellelid <hans@xmpl.org>                               |
+ * +----------------------------------------------------------------------+
+ *
+ * @category Net
+ * @package  Net_GeoIP
+ * @author   Jim Winstead <jimw@apache.org> (original Maxmind PHP API)
+ * @author   Hans Lellelid <hans@xmpl.org>
+ * @license  LGPL http://www.gnu.org/licenses/lgpl.txt
+ * @link     http://pear.php.net/package/Net_GeoIp
+ * $Id$
+ */
+
+require_once 'PEAR/Exception.php';
+
+/**
+ * GeoIP class provides an API for performing geo-location lookups based on IP
+ * address.
+ *
+ * To use this class you must have a [binary version] GeoIP database. There is
+ * a free GeoIP country database which can be obtained from Maxmind:
+ * {@link http://www.maxmind.com/app/geoip_country}
+ *
+ *
+ * <b>SIMPLE USE</b>
+ *
+ *
+ * Create an instance:
+ *
+ * <code>
+ * $geoip = Net_GeoIP::getInstance('/path/to/geoipdb.dat', Net_GeoIP::SHARED_MEMORY);
+ * </code>
+ *
+ * Depending on which database you are using (free, or one of paid versions)
+ * you must use appropriate lookup method:
+ *
+ * <code>
+ * // for free country db:
+ * $country_name = $geoip->lookupCountryName($_SERVER['REMOTE_ADDR']);
+ * $country_code = $geoip->lookupCountryCode($_SERVER['REMOTE_ADDR']);
+ *
+ * // for [non-free] region db:
+ * list($ctry_code, $region) = $geoip->lookupRegion($_SERVER['REMOTE_ADDR']);
+ *
+ * // for [non-free] city db:
+ * $location = $geoip->lookupLocation($_SERVER['REMOTE_ADDR']);
+ * print "city: " . $location->city . ", " . $location->region;
+ * print "lat: " . $location->latitude . ", long: " . $location->longitude;
+ *
+ * // for organization or ISP db:
+ * $org_or_isp_name = $geoip->lookupOrg($_SERVER['REMOTE_ADDR']);
+ * </code>
+ *
+ *
+ * <b>MULTIPLE INSTANCES</b>
+ *
+ *
+ * You can have several instances of this class, one for each database file
+ * you are using.  You should use the static getInstance() singleton method
+ * to save on overhead of setting up database segments.  Note that only one
+ * instance is stored per filename, and any flags will be ignored if an
+ * instance already exists for the specifiedfilename.
+ *
+ * <b>Special note on using SHARED_MEMORY flag</b>
+ *
+ * If you are using SHARED_MEMORY (shmop) you can only use SHARED_MEMORY for
+ * one (1) instance  (i.e. for one database). Any subsequent attempts to
+ * instantiate using SHARED_MEMORY will read the same shared memory block
+ * already initialized, and therefore will cause problems since the expected
+ * database format won't match the database in the shared memory block.
+ *
+ * Note that there is no easy way to flag "nice errors" to prevent attempts
+ * to create new instances using SHARED_MEMORY flag and it is also not posible
+ * (in a safe way) to allow new instances to overwrite the shared memory block.
+ *
+ * In short, is you are using multiple databses, use the SHARED_MEMORY flag
+ * with care.
+ *
+ *
+ * <b>LOOKUPS ON HOSTNAMES</b>
+ *
+ *
+ * Note that this PHP API does NOT support lookups on hostnames.  This is so
+ * that the public API can be kept simple and so that the lookup functions
+ * don't need to try name lookups if IP lookup fails (which would be the only
+ * way to keep the API simple and support name-based lookups).
+ *
+ * If you do not know the IP address, you can convert an name to IP very
+ * simply using PHP native functions or other libraries:
+ *
+ * <code>
+ *     $geoip->lookupCountryName(gethostbyname('www.sunset.se'));
+ * </code>
+ *
+ * Or, if you don't know whether an address is a name or ip address, use
+ * application-level logic:
+ *
+ * <code>
+ * if (ip2long($ip_or_name) === false) {
+ *   $ip = gethostbyname($ip_or_name);
+ * } else {
+ *   $ip = $ip_or_name;
+ * }
+ * $ctry = $geoip->lookupCountryName($ip);
+ * </code>
+ *
+ * @category Net
+ * @package  Net_GeoIP
+ * @author   Jim Winstead <jimw@apache.org> (original Maxmind PHP API)
+ * @author   Hans Lellelid <hans@xmpl.org>
+ * @license  LGPL http://www.gnu.org/licenses/lgpl.txt
+ * @link     http://pear.php.net/package/Net_GeoIp
+ */
+class Net_GeoIP
+{
+    /**
+     * Exception error code used for invalid IP address.
+     */
+    const ERR_INVALID_IP =  218624992; // crc32('Net_GeoIP::ERR_INVALID_IP')
+
+    /**
+     * Exception error code when there is a DB-format-related error.
+     */
+    const ERR_DB_FORMAT = 866184008; // crc32('Net_GeoIP::ERR_DB_FORMAT')
+
+    public static $COUNTRY_CODES = array(
+      "", "AP", "EU", "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AN", "AO", "AQ",
+      "AR", "AS", "AT", "AU", "AW", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH",
+      "BI", "BJ", "BM", "BN", "BO", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA",
+      "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU",
+      "CV", "CX", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG",
+      "EH", "ER", "ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", "FX", "GA", "GB",
+      "GD", "GE", "GF", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT",
+      "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", "ID", "IE", "IL", "IN",
+      "IO", "IQ", "IR", "IS", "IT", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM",
+      "KN", "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS",
+      "LT", "LU", "LV", "LY", "MA", "MC", "MD", "MG", "MH", "MK", "ML", "MM", "MN",
+      "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA",
+      "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", "OM", "PA",
+      "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY",
+      "QA", "RE", "RO", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI",
+      "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "ST", "SV", "SY", "SZ", "TC", "TD",
+      "TF", "TG", "TH", "TJ", "TK", "TM", "TN", "TO", "TL", "TR", "TT", "TV", "TW",
+      "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN",
+      "VU", "WF", "WS", "YE", "YT", "RS", "ZA", "ZM", "ME", "ZW", "A1", "A2", "O1",
+      "AX", "GG", "IM", "JE", "BL", "MF"
+        );
+
+    public static $COUNTRY_CODES3 = array(
+    "","AP","EU","AND","ARE","AFG","ATG","AIA","ALB","ARM","ANT","AGO","AQ","ARG",
+    "ASM","AUT","AUS","ABW","AZE","BIH","BRB","BGD","BEL","BFA","BGR","BHR","BDI",
+    "BEN","BMU","BRN","BOL","BRA","BHS","BTN","BV","BWA","BLR","BLZ","CAN","CC",
+    "COD","CAF","COG","CHE","CIV","COK","CHL","CMR","CHN","COL","CRI","CUB","CPV",
+    "CX","CYP","CZE","DEU","DJI","DNK","DMA","DOM","DZA","ECU","EST","EGY","ESH",
+    "ERI","ESP","ETH","FIN","FJI","FLK","FSM","FRO","FRA","FX","GAB","GBR","GRD",
+    "GEO","GUF","GHA","GIB","GRL","GMB","GIN","GLP","GNQ","GRC","GS","GTM","GUM",
+    "GNB","GUY","HKG","HM","HND","HRV","HTI","HUN","IDN","IRL","ISR","IND","IO",
+    "IRQ","IRN","ISL","ITA","JAM","JOR","JPN","KEN","KGZ","KHM","KIR","COM","KNA",
+    "PRK","KOR","KWT","CYM","KAZ","LAO","LBN","LCA","LIE","LKA","LBR","LSO","LTU",
+    "LUX","LVA","LBY","MAR","MCO","MDA","MDG","MHL","MKD","MLI","MMR","MNG","MAC",
+    "MNP","MTQ","MRT","MSR","MLT","MUS","MDV","MWI","MEX","MYS","MOZ","NAM","NCL",
+    "NER","NFK","NGA","NIC","NLD","NOR","NPL","NRU","NIU","NZL","OMN","PAN","PER",
+    "PYF","PNG","PHL","PAK","POL","SPM","PCN","PRI","PSE","PRT","PLW","PRY","QAT",
+    "REU","ROU","RUS","RWA","SAU","SLB","SYC","SDN","SWE","SGP","SHN","SVN","SJM",
+    "SVK","SLE","SMR","SEN","SOM","SUR","STP","SLV","SYR","SWZ","TCA","TCD","TF",
+    "TGO","THA","TJK","TKL","TLS","TKM","TUN","TON","TUR","TTO","TUV","TWN","TZA",
+    "UKR","UGA","UM","USA","URY","UZB","VAT","VCT","VEN","VGB","VIR","VNM","VUT",
+    "WLF","WSM","YEM","YT","SRB","ZAF","ZMB","MNE","ZWE","A1","A2","O1",
+    "ALA","GGY","IMN","JEY","BLM","MAF"
+        );
+
+    public static $COUNTRY_NAMES = array(
+        "", "Asia/Pacific Region", "Europe", "Andorra", "United Arab Emirates",
+        "Afghanistan", "Antigua and Barbuda", "Anguilla", "Albania", "Armenia",
+        "Netherlands Antilles", "Angola", "Antarctica", "Argentina", "American Samoa",
+        "Austria", "Australia", "Aruba", "Azerbaijan", "Bosnia and Herzegovina",
+        "Barbados", "Bangladesh", "Belgium", "Burkina Faso", "Bulgaria", "Bahrain",
+        "Burundi", "Benin", "Bermuda", "Brunei Darussalam", "Bolivia", "Brazil",
+        "Bahamas", "Bhutan", "Bouvet Island", "Botswana", "Belarus", "Belize",
+        "Canada", "Cocos (Keeling) Islands", "Congo, The Democratic Republic of the",
+        "Central African Republic", "Congo", "Switzerland", "Cote D'Ivoire", "Cook Islands",
+        "Chile", "Cameroon", "China", "Colombia", "Costa Rica", "Cuba", "Cape Verde",
+        "Christmas Island", "Cyprus", "Czech Republic", "Germany", "Djibouti",
+        "Denmark", "Dominica", "Dominican Republic", "Algeria", "Ecuador", "Estonia",
+        "Egypt", "Western Sahara", "Eritrea", "Spain", "Ethiopia", "Finland", "Fiji",
+        "Falkland Islands (Malvinas)", "Micronesia, Federated States of", "Faroe Islands",
+        "France", "France, Metropolitan", "Gabon", "United Kingdom",
+        "Grenada", "Georgia", "French Guiana", "Ghana", "Gibraltar", "Greenland",
+        "Gambia", "Guinea", "Guadeloupe", "Equatorial Guinea", "Greece", "South Georgia and the South Sandwich Islands",
+        "Guatemala", "Guam", "Guinea-Bissau",
+        "Guyana", "Hong Kong", "Heard Island and McDonald Islands", "Honduras",
+        "Croatia", "Haiti", "Hungary", "Indonesia", "Ireland", "Israel", "India",
+        "British Indian Ocean Territory", "Iraq", "Iran, Islamic Republic of",
+        "Iceland", "Italy", "Jamaica", "Jordan", "Japan", "Kenya", "Kyrgyzstan",
+        "Cambodia", "Kiribati", "Comoros", "Saint Kitts and Nevis", "Korea, Democratic People's Republic of",
+        "Korea, Republic of", "Kuwait", "Cayman Islands",
+        "Kazakstan", "Lao People's Democratic Republic", "Lebanon", "Saint Lucia",
+        "Liechtenstein", "Sri Lanka", "Liberia", "Lesotho", "Lithuania", "Luxembourg",
+        "Latvia", "Libyan Arab Jamahiriya", "Morocco", "Monaco", "Moldova, Republic of",
+        "Madagascar", "Marshall Islands", "Macedonia",
+        "Mali", "Myanmar", "Mongolia", "Macau", "Northern Mariana Islands",
+        "Martinique", "Mauritania", "Montserrat", "Malta", "Mauritius", "Maldives",
+        "Malawi", "Mexico", "Malaysia", "Mozambique", "Namibia", "New Caledonia",
+        "Niger", "Norfolk Island", "Nigeria", "Nicaragua", "Netherlands", "Norway",
+        "Nepal", "Nauru", "Niue", "New Zealand", "Oman", "Panama", "Peru", "French Polynesia",
+        "Papua New Guinea", "Philippines", "Pakistan", "Poland", "Saint Pierre and Miquelon",
+        "Pitcairn Islands", "Puerto Rico", "Palestinian Territory",
+        "Portugal", "Palau", "Paraguay", "Qatar", "Reunion", "Romania",
+        "Russian Federation", "Rwanda", "Saudi Arabia", "Solomon Islands",
+        "Seychelles", "Sudan", "Sweden", "Singapore", "Saint Helena", "Slovenia",
+        "Svalbard and Jan Mayen", "Slovakia", "Sierra Leone", "San Marino", "Senegal",
+        "Somalia", "Suriname", "Sao Tome and Principe", "El Salvador", "Syrian Arab Republic",
+        "Swaziland", "Turks and Caicos Islands", "Chad", "French Southern Territories",
+        "Togo", "Thailand", "Tajikistan", "Tokelau", "Turkmenistan",
+        "Tunisia", "Tonga", "Timor-Leste", "Turkey", "Trinidad and Tobago", "Tuvalu",
+        "Taiwan", "Tanzania, United Republic of", "Ukraine",
+        "Uganda", "United States Minor Outlying Islands", "United States", "Uruguay",
+        "Uzbekistan", "Holy See (Vatican City State)", "Saint Vincent and the Grenadines",
+        "Venezuela", "Virgin Islands, British", "Virgin Islands, U.S.",
+        "Vietnam", "Vanuatu", "Wallis and Futuna", "Samoa", "Yemen", "Mayotte",
+        "Serbia", "South Africa", "Zambia", "Montenegro", "Zimbabwe",
+        "Anonymous Proxy","Satellite Provider","Other",
+        "Aland Islands","Guernsey","Isle of Man","Jersey","Saint Barthelemy","Saint Martin"
+        );
+
+    // storage / caching flags
+    const STANDARD = 0;
+    const MEMORY_CACHE = 1;
+    const SHARED_MEMORY = 2;
+
+    // Database structure constants
+    const COUNTRY_BEGIN = 16776960;
+    const STATE_BEGIN_REV0 = 16700000;
+    const STATE_BEGIN_REV1 = 16000000;
+
+    const STRUCTURE_INFO_MAX_SIZE = 20;
+    const DATABASE_INFO_MAX_SIZE = 100;
+    const COUNTRY_EDITION = 106;
+    const REGION_EDITION_REV0 = 112;
+    const REGION_EDITION_REV1 = 3;
+    const CITY_EDITION_REV0 = 111;
+    const CITY_EDITION_REV1 = 2;
+    const ORG_EDITION = 110;
+    const SEGMENT_RECORD_LENGTH = 3;
+    const STANDARD_RECORD_LENGTH = 3;
+    const ORG_RECORD_LENGTH = 4;
+    const MAX_RECORD_LENGTH = 4;
+    const MAX_ORG_RECORD_LENGTH = 300;
+    const FULL_RECORD_LENGTH = 50;
+
+    const US_OFFSET = 1;
+    const CANADA_OFFSET = 677;
+    const WORLD_OFFSET = 1353;
+    const FIPS_RANGE = 360;
+
+    // SHMOP memory address
+    const SHM_KEY = 0x4f415401;
+
+    /**
+     * @var int
+     */
+    private $flags = 0;
+
+    /**
+     * @var resource
+     */
+    private $filehandle;
+
+    /**
+     * @var string
+     */
+    private $memoryBuffer;
+
+    /**
+     * @var int
+     */
+    private $databaseType;
+
+    /**
+     * @var int
+     */
+    private $databaseSegments;
+
+    /**
+     * @var int
+     */
+    private $recordLength;
+
+    /**
+     * The memory addr "id" for use with SHMOP.
+     * @var int
+     */
+    private $shmid;
+
+    /**
+     * Support for singleton pattern.
+     * @var array
+     */
+    private static $instances = array();
+
+    /**
+     * Construct a Net_GeoIP instance.
+     * You should use the getInstance() method if you plan to use multiple databases or
+     * the same database from several different places in your script.
+     *
+     * @param string $filename Path to binary geoip database.
+     * @param int    $flags    Flags
+     *
+     * @see getInstance()
+     */
+    public function __construct($filename = null, $flags = null)
+    {
+        if ($filename !== null) {
+            $this->open($filename, $flags);
+        }
+        // store the instance, so that it will be returned by a call to
+        // getInstance() (with the same db filename).
+        self::$instances[$filename] = $this;
+    }
+
+    /**
+     * Calls the close() function to free any resources.
+     * @see close()
+     *
+     * COMMENTED OUT TO ADDRESS BUG IN PHP 5.0.4, 5.0.5dev.  THIS RESOURCE
+     * SHOULD AUTOMATICALLY BE FREED AT SCRIPT CLOSE, SO A DESTRUCTOR
+     * IS A GOOD IDEA BUT NOT NECESSARILY A NECESSITY.
+    public function __destruct()
+    {
+        $this->close();
+    }
+    */
+
+    /**
+     * Singleton method, use this to get an instance and avoid re-parsing the db.
+     *
+     * Unique instances are instantiated based on the filename of the db. The flags
+     * are ignored -- in that requests to for instance with same filename but different
+     * flags will return the already-instantiated instance.  For example:
+     * <code>
+     * // create new instance with memory_cache enabled
+     * $geoip = Net_GeoIP::getInstance('C:\mydb.dat', Net_GeoIP::MEMORY_CACHE);
+     * ....
+     *
+     * // later in code, request instance with no flags specified.
+     * $geoip = Net_GeoIP::getInstance('C:\mydb.dat');
+     *
+     * // Normally this means no MEMORY_CACHE but since an instance
+     * // with memory cache enabled has already been created for 'C:\mydb.dat', the
+     * // existing instance (with memory cache) will be returned.
+     * </code>
+     *
+     * NOTE: You can only use SHARED_MEMORY flag for one instance!  Any subsquent instances
+     * that attempt to use the SHARED_MEMORY will use the *same* shared memory, which will break
+     * your script.
+     *
+     * @param string $filename Filename
+     * @param int    $flags    Flags that control class behavior.
+     *          + Net_GeoIp::SHARED_MEMORY
+     *             Use SHMOP to share a db among multiple PHP instances.
+     *             NOTE: ONLY ONE GEOIP INSTANCE CAN USE SHARED MEMORY!!!
+     *          + Net_GeoIp::MEMORY_CACHE
+     *             Store the full contents of the database in memory for current script.
+     *             This is useful if you access the database several times in a script.
+     *          + Net_GeoIp::STANDARD
+     *             [default] standard no-cache version.
+     *
+     * @return Net_GeoIP
+     */
+    public static function getInstance($filename = null, $flags = null)
+    {
+        if (!isset(self::$instances[$filename])) {
+            self::$instances[$filename] = new Net_GeoIP($filename, $flags);
+        }
+        return self::$instances[$filename];
+    }
+
+    /**
+     * Opens geoip database at filename and with specified flags.
+     *
+     * @param string $filename File to open
+     * @param int    $flags    Flags
+     *
+     * @return void
+     *
+     * @throws PEAR_Exception if unable to open specified file or shared memory.
+     */
+    public function open($filename, $flags = null)
+    {
+        if ($flags !== null) {
+            $this->flags = $flags;
+        }
+        if ($this->flags & self::SHARED_MEMORY) {
+            $this->shmid = @shmop_open(self::SHM_KEY, "a", 0, 0);
+            if ($this->shmid === false) {
+                $this->loadSharedMemory($filename);
+                $this->shmid = @shmop_open(self::SHM_KEY, "a", 0, 0);
+                if ($this->shmid === false) { // should never be false as loadSharedMemory() will throw Exc if cannot create
+                    throw new PEAR_Exception("Unable to open shared memory at key: " . dechex(self::SHM_KEY));
+                }
+            }
+        } else {
+            $this->filehandle = fopen($filename, "rb");
+            if (!$this->filehandle) {
+                throw new PEAR_Exception("Unable to open file: $filename");
+            }
+            if ($this->flags & self::MEMORY_CACHE) {
+                $s_array = fstat($this->filehandle);
+                $this->memoryBuffer = fread($this->filehandle, $s_array['size']);
+            }
+        }
+        $this->setupSegments();
+    }
+
+    /**
+     * Loads the database file into shared memory.
+     *
+     * @param string $filename Path to database file to read into shared memory.
+     *
+     * @return void
+     *
+     * @throws PEAR_Exception     - if unable to read the db file.
+     */
+    protected function loadSharedMemory($filename)
+    {
+        $fp = fopen($filename, "rb");
+        if (!$fp) {
+            throw new PEAR_Exception("Unable to open file: $filename");
+        }
+        $s_array = fstat($fp);
+        $size = $s_array['size'];
+
+        if ($shmid = @shmop_open(self::SHM_KEY, "w", 0, 0)) {
+            shmop_delete($shmid);
+            shmop_close($shmid);
+        }
+
+        if ($shmid = @shmop_open(self::SHM_KEY, "c", 0644, $size)) {
+            $offset = 0;
+            while ($offset < $size) {
+                $buf = fread($fp, 524288);
+                shmop_write($shmid, $buf, $offset);
+                $offset += 524288;
+            }
+            shmop_close($shmid);
+        }
+
+        fclose($fp);
+    }
+
+    /**
+     * Parses the database file to determine what kind of database is being used and setup
+     * segment sizes and start points that will be used by the seek*() methods later.
+     *
+     * @return void
+     */
+    protected function setupSegments()
+    {
+
+        $this->databaseType = self::COUNTRY_EDITION;
+        $this->recordLength = self::STANDARD_RECORD_LENGTH;
+
+        if ($this->flags & self::SHARED_MEMORY) {
+
+            $offset = shmop_size($this->shmid) - 3;
+            for ($i = 0; $i < self::STRUCTURE_INFO_MAX_SIZE; $i++) {
+                $delim = shmop_read($this->shmid, $offset, 3);
+                $offset += 3;
+                if ($delim == (chr(255).chr(255).chr(255))) {
+                    $this->databaseType = ord(shmop_read($this->shmid, $offset, 1));
+                    $offset++;
+                    if ($this->databaseType === self::REGION_EDITION_REV0) {
+                        $this->databaseSegments = self::STATE_BEGIN_REV0;
+                    } elseif ($this->databaseType === self::REGION_EDITION_REV1) {
+                        $this->databaseSegments = self::STATE_BEGIN_REV1;
+                    } elseif (($this->databaseType === self::CITY_EDITION_REV0)
+                                || ($this->databaseType === self::CITY_EDITION_REV1)
+                                || ($this->databaseType === self::ORG_EDITION)) {
+                        $this->databaseSegments = 0;
+                        $buf = shmop_read($this->shmid, $offset, self::SEGMENT_RECORD_LENGTH);
+                        for ($j = 0; $j < self::SEGMENT_RECORD_LENGTH; $j++) {
+                            $this->databaseSegments += (ord($buf[$j]) << ($j * 8));
+                        }
+                        if ($this->databaseType === self::ORG_EDITION) {
+                            $this->recordLength = self::ORG_RECORD_LENGTH;
+                        }
+                    }
+                    break;
+                } else {
+                    $offset -= 4;
+                }
+            }
+            if ($this->databaseType == self::COUNTRY_EDITION) {
+                $this->databaseSegments = self::COUNTRY_BEGIN;
+            }
+
+        } else {
+
+            $filepos = ftell($this->filehandle);
+            fseek($this->filehandle, -3, SEEK_END);
+            for ($i = 0; $i < self::STRUCTURE_INFO_MAX_SIZE; $i++) {
+                $delim = fread($this->filehandle, 3);
+                if ($delim == (chr(255).chr(255).chr(255))) {
+                    $this->databaseType = ord(fread($this->filehandle, 1));
+                    if ($this->databaseType === self::REGION_EDITION_REV0) {
+                        $this->databaseSegments = self::STATE_BEGIN_REV0;
+                    } elseif ($this->databaseType === self::REGION_EDITION_REV1) {
+                        $this->databaseSegments = self::STATE_BEGIN_REV1;
+                    } elseif ($this->databaseType === self::CITY_EDITION_REV0
+                                || $this->databaseType === self::CITY_EDITION_REV1
+                                || $this->databaseType === self::ORG_EDITION) {
+                        $this->databaseSegments = 0;
+                        $buf = fread($this->filehandle, self::SEGMENT_RECORD_LENGTH);
+                        for ($j = 0; $j < self::SEGMENT_RECORD_LENGTH; $j++) {
+                            $this->databaseSegments += (ord($buf[$j]) << ($j * 8));
+                        }
+                        if ($this->databaseType === self::ORG_EDITION) {
+                            $this->recordLength = self::ORG_RECORD_LENGTH;
+                        }
+                    }
+                    break;
+                } else {
+                    fseek($this->filehandle, -4, SEEK_CUR);
+                }
+            }
+            if ($this->databaseType === self::COUNTRY_EDITION) {
+                $this->databaseSegments = self::COUNTRY_BEGIN;
+            }
+            fseek($this->filehandle, $filepos, SEEK_SET);
+
+        }
+    }
+
+    /**
+     * Closes the geoip database.
+     *
+     * @return int Status of close command.
+     */
+    public function close()
+    {
+        if ($this->flags & self::SHARED_MEMORY) {
+            return shmop_close($this->shmid);
+        } else {
+            // right now even if file was cached in RAM the file was not closed
+            // so it's safe to expect no error w/ fclose()
+            return fclose($this->filehandle);
+        }
+    }
+
+    /**
+     * Get the country index.
+     *
+     * This method is called by the lookupCountryCode() and lookupCountryName()
+     * methods.  It lookups up the index ('id') for the country which is the key
+     * for the code and name.
+     *
+     * @param string $addr IP address (hostname not allowed)
+     *
+     * @throws PEAR_Exception  - if IP address is invalid.
+     *                         - if database type is incorrect
+     *
+     * @return string ID for the country
+     */
+    protected function lookupCountryId($addr)
+    {
+        $ipnum = ip2long($addr);
+        if ($ipnum === false) {
+            throw new PEAR_Exception("Invalid IP address: " . var_export($addr, true), self::ERR_INVALID_IP);
+        }
+        if ($this->databaseType !== self::COUNTRY_EDITION) {
+            throw new PEAR_Exception("Invalid database type; lookupCountry*() methods expect Country database.");
+        }
+        return $this->seekCountry($ipnum) - self::COUNTRY_BEGIN;
+    }
+
+    /**
+     * Returns 2-letter country code (e.g. 'CA') for specified IP address.
+     * Use this method if you have a Country database.
+     *
+     * @param string $addr IP address (hostname not allowed).
+     *
+     * @return string 2-letter country code
+     *
+     * @throws PEAR_Exception (see lookupCountryId())
+     * @see lookupCountryId()
+     */
+    public function lookupCountryCode($addr)
+    {
+        return self::$COUNTRY_CODES[$this->lookupCountryId($addr)];
+    }
+
+    /**
+     * Returns full country name for specified IP address.
+     * Use this method if you have a Country database.
+     *
+     * @param string $addr IP address (hostname not allowed).
+     *
+     * @return string Country name
+     * @throws PEAR_Exception (see lookupCountryId())
+     * @see lookupCountryId()
+     */
+    public function lookupCountryName($addr)
+    {
+        return self::$COUNTRY_NAMES[$this->lookupCountryId($addr)];
+    }
+
+    /**
+     * Using the record length and appropriate start points, seek to the country that corresponds
+     * to the converted IP address integer.
+     *
+     * @param int $ipnum Result of ip2long() conversion.
+     *
+     * @return int Offset of start of record.
+     * @throws PEAR_Exception - if fseek() fails on the file or no results after traversing the database (indicating corrupt db).
+     */
+    protected function seekCountry($ipnum)
+    {
+        $offset = 0;
+        for ($depth = 31; $depth >= 0; --$depth) {
+            if ($this->flags & self::MEMORY_CACHE) {
+                  $buf = substr($this->memoryBuffer, 2 * $this->recordLength * $offset, 2 * $this->recordLength);
+            } elseif ($this->flags & self::SHARED_MEMORY) {
+                $buf = shmop_read($this->shmid, 2 * $this->recordLength * $offset, 2 * $this->recordLength);
+            } else {
+                if (fseek($this->filehandle, 2 * $this->recordLength * $offset, SEEK_SET) !== 0) {
+                    throw new PEAR_Exception("fseek failed");
+                }
+                $buf = fread($this->filehandle, 2 * $this->recordLength);
+            }
+            $x = array(0,0);
+            for ($i = 0; $i < 2; ++$i) {
+                for ($j = 0; $j < $this->recordLength; ++$j) {
+                    $x[$i] += ord($buf[$this->recordLength * $i + $j]) << ($j * 8);
+                }
+            }
+            if ($ipnum & (1 << $depth)) {
+                if ($x[1] >= $this->databaseSegments) {
+                    return $x[1];
+                }
+                $offset = $x[1];
+            } else {
+                if ($x[0] >= $this->databaseSegments) {
+                    return $x[0];
+                }
+                $offset = $x[0];
+            }
+        }
+        throw new PEAR_Exception("Error traversing database - perhaps it is corrupt?");
+    }
+
+    /**
+     * Lookup the organization (or ISP) for given IP address.
+     * Use this method if you have an Organization/ISP database.
+     *
+     * @param string $addr IP address (hostname not allowed).
+     *
+     * @throws PEAR_Exception  - if IP address is invalid.
+     *                         - if database is of wrong type
+     *
+     * @return string The organization
+     */
+    public function lookupOrg($addr)
+    {
+        $ipnum = ip2long($addr);
+        if ($ipnum === false) {
+            throw new PEAR_Exception("Invalid IP address: " . var_export($addr, true), self::ERR_INVALID_IP);
+        }
+        if ($this->databaseType !== self::ORG_EDITION) {
+            throw new PEAR_Exception("Invalid database type; lookupOrg() method expects Org/ISP database.", self::ERR_DB_FORMAT);
+        }
+        return $this->getOrg($ipnum);
+    }
+
+    /**
+     * Lookup the region for given IP address.
+     * Use this method if you have a Region database.
+     *
+     * @param string $addr IP address (hostname not allowed).
+     *
+     * @return array Array containing country code and region: array($country_code, $region)
+     *
+     * @throws PEAR_Exception - if IP address is invalid.
+     */
+    public function lookupRegion($addr)
+    {
+        $ipnum = ip2long($addr);
+        if ($ipnum === false) {
+            throw new PEAR_Exception("Invalid IP address: " . var_export($addr, true), self::ERR_INVALID_IP);
+        }
+        if ($this->databaseType !== self::REGION_EDITION_REV0 && $this->databaseType !== self::REGION_EDITION_REV1) {
+            throw new PEAR_Exception("Invalid database type; lookupRegion() method expects Region database.", self::ERR_DB_FORMAT);
+        }
+        return $this->getRegion($ipnum);
+    }
+
+    /**
+     * Lookup the location record for given IP address.
+     * Use this method if you have a City database.
+     *
+     * @param string $addr IP address (hostname not allowed).
+     *
+     * @return Net_GeoIP_Location The full location record.
+     *
+     * @throws PEAR_Exception - if IP address is invalid.
+     */
+    public function lookupLocation($addr)
+    {
+        include_once 'Net/GeoIP/Location.php';
+        $ipnum = ip2long($addr);
+        if ($ipnum === false) {
+            throw new PEAR_Exception("Invalid IP address: " . var_export($addr, true), self::ERR_INVALID_IP);
+        }
+        if ($this->databaseType !== self::CITY_EDITION_REV0 && $this->databaseType !== self::CITY_EDITION_REV1) {
+            throw new PEAR_Exception("Invalid database type; lookupLocation() method expects City database.");
+        }
+        return $this->getRecord($ipnum);
+    }
+
+    /**
+     * Seek and return organization (or ISP) name for converted IP addr.
+     *
+     * @param int $ipnum Converted IP address.
+     *
+     * @return string The organization
+     */
+    protected function getOrg($ipnum)
+    {
+        $seek_org = $this->seekCountry($ipnum);
+        if ($seek_org == $this->databaseSegments) {
+            return null;
+        }
+        $record_pointer = $seek_org + (2 * $this->recordLength - 1) * $this->databaseSegments;
+        if ($this->flags & self::SHARED_MEMORY) {
+            $org_buf = shmop_read($this->shmid, $record_pointer, self::MAX_ORG_RECORD_LENGTH);
+        } else {
+            fseek($this->filehandle, $record_pointer, SEEK_SET);
+            $org_buf = fread($this->filehandle, self::MAX_ORG_RECORD_LENGTH);
+        }
+        $org_buf = substr($org_buf, 0, strpos($org_buf, 0));
+        return $org_buf;
+    }
+
+    /**
+     * Seek and return the region info (array containing country code and region name) for converted IP addr.
+     *
+     * @param int $ipnum Converted IP address.
+     *
+     * @return array Array containing country code and region: array($country_code, $region)
+     */
+    protected function getRegion($ipnum)
+    {
+        if ($this->databaseType == self::REGION_EDITION_REV0) {
+            $seek_region = $this->seekCountry($ipnum) - self::STATE_BEGIN_REV0;
+            if ($seek_region >= 1000) {
+                $country_code = "US";
+                $region = chr(($seek_region - 1000)/26 + 65) . chr(($seek_region - 1000)%26 + 65);
+            } else {
+                $country_code = self::$COUNTRY_CODES[$seek_region];
+                $region = "";
+            }
+            return array($country_code, $region);
+        } elseif ($this->databaseType == self::REGION_EDITION_REV1) {
+            $seek_region = $this->seekCountry($ipnum) - self::STATE_BEGIN_REV1;
+            //print $seek_region;
+            if ($seek_region < self::US_OFFSET) {
+                $country_code = "";
+                $region = "";
+            } elseif ($seek_region < self::CANADA_OFFSET) {
+                $country_code = "US";
+                $region = chr(($seek_region - self::US_OFFSET)/26 + 65) . chr(($seek_region - self::US_OFFSET)%26 + 65);
+            } elseif ($seek_region < self::WORLD_OFFSET) {
+                $country_code = "CA";
+                $region = chr(($seek_region - self::CANADA_OFFSET)/26 + 65) . chr(($seek_region - self::CANADA_OFFSET)%26 + 65);
+            } else {
+                $country_code = self::$COUNTRY_CODES[($seek_region - self::WORLD_OFFSET) / self::FIPS_RANGE];
+                $region = "";
+            }
+            return array ($country_code,$region);
+        }
+    }
+
+    /**
+     * Seek and populate Net_GeoIP_Location object for converted IP addr.
+     * Note: this
+     *
+     * @param int $ipnum Converted IP address.
+     *
+     * @return Net_GeoIP_Location
+     */
+    protected function getRecord($ipnum)
+    {
+        $seek_country = $this->seekCountry($ipnum);
+        if ($seek_country == $this->databaseSegments) {
+            return null;
+        }
+
+        $record_pointer = $seek_country + (2 * $this->recordLength - 1) * $this->databaseSegments;
+
+        if ($this->flags & self::SHARED_MEMORY) {
+            $record_buf = shmop_read($this->shmid, $record_pointer, self::FULL_RECORD_LENGTH);
+        } else {
+            fseek($this->filehandle, $record_pointer, SEEK_SET);
+            $record_buf = fread($this->filehandle, self::FULL_RECORD_LENGTH);
+        }
+
+        $record = new Net_GeoIP_Location();
+
+        $record_buf_pos = 0;
+        $char = ord(substr($record_buf, $record_buf_pos, 1));
+
+        $record->countryCode  = self::$COUNTRY_CODES[$char];
+        $record->countryCode3 = self::$COUNTRY_CODES3[$char];
+        $record->countryName  = self::$COUNTRY_NAMES[$char];
+        $record_buf_pos++;
+        $str_length = 0;
+
+        //get region
+        $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1));
+        while ($char != 0) {
+            $str_length++;
+            $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1));
+        }
+        if ($str_length > 0) {
+            $record->region = substr($record_buf, $record_buf_pos, $str_length);
+        }
+        $record_buf_pos += $str_length + 1;
+        $str_length = 0;
+
+        //get city
+        $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1));
+        while ($char != 0) {
+            $str_length++;
+            $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1));
+        }
+        if ($str_length > 0) {
+            $record->city = substr($record_buf, $record_buf_pos, $str_length);
+        }
+        $record_buf_pos += $str_length + 1;
+        $str_length = 0;
+
+        //get postal code
+        $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1));
+        while ($char != 0) {
+            $str_length++;
+            $char = ord(substr($record_buf, $record_buf_pos+$str_length, 1));
+        }
+        if ($str_length > 0) {
+            $record->postalCode = substr($record_buf, $record_buf_pos, $str_length);
+        }
+        $record_buf_pos += $str_length + 1;
+        $str_length = 0;
+        $latitude   = 0;
+        $longitude  = 0;
+        for ($j = 0;$j < 3; ++$j) {
+            $char = ord(substr($record_buf, $record_buf_pos++, 1));
+            $latitude += ($char << ($j * 8));
+        }
+        $record->latitude = ($latitude/10000) - 180;
+
+        for ($j = 0;$j < 3; ++$j) {
+            $char = ord(substr($record_buf, $record_buf_pos++, 1));
+            $longitude += ($char << ($j * 8));
+        }
+        $record->longitude = ($longitude/10000) - 180;
+
+        if ($this->databaseType === self::CITY_EDITION_REV1) {
+            $dmaarea_combo = 0;
+            if ($record->countryCode == "US") {
+                for ($j = 0;$j < 3;++$j) {
+                    $char = ord(substr($record_buf, $record_buf_pos++, 1));
+                    $dmaarea_combo += ($char << ($j * 8));
+                }
+                $record->dmaCode = floor($dmaarea_combo/1000);
+                $record->areaCode = $dmaarea_combo%1000;
+            }
+        }
+
+        return $record;
+    }
+
+}
+
index 412254d..253f7b5 100644 (file)
-<?php\r
-/**\r
- * +----------------------------------------------------------------------+\r
- * | PHP version 5                                                        |\r
- * +----------------------------------------------------------------------+\r
- * | Copyright (C) 2004 MaxMind LLC                                       |\r
- * +----------------------------------------------------------------------+\r
- * | This library is free software; you can redistribute it and/or        |\r
- * | modify it under the terms of the GNU Lesser General Public           |\r
- * | License as published by the Free Software Foundation; either         |\r
- * | version 2.1 of the License, or (at your option) any later version.   |\r
- * |                                                                      |\r
- * | This library is distributed in the hope that it will be useful,      |\r
- * | but WITHOUT ANY WARRANTY; without even the implied warranty of       |\r
- * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |\r
- * | Lesser General Public License for more details.                      |\r
- * |                                                                      |\r
- * | You should have received a copy of the GNU Lesser General Public     |\r
- * | License along with this library; if not, write to the Free Software  |\r
- * | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 |\r
- * | USA, or view it online at http://www.gnu.org/licenses/lgpl.txt.      |\r
- * +----------------------------------------------------------------------+\r
- * | Authors: Jim Winstead <jimw@apache.org> (original Maxmind version)   |\r
- * |          Hans Lellelid <hans@xmpl.org>                               |\r
- * +----------------------------------------------------------------------+\r
- *\r
- * @category Net\r
- * @package  Net_GeoIP\r
- * @author   Hans Lellelid <hans@xmpl.org>\r
- * @license  LGPL http://www.gnu.org/licenses/lgpl.txt\r
- * @link     http://pear.php.net/package/Net_GeoIp\r
- * $Id$\r
- */\r
-\r
-/**\r
- * Static class to handle mapping of DMA codes to metro regions.\r
- * \r
- * Use this class with the dmaCode property of the Net_GeoIpLocation object.\r
- * \r
- * <code>\r
- * $region = Net_GeoIPDMA::getMetroRegion($record->dmaCode);\r
- * </code>\r
- * \r
- * @category Net\r
- * @package  Net_GeoIP\r
- * @author   Hans Lellelid <hans@xmpl.org>\r
- * @author   Dmitri Snytkine <d.snytkine@gmail.com>\r
- * @license  LGPL http://www.gnu.org/licenses/lgpl.txt\r
- * @version  $Revision$\r
- * @link     http://pear.php.net/package/Net_GeoIp\r
- */\r
-class Net_GeoIP_DMA\r
-{\r
-    /**\r
-     * Holds DMA -> Metro mapping.\r
-     * @var array\r
-     */\r
-    private static $dmaMap;\r
-    \r
-    /**\r
-     * Initialize\r
-     * \r
-     * @return void\r
-     */\r
-    public static function initialize()\r
-    {\r
-        self::$dmaMap = array(\r
-            500 => 'Portland-Auburn, ME',\r
-            501 => 'New York, NY',\r
-            502 => 'Binghamton, NY',\r
-            503 => 'Macon, GA',\r
-            504 => 'Philadelphia, PA',\r
-            505 => 'Detroit, MI',\r
-            506 => 'Boston, MA',\r
-            507 => 'Savannah, GA',\r
-            508 => 'Pittsburgh, PA',\r
-            509 => 'Ft Wayne, IN',\r
-            510 => 'Cleveland, OH',\r
-            511 => 'Washington, DC',\r
-            512 => 'Baltimore, MD',\r
-            513 => 'Flint, MI',\r
-            514 => 'Buffalo, NY',\r
-            515 => 'Cincinnati, OH',\r
-            516 => 'Erie, PA',\r
-            517 => 'Charlotte, NC',\r
-            518 => 'Greensboro, NC',\r
-            519 => 'Charleston, SC',\r
-            520 => 'Augusta, GA',\r
-            521 => 'Providence, RI',\r
-            522 => 'Columbus, GA',\r
-            523 => 'Burlington, VT',\r
-            524 => 'Atlanta, GA',\r
-            525 => 'Albany, GA',\r
-            526 => 'Utica-Rome, NY',\r
-            527 => 'Indianapolis, IN',\r
-            528 => 'Miami, FL',\r
-            529 => 'Louisville, KY',\r
-            530 => 'Tallahassee, FL',\r
-            531 => 'Tri-Cities, TN',\r
-            532 => 'Albany-Schenectady-Troy, NY',\r
-            533 => 'Hartford, CT',\r
-            534 => 'Orlando, FL',\r
-            535 => 'Columbus, OH',\r
-            536 => 'Youngstown-Warren, OH',\r
-            537 => 'Bangor, ME',\r
-            538 => 'Rochester, NY',\r
-            539 => 'Tampa, FL',\r
-            540 => 'Traverse City-Cadillac, MI',\r
-            541 => 'Lexington, KY',\r
-            542 => 'Dayton, OH',\r
-            543 => 'Springfield-Holyoke, MA',\r
-            544 => 'Norfolk-Portsmouth, VA',\r
-            545 => 'Greenville-New Bern-Washington, NC',\r
-            546 => 'Columbia, SC',\r
-            547 => 'Toledo, OH',\r
-            548 => 'West Palm Beach, FL',\r
-            549 => 'Watertown, NY',\r
-            550 => 'Wilmington, NC',\r
-            551 => 'Lansing, MI',\r
-            552 => 'Presque Isle, ME',\r
-            553 => 'Marquette, MI',\r
-            554 => 'Wheeling, WV',\r
-            555 => 'Syracuse, NY',\r
-            556 => 'Richmond-Petersburg, VA',\r
-            557 => 'Knoxville, TN',\r
-            558 => 'Lima, OH',\r
-            559 => 'Bluefield-Beckley-Oak Hill, WV',\r
-            560 => 'Raleigh-Durham, NC',\r
-            561 => 'Jacksonville, FL',\r
-            563 => 'Grand Rapids, MI',\r
-            564 => 'Charleston-Huntington, WV',\r
-            565 => 'Elmira, NY',\r
-            566 => 'Harrisburg-Lancaster-Lebanon-York, PA',\r
-            567 => 'Greenville-Spartenburg, SC',\r
-            569 => 'Harrisonburg, VA',\r
-            570 => 'Florence-Myrtle Beach, SC',\r
-            571 => 'Ft Myers, FL',\r
-            573 => 'Roanoke-Lynchburg, VA',\r
-            574 => 'Johnstown-Altoona, PA',\r
-            575 => 'Chattanooga, TN',\r
-            576 => 'Salisbury, MD',\r
-            577 => 'Wilkes Barre-Scranton, PA',\r
-            581 => 'Terre Haute, IN',\r
-            582 => 'Lafayette, IN',\r
-            583 => 'Alpena, MI',\r
-            584 => 'Charlottesville, VA',\r
-            588 => 'South Bend, IN',\r
-            592 => 'Gainesville, FL',\r
-            596 => 'Zanesville, OH',\r
-            597 => 'Parkersburg, WV',\r
-            598 => 'Clarksburg-Weston, WV',\r
-            600 => 'Corpus Christi, TX',\r
-            602 => 'Chicago, IL',\r
-            603 => 'Joplin-Pittsburg, MO',\r
-            604 => 'Columbia-Jefferson City, MO',\r
-            605 => 'Topeka, KS',\r
-            606 => 'Dothan, AL',\r
-            609 => 'St Louis, MO',\r
-            610 => 'Rockford, IL',\r
-            611 => 'Rochester-Mason City-Austin, MN',\r
-            612 => 'Shreveport, LA',\r
-            613 => 'Minneapolis-St Paul, MN',\r
-            616 => 'Kansas City, MO',\r
-            617 => 'Milwaukee, WI',\r
-            618 => 'Houston, TX',\r
-            619 => 'Springfield, MO',\r
-            620 => 'Tuscaloosa, AL',\r
-            622 => 'New Orleans, LA',\r
-            623 => 'Dallas-Fort Worth, TX',\r
-            624 => 'Sioux City, IA',\r
-            625 => 'Waco-Temple-Bryan, TX',\r
-            626 => 'Victoria, TX',\r
-            627 => 'Wichita Falls, TX',\r
-            628 => 'Monroe, LA',\r
-            630 => 'Birmingham, AL',\r
-            631 => 'Ottumwa-Kirksville, IA',\r
-            632 => 'Paducah, KY',\r
-            633 => 'Odessa-Midland, TX',\r
-            634 => 'Amarillo, TX',\r
-            635 => 'Austin, TX',\r
-            636 => 'Harlingen, TX',\r
-            637 => 'Cedar Rapids-Waterloo, IA',\r
-            638 => 'St Joseph, MO',\r
-            639 => 'Jackson, TN',\r
-            640 => 'Memphis, TN',\r
-            641 => 'San Antonio, TX',\r
-            642 => 'Lafayette, LA',\r
-            643 => 'Lake Charles, LA',\r
-            644 => 'Alexandria, LA',\r
-            646 => 'Anniston, AL',\r
-            647 => 'Greenwood-Greenville, MS',\r
-            648 => 'Champaign-Springfield-Decatur, IL',\r
-            649 => 'Evansville, IN',\r
-            650 => 'Oklahoma City, OK',\r
-            651 => 'Lubbock, TX',\r
-            652 => 'Omaha, NE',\r
-            656 => 'Panama City, FL',\r
-            657 => 'Sherman, TX',\r
-            658 => 'Green Bay-Appleton, WI',\r
-            659 => 'Nashville, TN',\r
-            661 => 'San Angelo, TX',\r
-            662 => 'Abilene-Sweetwater, TX',\r
-            669 => 'Madison, WI',\r
-            670 => 'Ft Smith-Fay-Springfield, AR',\r
-            671 => 'Tulsa, OK',\r
-            673 => 'Columbus-Tupelo-West Point, MS',\r
-            675 => 'Peoria-Bloomington, IL',\r
-            676 => 'Duluth, MN',\r
-            678 => 'Wichita, KS',\r
-            679 => 'Des Moines, IA',\r
-            682 => 'Davenport-Rock Island-Moline, IL',\r
-            686 => 'Mobile, AL',\r
-            687 => 'Minot-Bismarck-Dickinson, ND',\r
-            691 => 'Huntsville, AL',\r
-            692 => 'Beaumont-Port Author, TX',\r
-            693 => 'Little Rock-Pine Bluff, AR',\r
-            698 => 'Montgomery, AL',\r
-            702 => 'La Crosse-Eau Claire, WI',\r
-            705 => 'Wausau-Rhinelander, WI',\r
-            709 => 'Tyler-Longview, TX',\r
-            710 => 'Hattiesburg-Laurel, MS',\r
-            711 => 'Meridian, MS',\r
-            716 => 'Baton Rouge, LA',\r
-            717 => 'Quincy, IL',\r
-            718 => 'Jackson, MS',\r
-            722 => 'Lincoln-Hastings, NE',\r
-            724 => 'Fargo-Valley City, ND',\r
-            725 => 'Sioux Falls, SD',\r
-            734 => 'Jonesboro, AR',\r
-            736 => 'Bowling Green, KY',\r
-            737 => 'Mankato, MN',\r
-            740 => 'North Platte, NE',\r
-            743 => 'Anchorage, AK',\r
-            744 => 'Honolulu, HI',\r
-            745 => 'Fairbanks, AK',\r
-            746 => 'Biloxi-Gulfport, MS',\r
-            747 => 'Juneau, AK',\r
-            749 => 'Laredo, TX',\r
-            751 => 'Denver, CO',\r
-            752 => 'Colorado Springs, CO',\r
-            753 => 'Phoenix, AZ',\r
-            754 => 'Butte-Bozeman, MT',\r
-            755 => 'Great Falls, MT',\r
-            756 => 'Billings, MT',\r
-            757 => 'Boise, ID',\r
-            758 => 'Idaho Falls-Pocatello, ID',\r
-            759 => 'Cheyenne, WY',\r
-            760 => 'Twin Falls, ID',\r
-            762 => 'Missoula, MT',\r
-            764 => 'Rapid City, SD',\r
-            765 => 'El Paso, TX',\r
-            766 => 'Helena, MT',\r
-            767 => 'Casper-Riverton, WY',\r
-            770 => 'Salt Lake City, UT',\r
-            771 => 'Yuma, AZ',\r
-            773 => 'Grand Junction, CO',\r
-            789 => 'Tucson, AZ',\r
-            790 => 'Albuquerque, NM',\r
-            798 => 'Glendive, MT',\r
-            800 => 'Bakersfield, CA',\r
-            801 => 'Eugene, OR',\r
-            802 => 'Eureka, CA',\r
-            803 => 'Los Angeles, CA',\r
-            804 => 'Palm Springs, CA',\r
-            807 => 'San Francisco, CA',\r
-            810 => 'Yakima-Pasco, WA',\r
-            811 => 'Reno, NV',\r
-            813 => 'Medford-Klamath Falls, OR',\r
-            819 => 'Seattle-Tacoma, WA',\r
-            820 => 'Portland, OR',\r
-            821 => 'Bend, OR',\r
-            825 => 'San Diego, CA',\r
-            828 => 'Monterey-Salinas, CA',\r
-            839 => 'Las Vegas, NV',\r
-            855 => 'Santa Barbara, CA',\r
-            862 => 'Sacramento, CA',\r
-            866 => 'Fresno, CA',\r
-            868 => 'Chico-Redding, CA',\r
-            881 => 'Spokane, WA');\r
-    }\r
-    \r
-    /**\r
-     * Lookup the metro region based on the provided DMA code.\r
-     * \r
-     * @param int $dmaCode The DMA code\r
-     * \r
-     * @return string Metro region name.\r
-     */\r
-    public static function getMetroRegion($dmaCode)\r
-    {\r
-        if ($dmaCode === null) {\r
-            return null;\r
-        }\r
-        if (self::$dmaMap === null) {\r
-            self::initialize();\r
-        }\r
-        return self::$dmaMap[$dmaCode];\r
-    }\r
-\r
-    /**\r
-     * Reverse lookup of DMA code if [exact] metro region name is known.\r
-     * \r
-     * @param string $metro Metro region name.\r
-     * \r
-     * @return int DMA code, or false if not found.\r
-     */\r
-    public static function getDMACode($metro)    \r
-    {\r
-        if (self::$dmaMap === null) {\r
-            self::initialize();\r
-        }\r
-        return array_search($metro, self::$dmaMap);\r
-    }\r
-\r
+<?php
+/**
+ * +----------------------------------------------------------------------+
+ * | PHP version 5                                                        |
+ * +----------------------------------------------------------------------+
+ * | Copyright (C) 2004 MaxMind LLC                                       |
+ * +----------------------------------------------------------------------+
+ * | This library is free software; you can redistribute it and/or        |
+ * | modify it under the terms of the GNU Lesser General Public           |
+ * | License as published by the Free Software Foundation; either         |
+ * | version 2.1 of the License, or (at your option) any later version.   |
+ * |                                                                      |
+ * | This library is distributed in the hope that it will be useful,      |
+ * | but WITHOUT ANY WARRANTY; without even the implied warranty of       |
+ * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |
+ * | Lesser General Public License for more details.                      |
+ * |                                                                      |
+ * | You should have received a copy of the GNU Lesser General Public     |
+ * | License along with this library; if not, write to the Free Software  |
+ * | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 |
+ * | USA, or view it online at http://www.gnu.org/licenses/lgpl.txt.      |
+ * +----------------------------------------------------------------------+
+ * | Authors: Jim Winstead <jimw@apache.org> (original Maxmind version)   |
+ * |          Hans Lellelid <hans@xmpl.org>                               |
+ * +----------------------------------------------------------------------+
+ *
+ * @category Net
+ * @package  Net_GeoIP
+ * @author   Hans Lellelid <hans@xmpl.org>
+ * @license  LGPL http://www.gnu.org/licenses/lgpl.txt
+ * @link     http://pear.php.net/package/Net_GeoIp
+ * $Id$
+ */
+
+/**
+ * Static class to handle mapping of DMA codes to metro regions.
+ * 
+ * Use this class with the dmaCode property of the Net_GeoIpLocation object.
+ * 
+ * <code>
+ * $region = Net_GeoIPDMA::getMetroRegion($record->dmaCode);
+ * </code>
+ * 
+ * @category Net
+ * @package  Net_GeoIP
+ * @author   Hans Lellelid <hans@xmpl.org>
+ * @author   Dmitri Snytkine <d.snytkine@gmail.com>
+ * @license  LGPL http://www.gnu.org/licenses/lgpl.txt
+ * @version  $Revision$
+ * @link     http://pear.php.net/package/Net_GeoIp
+ */
+class Net_GeoIP_DMA
+{
+    /**
+     * Holds DMA -> Metro mapping.
+     * @var array
+     */
+    private static $dmaMap;
+    
+    /**
+     * Initialize
+     * 
+     * @return void
+     */
+    public static function initialize()
+    {
+        self::$dmaMap = array(
+            500 => 'Portland-Auburn, ME',
+            501 => 'New York, NY',
+            502 => 'Binghamton, NY',
+            503 => 'Macon, GA',
+            504 => 'Philadelphia, PA',
+            505 => 'Detroit, MI',
+            506 => 'Boston, MA',
+            507 => 'Savannah, GA',
+            508 => 'Pittsburgh, PA',
+            509 => 'Ft Wayne, IN',
+            510 => 'Cleveland, OH',
+            511 => 'Washington, DC',
+            512 => 'Baltimore, MD',
+            513 => 'Flint, MI',
+            514 => 'Buffalo, NY',
+            515 => 'Cincinnati, OH',
+            516 => 'Erie, PA',
+            517 => 'Charlotte, NC',
+            518 => 'Greensboro, NC',
+            519 => 'Charleston, SC',
+            520 => 'Augusta, GA',
+            521 => 'Providence, RI',
+            522 => 'Columbus, GA',
+            523 => 'Burlington, VT',
+            524 => 'Atlanta, GA',
+            525 => 'Albany, GA',
+            526 => 'Utica-Rome, NY',
+            527 => 'Indianapolis, IN',
+            528 => 'Miami, FL',
+            529 => 'Louisville, KY',
+            530 => 'Tallahassee, FL',
+            531 => 'Tri-Cities, TN',
+            532 => 'Albany-Schenectady-Troy, NY',
+            533 => 'Hartford, CT',
+            534 => 'Orlando, FL',
+            535 => 'Columbus, OH',
+            536 => 'Youngstown-Warren, OH',
+            537 => 'Bangor, ME',
+            538 => 'Rochester, NY',
+            539 => 'Tampa, FL',
+            540 => 'Traverse City-Cadillac, MI',
+            541 => 'Lexington, KY',
+            542 => 'Dayton, OH',
+            543 => 'Springfield-Holyoke, MA',
+            544 => 'Norfolk-Portsmouth, VA',
+            545 => 'Greenville-New Bern-Washington, NC',
+            546 => 'Columbia, SC',
+            547 => 'Toledo, OH',
+            548 => 'West Palm Beach, FL',
+            549 => 'Watertown, NY',
+            550 => 'Wilmington, NC',
+            551 => 'Lansing, MI',
+            552 => 'Presque Isle, ME',
+            553 => 'Marquette, MI',
+            554 => 'Wheeling, WV',
+            555 => 'Syracuse, NY',
+            556 => 'Richmond-Petersburg, VA',
+            557 => 'Knoxville, TN',
+            558 => 'Lima, OH',
+            559 => 'Bluefield-Beckley-Oak Hill, WV',
+            560 => 'Raleigh-Durham, NC',
+            561 => 'Jacksonville, FL',
+            563 => 'Grand Rapids, MI',
+            564 => 'Charleston-Huntington, WV',
+            565 => 'Elmira, NY',
+            566 => 'Harrisburg-Lancaster-Lebanon-York, PA',
+            567 => 'Greenville-Spartenburg, SC',
+            569 => 'Harrisonburg, VA',
+            570 => 'Florence-Myrtle Beach, SC',
+            571 => 'Ft Myers, FL',
+            573 => 'Roanoke-Lynchburg, VA',
+            574 => 'Johnstown-Altoona, PA',
+            575 => 'Chattanooga, TN',
+            576 => 'Salisbury, MD',
+            577 => 'Wilkes Barre-Scranton, PA',
+            581 => 'Terre Haute, IN',
+            582 => 'Lafayette, IN',
+            583 => 'Alpena, MI',
+            584 => 'Charlottesville, VA',
+            588 => 'South Bend, IN',
+            592 => 'Gainesville, FL',
+            596 => 'Zanesville, OH',
+            597 => 'Parkersburg, WV',
+            598 => 'Clarksburg-Weston, WV',
+            600 => 'Corpus Christi, TX',
+            602 => 'Chicago, IL',
+            603 => 'Joplin-Pittsburg, MO',
+            604 => 'Columbia-Jefferson City, MO',
+            605 => 'Topeka, KS',
+            606 => 'Dothan, AL',
+            609 => 'St Louis, MO',
+            610 => 'Rockford, IL',
+            611 => 'Rochester-Mason City-Austin, MN',
+            612 => 'Shreveport, LA',
+            613 => 'Minneapolis-St Paul, MN',
+            616 => 'Kansas City, MO',
+            617 => 'Milwaukee, WI',
+            618 => 'Houston, TX',
+            619 => 'Springfield, MO',
+            620 => 'Tuscaloosa, AL',
+            622 => 'New Orleans, LA',
+            623 => 'Dallas-Fort Worth, TX',
+            624 => 'Sioux City, IA',
+            625 => 'Waco-Temple-Bryan, TX',
+            626 => 'Victoria, TX',
+            627 => 'Wichita Falls, TX',
+            628 => 'Monroe, LA',
+            630 => 'Birmingham, AL',
+            631 => 'Ottumwa-Kirksville, IA',
+            632 => 'Paducah, KY',
+            633 => 'Odessa-Midland, TX',
+            634 => 'Amarillo, TX',
+            635 => 'Austin, TX',
+            636 => 'Harlingen, TX',
+            637 => 'Cedar Rapids-Waterloo, IA',
+            638 => 'St Joseph, MO',
+            639 => 'Jackson, TN',
+            640 => 'Memphis, TN',
+            641 => 'San Antonio, TX',
+            642 => 'Lafayette, LA',
+            643 => 'Lake Charles, LA',
+            644 => 'Alexandria, LA',
+            646 => 'Anniston, AL',
+            647 => 'Greenwood-Greenville, MS',
+            648 => 'Champaign-Springfield-Decatur, IL',
+            649 => 'Evansville, IN',
+            650 => 'Oklahoma City, OK',
+            651 => 'Lubbock, TX',
+            652 => 'Omaha, NE',
+            656 => 'Panama City, FL',
+            657 => 'Sherman, TX',
+            658 => 'Green Bay-Appleton, WI',
+            659 => 'Nashville, TN',
+            661 => 'San Angelo, TX',
+            662 => 'Abilene-Sweetwater, TX',
+            669 => 'Madison, WI',
+            670 => 'Ft Smith-Fay-Springfield, AR',
+            671 => 'Tulsa, OK',
+            673 => 'Columbus-Tupelo-West Point, MS',
+            675 => 'Peoria-Bloomington, IL',
+            676 => 'Duluth, MN',
+            678 => 'Wichita, KS',
+            679 => 'Des Moines, IA',
+            682 => 'Davenport-Rock Island-Moline, IL',
+            686 => 'Mobile, AL',
+            687 => 'Minot-Bismarck-Dickinson, ND',
+            691 => 'Huntsville, AL',
+            692 => 'Beaumont-Port Author, TX',
+            693 => 'Little Rock-Pine Bluff, AR',
+            698 => 'Montgomery, AL',
+            702 => 'La Crosse-Eau Claire, WI',
+            705 => 'Wausau-Rhinelander, WI',
+            709 => 'Tyler-Longview, TX',
+            710 => 'Hattiesburg-Laurel, MS',
+            711 => 'Meridian, MS',
+            716 => 'Baton Rouge, LA',
+            717 => 'Quincy, IL',
+            718 => 'Jackson, MS',
+            722 => 'Lincoln-Hastings, NE',
+            724 => 'Fargo-Valley City, ND',
+            725 => 'Sioux Falls, SD',
+            734 => 'Jonesboro, AR',
+            736 => 'Bowling Green, KY',
+            737 => 'Mankato, MN',
+            740 => 'North Platte, NE',
+            743 => 'Anchorage, AK',
+            744 => 'Honolulu, HI',
+            745 => 'Fairbanks, AK',
+            746 => 'Biloxi-Gulfport, MS',
+            747 => 'Juneau, AK',
+            749 => 'Laredo, TX',
+            751 => 'Denver, CO',
+            752 => 'Colorado Springs, CO',
+            753 => 'Phoenix, AZ',
+            754 => 'Butte-Bozeman, MT',
+            755 => 'Great Falls, MT',
+            756 => 'Billings, MT',
+            757 => 'Boise, ID',
+            758 => 'Idaho Falls-Pocatello, ID',
+            759 => 'Cheyenne, WY',
+            760 => 'Twin Falls, ID',
+            762 => 'Missoula, MT',
+            764 => 'Rapid City, SD',
+            765 => 'El Paso, TX',
+            766 => 'Helena, MT',
+            767 => 'Casper-Riverton, WY',
+            770 => 'Salt Lake City, UT',
+            771 => 'Yuma, AZ',
+            773 => 'Grand Junction, CO',
+            789 => 'Tucson, AZ',
+            790 => 'Albuquerque, NM',
+            798 => 'Glendive, MT',
+            800 => 'Bakersfield, CA',
+            801 => 'Eugene, OR',
+            802 => 'Eureka, CA',
+            803 => 'Los Angeles, CA',
+            804 => 'Palm Springs, CA',
+            807 => 'San Francisco, CA',
+            810 => 'Yakima-Pasco, WA',
+            811 => 'Reno, NV',
+            813 => 'Medford-Klamath Falls, OR',
+            819 => 'Seattle-Tacoma, WA',
+            820 => 'Portland, OR',
+            821 => 'Bend, OR',
+            825 => 'San Diego, CA',
+            828 => 'Monterey-Salinas, CA',
+            839 => 'Las Vegas, NV',
+            855 => 'Santa Barbara, CA',
+            862 => 'Sacramento, CA',
+            866 => 'Fresno, CA',
+            868 => 'Chico-Redding, CA',
+            881 => 'Spokane, WA');
+    }
+    
+    /**
+     * Lookup the metro region based on the provided DMA code.
+     * 
+     * @param int $dmaCode The DMA code
+     * 
+     * @return string Metro region name.
+     */
+    public static function getMetroRegion($dmaCode)
+    {
+        if ($dmaCode === null) {
+            return null;
+        }
+        if (self::$dmaMap === null) {
+            self::initialize();
+        }
+        return self::$dmaMap[$dmaCode];
+    }
+
+    /**
+     * Reverse lookup of DMA code if [exact] metro region name is known.
+     * 
+     * @param string $metro Metro region name.
+     * 
+     * @return int DMA code, or false if not found.
+     */
+    public static function getDMACode($metro)    
+    {
+        if (self::$dmaMap === null) {
+            self::initialize();
+        }
+        return array_search($metro, self::$dmaMap);
+    }
+
 }
\ No newline at end of file
index 2b70f6d..c02598b 100644 (file)
-<?php\r
-/**\r
- * +----------------------------------------------------------------------+\r
- * | PHP version 5                                                        |\r
- * +----------------------------------------------------------------------+\r
- * | Copyright (C) 2004 MaxMind LLC                                       |\r
- * +----------------------------------------------------------------------+\r
- * | This library is free software; you can redistribute it and/or        |\r
- * | modify it under the terms of the GNU Lesser General Public           |\r
- * | License as published by the Free Software Foundation; either         |\r
- * | version 2.1 of the License, or (at your option) any later version.   |\r
- * |                                                                      |\r
- * | This library is distributed in the hope that it will be useful,      |\r
- * | but WITHOUT ANY WARRANTY; without even the implied warranty of       |\r
- * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |\r
- * | Lesser General Public License for more details.                      |\r
- * |                                                                      |\r
- * | You should have received a copy of the GNU Lesser General Public     |\r
- * | License along with this library; if not, write to the Free Software  |\r
- * | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 |\r
- * | USA, or view it online at http://www.gnu.org/licenses/lgpl.txt.      |\r
- * +----------------------------------------------------------------------+\r
- * | Authors: Jim Winstead <jimw@apache.org> (original Maxmind version)   |\r
- * |          Hans Lellelid <hans@xmpl.org>                               |\r
- * +----------------------------------------------------------------------+\r
- *\r
- * @category Net\r
- * @package  Net_GeoIP\r
- * @author   Hans Lellelid <hans@xmpl.org>\r
- * @license  LGPL http://www.gnu.org/licenses/lgpl.txt\r
- * @link     http://pear.php.net/package/Net_GeoIp\r
- * $Id$\r
- */\r
-\r
-/**\r
- * This class represents a location record as returned by Net_GeoIP::lookupLocation().\r
- *\r
- * This class is primarily a collection of values (the public properties of the class), but\r
- * there is also a distance() method to calculate the km distance between two points.\r
- *\r
- * @category Net\r
- * @package  Net_GeoIP\r
- * @author   Hans Lellelid <hans@xmpl.org>\r
- * @author   Dmitri Snytkine <d.snytkine@gmail.com>\r
- * @license  LGPL http://www.gnu.org/licenses/lgpl.txt\r
- * @version  $Revision$\r
- * @link     http://pear.php.net/package/Net_GeoIp\r
- * @see      Net_GeoIP::lookupLocation()\r
- */\r
-class Net_GeoIP_Location implements Serializable\r
-{\r
-    protected $aData = array(\r
-        'countryCode'  => null,\r
-        'countryCode3' => null,\r
-        'countryName'  => null,\r
-        'region'       => null,\r
-        'city'         => null,\r
-        'postalCode'   => null,\r
-        'latitude'     => null,\r
-        'longitude'    => null,\r
-        'areaCode'     => null,\r
-        'dmaCode'      => null\r
-    );\r
-\r
-\r
-    /**\r
-     * Calculate the distance in km between two points.\r
-     *\r
-     * @param Net_GeoIP_Location $loc The other point to which distance will be calculated.\r
-     *\r
-     * @return float The number of km between two points on the globe.\r
-     */\r
-    public function distance(Net_GeoIP_Location $loc)\r
-    {\r
-        // ideally these should be class constants, but class constants\r
-        // can't be operations.\r
-        $RAD_CONVERT = M_PI / 180;\r
-        $EARTH_DIAMETER = 2 * 6378.2;\r
-\r
-        $lat1 = $this->latitude;\r
-        $lon1 = $this->longitude;\r
-        $lat2 = $loc->latitude;\r
-        $lon2 = $loc->longitude;\r
-\r
-        // convert degrees to radians\r
-        $lat1 *= $RAD_CONVERT;\r
-        $lat2 *= $RAD_CONVERT;\r
-\r
-        // find the deltas\r
-        $delta_lat = $lat2 - $lat1;\r
-        $delta_lon = ($lon2 - $lon1) * $RAD_CONVERT;\r
-\r
-        // Find the great circle distance\r
-        $temp = pow(sin($delta_lat/2), 2) + cos($lat1) * cos($lat2) * pow(sin($delta_lon/2), 2);\r
-        return $EARTH_DIAMETER * atan2(sqrt($temp), sqrt(1-$temp));\r
-    }\r
-\r
-    /**\r
-     * magic method to make it possible\r
-     * to store this object in cache when\r
-     * automatic serialization is on\r
-     * Specifically it makes it possible to store\r
-     * this object in memcache\r
-     *\r
-     * @return array\r
-     */\r
-    public function serialize()\r
-    {\r
-        return serialize($this->aData);\r
-    }\r
-\r
-    /**\r
-     * unserialize a representation of the object\r
-     *\r
-     * @param array $serialized The serialized representation of the location\r
-     *\r
-     * @return void\r
-     */\r
-    public function unserialize($serialized)\r
-    {\r
-        $this->aData = unserialize($serialized);\r
-    }\r
-\r
-\r
-    /**\r
-     * Setter for elements of $this->aData array\r
-     *\r
-     * @param string $name The variable to set\r
-     * @param string $val  The value\r
-     *\r
-     * @return object $this object\r
-     */\r
-    public function set($name, $val)\r
-    {\r
-        if (array_key_exists($name, $this->aData)) {\r
-            $this->aData[$name] = $val;\r
-        }\r
-\r
-        return $this;\r
-    }\r
-\r
-    public function __set($name, $val)\r
-    {\r
-        return $this->set($name, $val);\r
-    }\r
-\r
-    /**\r
-     * Getter for $this->aData array\r
-     *\r
-     * @return array\r
-     */\r
-    public function getData()\r
-    {\r
-         return $this->aData;\r
-    }\r
-\r
-\r
-    /**\r
-     * Magic method to get value from $this->aData array\r
-     *\r
-     * @param string $name The var to get\r
-     *\r
-     * @return mixed string if value exists or null if it is empty of\r
-     * just does not exist\r
-     */\r
-    public function __get($name)\r
-    {\r
-        if (array_key_exists($name, $this->aData)) {\r
-            return $this->aData[$name];\r
-        }\r
-\r
-        return null;\r
-    }\r
-\r
-\r
-    /**\r
-     * String representation of the object\r
-     *\r
-     * @return string text and result of print_r of $this->aData array\r
-     */\r
-    public function __toString()\r
-    {\r
-        return 'object of type '.__CLASS__.'. data: '.implode(',', $this->aData);\r
-    }\r
-\r
-\r
-    /**\r
-     * Magic method\r
-     * makes it possible to check if specific record exists\r
-     * and also makes it possible to use empty() on any property\r
-     *\r
-     * @param strign $name The name of the var to check\r
-     *\r
-     * @return bool\r
-     */\r
-    public function __isset($name)\r
-    {\r
-        return (null !== $this->__get($name));\r
-    }\r
-\r
-}\r
+<?php
+/**
+ * +----------------------------------------------------------------------+
+ * | PHP version 5                                                        |
+ * +----------------------------------------------------------------------+
+ * | Copyright (C) 2004 MaxMind LLC                                       |
+ * +----------------------------------------------------------------------+
+ * | This library is free software; you can redistribute it and/or        |
+ * | modify it under the terms of the GNU Lesser General Public           |
+ * | License as published by the Free Software Foundation; either         |
+ * | version 2.1 of the License, or (at your option) any later version.   |
+ * |                                                                      |
+ * | This library is distributed in the hope that it will be useful,      |
+ * | but WITHOUT ANY WARRANTY; without even the implied warranty of       |
+ * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |
+ * | Lesser General Public License for more details.                      |
+ * |                                                                      |
+ * | You should have received a copy of the GNU Lesser General Public     |
+ * | License along with this library; if not, write to the Free Software  |
+ * | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 |
+ * | USA, or view it online at http://www.gnu.org/licenses/lgpl.txt.      |
+ * +----------------------------------------------------------------------+
+ * | Authors: Jim Winstead <jimw@apache.org> (original Maxmind version)   |
+ * |          Hans Lellelid <hans@xmpl.org>                               |
+ * +----------------------------------------------------------------------+
+ *
+ * @category Net
+ * @package  Net_GeoIP
+ * @author   Hans Lellelid <hans@xmpl.org>
+ * @license  LGPL http://www.gnu.org/licenses/lgpl.txt
+ * @link     http://pear.php.net/package/Net_GeoIp
+ * $Id$
+ */
+
+/**
+ * This class represents a location record as returned by Net_GeoIP::lookupLocation().
+ *
+ * This class is primarily a collection of values (the public properties of the class), but
+ * there is also a distance() method to calculate the km distance between two points.
+ *
+ * @category Net
+ * @package  Net_GeoIP
+ * @author   Hans Lellelid <hans@xmpl.org>
+ * @author   Dmitri Snytkine <d.snytkine@gmail.com>
+ * @license  LGPL http://www.gnu.org/licenses/lgpl.txt
+ * @version  $Revision$
+ * @link     http://pear.php.net/package/Net_GeoIp
+ * @see      Net_GeoIP::lookupLocation()
+ */
+class Net_GeoIP_Location implements Serializable
+{
+    protected $aData = array(
+        'countryCode'  => null,
+        'countryCode3' => null,
+        'countryName'  => null,
+        'region'       => null,
+        'city'         => null,
+        'postalCode'   => null,
+        'latitude'     => null,
+        'longitude'    => null,
+        'areaCode'     => null,
+        'dmaCode'      => null
+    );
+
+
+    /**
+     * Calculate the distance in km between two points.
+     *
+     * @param Net_GeoIP_Location $loc The other point to which distance will be calculated.
+     *
+     * @return float The number of km between two points on the globe.
+     */
+    public function distance(Net_GeoIP_Location $loc)
+    {
+        // ideally these should be class constants, but class constants
+        // can't be operations.
+        $RAD_CONVERT = M_PI / 180;
+        $EARTH_DIAMETER = 2 * 6378.2;
+
+        $lat1 = $this->latitude;
+        $lon1 = $this->longitude;
+        $lat2 = $loc->latitude;
+        $lon2 = $loc->longitude;
+
+        // convert degrees to radians
+        $lat1 *= $RAD_CONVERT;
+        $lat2 *= $RAD_CONVERT;
+
+        // find the deltas
+        $delta_lat = $lat2 - $lat1;
+        $delta_lon = ($lon2 - $lon1) * $RAD_CONVERT;
+
+        // Find the great circle distance
+        $temp = pow(sin($delta_lat/2), 2) + cos($lat1) * cos($lat2) * pow(sin($delta_lon/2), 2);
+        return $EARTH_DIAMETER * atan2(sqrt($temp), sqrt(1-$temp));
+    }
+
+    /**
+     * magic method to make it possible
+     * to store this object in cache when
+     * automatic serialization is on
+     * Specifically it makes it possible to store
+     * this object in memcache
+     *
+     * @return array
+     */
+    public function serialize()
+    {
+        return serialize($this->aData);
+    }
+
+    /**
+     * unserialize a representation of the object
+     *
+     * @param array $serialized The serialized representation of the location
+     *
+     * @return void
+     */
+    public function unserialize($serialized)
+    {
+        $this->aData = unserialize($serialized);
+    }
+
+
+    /**
+     * Setter for elements of $this->aData array
+     *
+     * @param string $name The variable to set
+     * @param string $val  The value
+     *
+     * @return object $this object
+     */
+    public function set($name, $val)
+    {
+        if (array_key_exists($name, $this->aData)) {
+            $this->aData[$name] = $val;
+        }
+
+        return $this;
+    }
+
+    public function __set($name, $val)
+    {
+        return $this->set($name, $val);
+    }
+
+    /**
+     * Getter for $this->aData array
+     *
+     * @return array
+     */
+    public function getData()
+    {
+         return $this->aData;
+    }
+
+
+    /**
+     * Magic method to get value from $this->aData array
+     *
+     * @param string $name The var to get
+     *
+     * @return mixed string if value exists or null if it is empty of
+     * just does not exist
+     */
+    public function __get($name)
+    {
+        if (array_key_exists($name, $this->aData)) {
+            return $this->aData[$name];
+        }
+
+        return null;
+    }
+
+
+    /**
+     * String representation of the object
+     *
+     * @return string text and result of print_r of $this->aData array
+     */
+    public function __toString()
+    {
+        return 'object of type '.__CLASS__.'. data: '.implode(',', $this->aData);
+    }
+
+
+    /**
+     * Magic method
+     * makes it possible to check if specific record exists
+     * and also makes it possible to use empty() on any property
+     *
+     * @param strign $name The name of the var to check
+     *
+     * @return bool
+     */
+    public function __isset($name)
+    {
+        return (null !== $this->__get($name));
+    }
+
+}