File Manager

Current Directory: /home/astoriaah/www/plugins/system/akgeoip/lib
Viewing File: /home/astoriaah/www/plugins/system/akgeoip/lib/akgeoip.php
<?php /** * @package akgeoip * @copyright Copyright (c)2013-2017 Nicholas K. Dionysopoulos * @license GNU General Public License version 3, or later * */ defined('_JEXEC') or die(); use GeoIp2\Database\Reader; class AkeebaGeoipProvider { /** * The MaxMind GeoLite database reader * * @var Reader * * @since 2.0 */ private $reader = null; /** * Records for IP addresses already looked up * * @var array * * @since 2.0 */ private $lookups = array(); /** * City records for IP addresses already looked up * * @var array * * @since 2.0 */ private $cityLookups = array(); /** @var bool Do I have a database with city-level information? */ /** * Do I have a database with city-level information? * * @var bool * * @since 2.0 */ private $hasCity = false; /** * Public constructor. Loads up the GeoLite2 database. */ public function __construct() { if (!function_exists('bcadd') || !function_exists('bcmul') || !function_exists('bcpow')) { require_once __DIR__ . '/fakebcmath.php'; } // Default to a country-level database $filePath = __DIR__ . '/../db/GeoLite2-Country.mmdb'; $this->hasCity = false; // If I have a city-level database prefer it $altFilePath = __DIR__ . '/../db/GeoLite2-City.mmdb'; if (file_exists($altFilePath)) { $filePath = $altFilePath; $this->hasCity = true; } try { $this->reader = new Reader($filePath); } // If anything goes wrong, MaxMind will raise an exception, resulting in a WSOD. Let's be sure to catch everything catch(\Exception $e) { $this->reader = null; } } /** * Gets a raw country record from an IP address * * @param string $ip The IP address to look up * * @return mixed A \GeoIp2\Model\Country record if found, false if the IP address is not found, null if the db can't be loaded */ public function getCountryRecord($ip) { if ($this->hasCity) { return $this->getCityRecord($ip); } if (!array_key_exists($ip, $this->lookups)) { try { $this->lookups[$ip] = null; if (!is_null($this->reader)) { $this->lookups[$ip] = $this->reader->country($ip); } } catch (\GeoIp2\Exception\AddressNotFoundException $e) { $this->lookups[$ip] = false; } catch (\Exception $e) { // GeoIp2 could throw several different types of exceptions. Let's be sure that we're going to catch them all $this->lookups[$ip] = null; } } return $this->lookups[$ip]; } /** * Gets the ISO country code from an IP address * * @param string $ip The IP address to look up * * @return mixed A string with the country ISO code if found, false if the IP address is not found, null if the db can't be loaded */ public function getCountryCode($ip) { $record = $this->getCountryRecord($ip); if ($record === false) { return false; } if (is_null($record)) { return false; } return $record->country->isoCode; } /** * Gets the country name from an IP address * * @param string $ip The IP address to look up * @param string $locale The locale of the country name, e.g 'de' to return the country names in German. If not specified the English (US) names are returned. * * @return mixed A string with the country name if found, false if the IP address is not found, null if the db can't be loaded */ public function getCountryName($ip, $locale = null) { $record = $this->getCountryRecord($ip); if ($record === false) { return false; } if (is_null($record)) { return false; } if (empty($locale)) { return $record->country->name; } return $record->country->names[$locale]; } /** * Gets the continent ISO code from an IP address * * @param string $ip The IP address to look up * * @return mixed A string with the country name if found, false if the IP address is not found, null if the db can't be loaded */ public function getContinent($ip, $locale = null) { $record = $this->getCountryRecord($ip); if ($record === false) { return false; } if (is_null($record)) { return false; } return $record->continent->code; } /** * Gets the continent name from an IP address * * @param string $ip The IP address to look up * @param string $locale The locale of the continent name, e.g 'de' to return the country names in German. If not specified the English (US) names are returned. * * @return mixed A string with the country name if found, false if the IP address is not found, null if the db can't be loaded */ public function getContinentName($ip, $locale = null) { $record = $this->getCountryRecord($ip); if ($record === false) { return false; } if (is_null($record)) { return false; } if (empty($locale)) { return $record->continent; } return $record->continent->names[$locale]; } /** * Gets a raw city record from an IP address * * @param string $ip The IP address to look up * * @return mixed A \GeoIp2\Model\City record if found, false if the IP address is not found, null if the db can't be loaded */ public function getCityRecord($ip) { if (!$this->hasCity) { return null; } $needsToLoad = !array_key_exists($ip, $this->cityLookups); if ($needsToLoad) { try { if (!is_null($this->reader)) { $this->cityLookups[$ip] = $this->reader->city($ip); } else { $this->cityLookups[$ip] = null; } } catch (\GeoIp2\Exception\AddressNotFoundException $e) { $this->cityLookups[$ip] = false; } catch (\Exception $e) { // GeoIp2 could throw several different types of exceptions. Let's be sure that we're going to catch them all $this->cityLookups[$ip] = null; } } return $this->cityLookups[$ip]; } /** * Gets the continent ISO code from an IP address * * @param string $ip The IP address to look up * * @return mixed A string with the country name if found, false if the IP address is not found, null if the db can't be loaded */ public function getCity($ip, $locale = null) { /** @var \GeoIp2\Record\City $record */ $record = $this->getCityRecord($ip); if ($record === false) { return false; } if (is_null($record)) { return false; } return $record->city->name; } /** * Downloads and installs a fresh copy of the GeoLite2 Country database * * @param bool $forceCity Should I force the download of the city-level library? * * @return mixed True on success, error string on failure */ public function updateDatabase($forceCity = false) { $outputFile = JPATH_PLUGINS . '/system/akgeoip/db/GeoLite2-Country.mmdb'; $deleteFile = JPATH_PLUGINS . '/system/akgeoip/db/GeoLite2-City.mmdb'; if ($this->hasCity || $forceCity) { $temp = $outputFile; $outputFile = $deleteFile; $outputFile = $temp; unset($temp); } // Sanity check if (!function_exists('gzinflate')) { return JText::_('PLG_SYSTEM_AKGEOIP_ERR_NOGZSUPPORT'); } // Try to download the package, if I get any exception I'll simply stop here and display the error try { $compressed = $this->downloadDatabase($forceCity); } catch (\Exception $e) { return $e->getMessage(); } // Write the downloaded file to a temporary location $tmpdir = $this->getTempFolder(); $target = $tmpdir . '/GeoLite2-Country.mmdb.gz'; $ret = JFile::write($target, $compressed); if ($ret === false) { return JText::_('PLG_SYSTEM_AKGEOIP_ERR_WRITEFAILED'); } unset($compressed); // Decompress the file $uncompressed = ''; $zp = @gzopen($target, 'rb'); if ($zp === false) { return JText::_('PLG_SYSTEM_AKGEOIP_ERR_CANTUNCOMPRESS'); } if ($zp !== false) { while (!gzeof($zp)) { $uncompressed .= @gzread($zp, 102400); } @gzclose($zp); if (!@unlink($target)) { JFile::delete($target); } } // Double check if MaxMind can actually read and validate the downloaded database try { // The Reader want a file, so let me write again the file in the temp directory JFile::write($target, $uncompressed); $reader = new Reader($target); } catch (\Exception $e) { JFile::delete($target); // MaxMind could not validate the database, let's inform the user return JText::_('PLG_SYSTEM_AKGEOIP_ERR_INVALIDDB'); } JFile::delete($target); // Check the size of the uncompressed data. When MaxMind goes into overload, we get crap data in return. if (strlen($uncompressed) < 1048576) { return JText::_('PLG_SYSTEM_AKGEOIP_ERR_MAXMINDRATELIMIT'); } // Check the contents of the uncompressed data. When MaxMind goes into overload, we get crap data in return. if (stristr($uncompressed, 'Rate limited exceeded') !== false) { return JText::_('PLG_SYSTEM_AKGEOIP_ERR_MAXMINDRATELIMIT'); } // Remove old file JLoader::import('joomla.filesystem.file'); if (JFile::exists($outputFile)) { if (!JFile::delete($outputFile)) { return JText::_('PLG_SYSTEM_AKGEOIP_ERR_CANTDELETEOLD'); } } // Write the update file if (!JFile::write($outputFile, $uncompressed)) { return JText::_('PLG_SYSTEM_AKGEOIP_ERR_CANTWRITE'); } // Finally, delete the other database file, if present if (JFile::exists($deleteFile)) { if (!JFile::delete($deleteFile)) { @unlink($deleteFile); } } // Piggyback on this method to also refresh the update site to this plugin $this->refreshUpdateSite(); return true; } /** * Refreshes the Joomla! update sites for this extension as needed * * @return void */ public function refreshUpdateSite() { JLoader::import('joomla.application.plugin.helper'); // Create the update site definition we want to store to the database $update_site = array( 'name' => 'Akeeba GeoIP Provider Plugin', 'type' => 'extension', 'location' => 'http://cdn.akeebabackup.com/updates/akgeoip.xml', 'enabled' => 1, 'last_check_timestamp' => 0, ); $db = JFactory::getDbo(); // Get the extension ID to ourselves $query = $db->getQuery(true) ->select($db->qn('extension_id')) ->from($db->qn('#__extensions')) ->where($db->qn('type') . ' = ' . $db->q('plugin')) ->where($db->qn('element') . ' = ' . $db->q('akgeoip')) ->where($db->qn('folder') . ' = ' . $db->q('system')); $db->setQuery($query); $extension_id = $db->loadResult(); if (empty($extension_id)) { return; } // Get the update sites for our extension $query = $db->getQuery(true) ->select($db->qn('update_site_id')) ->from($db->qn('#__update_sites_extensions')) ->where($db->qn('extension_id') . ' = ' . $db->q($extension_id)); $db->setQuery($query); $updateSiteIDs = $db->loadColumn(0); if (!count($updateSiteIDs)) { // No update sites defined. Create a new one. $newSite = (object)$update_site; $db->insertObject('#__update_sites', $newSite); $id = $db->insertid(); $updateSiteExtension = (object)array( 'update_site_id' => $id, 'extension_id' => $extension_id, ); $db->insertObject('#__update_sites_extensions', $updateSiteExtension); } // Loop through all update sites foreach ($updateSiteIDs as $id) { $query = $db->getQuery(true) ->select('*') ->from($db->qn('#__update_sites')) ->where($db->qn('update_site_id') . ' = ' . $db->q($id)); $db->setQuery($query); $aSite = $db->loadObject(); // Does the name and location match? if (($aSite->name == $update_site['name']) && ($aSite->location == $update_site['location'])) { continue; } $update_site['update_site_id'] = $id; $newSite = (object)$update_site; $db->updateObject('#__update_sites', $newSite, 'update_site_id', true); } } /** * Download the compressed database for the provider * * @param bool $forceCity Should I force the download of the city-level database * * @return string The compressed data * * @since 1.0.1 * * @throws Exception */ private function downloadDatabase($forceCity = false) { // Download the latest MaxMind GeoCountry Lite database $url = 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz'; if ($this->hasCity || $forceCity) { $url = 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz'; } // I should have F0F installed, but let's double check in order to avoid errors if (file_exists(JPATH_LIBRARIES . '/f0f/include.php')) { require_once JPATH_LIBRARIES . '/f0f/include.php'; } $http = JHttpFactory::getHttp(); // Let's bubble up the exception, we will take care in the caller $response = $http->get($url); $compressed = $response->body; // Generic check on valid HTTP code if ($response->code > 299) { throw new \Exception(JText::_('PLG_SYSTEM_AKGEOIP_ERR_MAXMIND_GENERIC')); } // An empty file indicates a problem with MaxMind's servers if (empty($compressed)) { throw new \Exception(JText::_('PLG_SYSTEM_AKGEOIP_ERR_EMPTYDOWNLOAD')); } // Sometimes you get a rate limit exceeded if (stristr($compressed, 'Rate limited exceeded') !== false) { throw new \Exception(JText::_('PLG_SYSTEM_AKGEOIP_ERR_MAXMINDRATELIMIT')); } return $compressed; } /** * Reads (and checks) the temp Joomla folder * * @return string */ private function getTempFolder() { $jRegistry = JFactory::getConfig(); $tmpdir = $jRegistry->get('tmp_path'); JLoader::import('joomla.filesystem.folder'); // Make sure the user doesn't use the system-wide tmp directory. You know, the one that's // being erased periodically and will cause a real mess while installing extensions (Grrr!) if (realpath($tmpdir) == '/tmp') { // Someone inform the user that what he's doing is insecure and stupid, please. In the // meantime, I will fix what is broken. $tmpdir = JPATH_SITE . '/tmp'; } // Make sure that folder exists (users do stupid things too often; you'd be surprised) elseif (!JFolder::exists($tmpdir)) { // Darn it, user! WTF where you thinking? OK, let's use a directory I know it's there... $tmpdir = JPATH_SITE . '/tmp'; } return $tmpdir; } }