| Current Path : /var/www/element/data/www/revenuestory.ru/bitrix/modules/main/lib/service/geoip/ |
| Current File : /var/www/element/data/www/revenuestory.ru/bitrix/modules/main/lib/service/geoip/manager.php |
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage main
* @copyright 2001-2022 Bitrix
*/
namespace Bitrix\Main\Service\GeoIp;
use Bitrix\Main\Application;
use Bitrix\Main\Event;
use Bitrix\Main\IO\File;
use Bitrix\Main\Loader;
use Bitrix\Main\EventResult;
use Bitrix\Main\Config\Configuration;
use Bitrix\Main\Config\Option;
use Bitrix\Main\Web;
use Bitrix\Main\Diag;
use Psr\Log;
/**
* Class for working with geolocation information.
* @package Bitrix\Main\Service\GeoIp
*/
class Manager
{
/** @var Base[] | null */
protected static $handlers = null;
/** @var Data[][] */
protected static $data = [];
/** @var bool */
protected static $logErrors = false;
/** @var Log\LoggerInterface|null */
protected static $logger;
/** @deprecated */
const INFO_NOT_AVAILABLE = null;
protected const CACHE_DIR = 'geoip_manager';
/**
* Get the two letters country code.
* @param string $ip Ip address.
* @param string $lang Language identifier.
* @return string
*/
public static function getCountryCode($ip = '', $lang = '')
{
$resultData = self::getDataResult($ip, $lang, array('countryCode'));
return $resultData !== null ? $resultData->getGeoData()->countryCode : '';
}
/**
* Get the full country name.
* @param string $ip Ip address.
* @param string $lang Language identifier.
* @return string
*/
public static function getCountryName($ip = '', $lang = '')
{
$resultData = self::getDataResult($ip, $lang, array('countryName'));
return $resultData !== null ? $resultData->getGeoData()->countryName : '';
}
/**
* Get the full city name.
* @param string $ip Ip address.
* @param string $lang Language identifier.
* @return string|null
*/
public static function getCityName($ip = '', $lang = '')
{
$resultData = self::getDataResult($ip, $lang, array('cityName'));
return $resultData !== null ? $resultData->getGeoData()->cityName : '';
}
/**
* Get the Postal Code, FSA or Zip Code.
* @param string $ip Ip address.
* @param string $lang Language identifier.
* @return string|null
*/
public static function getCityPostCode($ip = '', $lang = '')
{
$resultData = self::getDataResult($ip, $lang, array('zipCode'));
return $resultData !== null ? $resultData->getGeoData()->zipCode : '';
}
/**
* Get geo-position attribute.
* @param string $ip Ip address.
* @param string $lang Language identifier.
* @return array|null
*/
public static function getGeoPosition($ip = '', $lang = '')
{
$data = self::getDataResult($ip, $lang, array('latitude', 'longitude'));
if (
$data !== null
&& $data->getGeoData()->latitude != null
&& $data->getGeoData()->longitude != null
)
{
$result = Array(
'latitude' => $data->getGeoData()->latitude,
'longitude' => $data->getGeoData()->longitude,
);
}
else
{
$result = null;
}
return $result;
}
/**
* Get the Latitude as signed double.
* @param string $ip Ip address.
* @param string $lang Language identifier.
* @return string
*/
public static function getGeoPositionLatitude($ip = '', $lang = '')
{
$resultData = self::getDataResult($ip, $lang, array('latitude'));
return $resultData !== null ? $resultData->getGeoData()->latitude : '';
}
/**
* Get the Longitude as signed double.
* @param string $ip Ip address.
* @param string $lang Language identifier.
* @return string
*/
public static function getGeoPositionLongitude($ip = '', $lang = '')
{
$resultData = self::getDataResult($ip, $lang, array('longitude'));
return $resultData !== null ? $resultData->getGeoData()->longitude : '';
}
/**
* Get the organization name.
* @param string $ip Ip address.
* @param string $lang Language identifier.
* @return string
*/
public static function getOrganizationName($ip = '', $lang = '')
{
$resultData = self::getDataResult($ip, $lang, array('organizationName'));
return $resultData !== null ? $resultData->getGeoData()->organizationName : '';
}
/**
* Get the Internet Service Provider (ISP) name.
* @param string $ip Ip address.
* @param string $lang Language identifier.
* @return string
*/
public static function getIspName($ip = '', $lang = '')
{
$resultData = self::getDataResult($ip, $lang, array('ispName'));
return $resultData !== null ? $resultData->getGeoData()->ispName : '';
}
/**
* Get the time zone for country and region code combo.
* @param string $ip Ip address.
* @param string $lang Language identifier.
* @return string|null
*/
public static function getTimezoneName($ip = '', $lang = '')
{
$resultData = self::getDataResult($ip, $lang, array('timezone'));
return $resultData !== null ? $resultData->getGeoData()->timezone : '';
}
/**
* Get the all available information about geolocation.
*
* @param string $ip Ip address.
* @param string $lang Language identifier.
* @param array $required Required fields for result data.
* @return Result | null
*/
public static function getDataResult($ip = '', $lang = '', array $required = [])
{
$result = null;
if ($ip == '')
{
$ip = self::getRealIp();
}
// cache on the hit
if (isset(self::$data[$ip][$lang]))
{
$data = self::$data[$ip][$lang];
if (empty($required) || self::hasDataAllRequiredFields($required, $data))
{
$result = ((new Result())->setGeoData($data));
}
}
if (!$result)
{
if(self::$handlers === null)
{
self::initHandlers();
}
foreach (self::$handlers as $class => $handler)
{
if (!$handler->isInstalled() || !$handler->isActive())
{
continue;
}
if ($lang != '' && !in_array($lang, $handler->getSupportedLanguages()))
{
continue;
}
if (!empty($required) && !self::hasDataAllRequiredFields($required, $handler->getProvidingData()))
{
continue;
}
$ipAddress = new Web\IpAddress($ip);
// get from cache
$records = self::getFromStore($ipAddress, $class);
$data = static::findForIp($ipAddress, $records, $lang);
if ($data)
{
if (empty($required) || self::hasDataAllRequiredFields($required, $data))
{
$data->ip = $ip;
self::$data[$ip][$lang] = $data;
$result = ((new Result())->setGeoData($data));
break;
}
}
$dataResult = $handler->getDataResult($ip, $lang);
if (!$dataResult)
{
continue;
}
if (!$dataResult->isSuccess())
{
if (self::$logErrors && ($logger = static::getLogger()))
{
$logger->error(
"{date} - {host}\nIP: {ip}, handler: {handler}, lang: {lang}\n{errors}\n{trace}{delimiter}\n",
[
'ip' => $ip,
'lang' => $lang,
'handler' => $handler->getId(),
'errors' => $dataResult->getErrorMessages(),
'trace' => Diag\Helper::getBackTrace(6, DEBUG_BACKTRACE_IGNORE_ARGS, 3),
]
);
}
continue;
}
$data = $dataResult->getGeoData();
$data->handlerClass = $class;
$data->ip = $ip;
// write to cache
self::$data[$ip][$lang] = $data;
self::saveToStore($ipAddress, $records, $data, $lang);
// save geonames
if (Option::get('main', 'collect_geonames', 'N') == 'Y')
{
if (!empty($data->geonames))
{
Internal\GeonameTable::save($data->geonames);
}
}
$result = $dataResult;
break;
}
}
if ($result)
{
$event = new Event('main', 'onGeoIpGetResult', [
'originalData' => clone $result->getGeoData(),
'data' => $result->getGeoData(),
]);
$event->send();
}
return $result;
}
protected static function getCacheId(Web\IpAddress $ipAddress, string $handler): string
{
return $ipAddress->toRange(24) . ':v1:' . $handler;
}
protected static function getFromStore(Web\IpAddress $ipAddress, string $handler): array
{
$cacheTtl = static::getCacheTtl();
if ($cacheTtl > 0)
{
$cache = Application::getInstance()->getManagedCache();
$cacheId = static::getCacheId($ipAddress, $handler);
if ($cache->read($cacheTtl, $cacheId, self::CACHE_DIR))
{
$records = $cache->get($cacheId);
if (is_array($records))
{
return $records;
}
}
}
return [];
}
protected static function findForIp(Web\IpAddress $ipAddress, array $records, string $lang): ?Data
{
foreach ($records as $range => $data)
{
if (isset($data[$lang]))
{
// sorted by the most specific first
if ($ipAddress->matchRange($range))
{
$result = new Data();
foreach ($data[$lang] as $attr => $value)
{
if (property_exists($result, $attr))
{
$result->$attr = $value;
}
}
return $result;
}
}
}
return null;
}
protected static function saveToStore(Web\IpAddress $ipAddress, array $records, Data $geoData, string $lang): void
{
$cacheTtl = static::getCacheTtl();
if ($cacheTtl > 0)
{
$storedData = [];
foreach (get_object_vars($geoData) as $attr => $value)
{
if ($value !== null)
{
$storedData[$attr] = $value;
}
}
$network = $geoData->ipNetwork ?? $ipAddress->toRange(32);
$records[$network][$lang] = $storedData;
// the most specific first
krsort($records);
$cache = Application::getInstance()->getManagedCache();
$cacheId = static::getCacheId($ipAddress, $geoData->handlerClass);
$cache->clean($cacheId, self::CACHE_DIR);
$cache->read($cacheTtl, $cacheId, self::CACHE_DIR);
$cache->set($cacheId, $records);
}
}
/**
* @param array $required
* @param Data $geoData
* @return bool
*/
private static function hasDataAllRequiredFields(array $required, $geoData)
{
if(empty($required))
{
return true;
}
$vars = get_object_vars($geoData);
foreach($required as $field)
{
if($vars[$field] === null)
{
return false;
}
}
return true;
}
private static function initHandlers()
{
if(self::$handlers !== null)
return;
self::$handlers = array();
$handlersList = array();
$buildInHandlers = array(
'\Bitrix\Main\Service\GeoIp\GeoIP2' => 'lib/service/geoip/geoip2.php',
'\Bitrix\Main\Service\GeoIp\MaxMind' => 'lib/service/geoip/maxmind.php',
'\Bitrix\Main\Service\GeoIp\Extension' => 'lib/service/geoip/extension.php',
'\Bitrix\Main\Service\GeoIp\SypexGeo' => 'lib/service/geoip/sypexgeo.php'
);
Loader::registerAutoLoadClasses('main', $buildInHandlers);
$handlersFields = array();
$res = HandlerTable::getList(['cache' => ['ttl' => static::getCacheTtl()]]);
while($row = $res->fetch())
$handlersFields[$row['CLASS_NAME']] = $row;
foreach($buildInHandlers as $class => $file)
{
if(self::isHandlerClassValid($class))
{
$fields = $handlersFields[$class] ?? [];
$handlersList[$class] = new $class($fields);
$handlersSort[$class] = $handlersList[$class]->getSort();
}
}
$event = new Event('main', 'onMainGeoIpHandlersBuildList');
$event->send();
$resultList = $event->getResults();
if (is_array($resultList) && !empty($resultList))
{
$customClasses = array();
foreach ($resultList as $eventResult)
{
if ($eventResult->getType() != EventResult::SUCCESS)
continue;
$params = $eventResult->getParameters();
if(!empty($params) && is_array($params))
$customClasses = array_merge($customClasses, $params);
}
if(!empty($customClasses))
{
Loader::registerAutoLoadClasses(null, $customClasses);
foreach($customClasses as $class => $file)
{
if(!File::isFileExists(Application::getDocumentRoot().'/'.$file))
{
continue;
}
if(self::isHandlerClassValid($class))
{
$fields = $handlersFields[$class] ?? [];
$handlersList[$class] = new $class($fields);
$handlersSort[$class] = $handlersList[$class]->getSort();
}
}
}
}
asort($handlersSort, SORT_NUMERIC);
foreach($handlersSort as $class => $sort)
self::$handlers[$class] = $handlersList[$class];
}
/**
* @param string $className
* @return bool
*/
private static function isHandlerClassValid($className)
{
if(!class_exists($className))
return false;
if(!is_subclass_of($className, '\Bitrix\Main\Service\GeoIp\Base'))
return false;
return true;
}
/**
* @return string | false Ip address.
*/
public static function getRealIp()
{
$ip = false;
$xForwarded = Application::getInstance()->getContext()->getServer()->get('HTTP_X_FORWARDED_FOR');
if (!empty($xForwarded))
{
$ips = explode (", ", $xForwarded);
$fCount = count($ips);
for ($i = 0; $i < $fCount; $i++)
{
if (!preg_match("/^(10|172\\.16|192\\.168)\\./", $ips[$i]))
{
$ip = $ips[$i];
break;
}
}
}
if(!$ip)
{
$ip = trim(Application::getInstance()->getContext()->getRequest()->getRemoteAddress());
}
return $ip;
}
/**
* @return Base[] Handlers list.
*/
public static function getHandlers()
{
if(self::$handlers === null)
self::initHandlers();
return self::$handlers;
}
/**
* @param string $className. Class name of handler.
* @return Base | null Handler.
*/
public static function getHandlerByClassName($className)
{
if(self::$handlers === null)
self::initHandlers();
return self::$handlers[$className] ?? null;
}
/**
* Turn on / off error logging for debugging purposes.
* @param bool $isLog
*/
public static function setLogErrors($isLog)
{
self::$logErrors = $isLog;
}
/**
* @param Base $handler
* @return string Config HTML for admin interface form.
*/
public static function getHandlerAdminConfigHtml(Base $handler)
{
$result = '';
$adminFields = $handler->getConfigForAdmin();
foreach ($adminFields as $field)
{
if ($field['TYPE'] == 'COLSPAN2')
{
$heading = isset($field['HEADING']) && $field['HEADING'] ? ' class="heading"' : '';
$result .= '<tr'.$heading.'><td colspan="2">'.$field['TITLE'];
}
elseif ($field['TYPE'] == 'TEXT' || $field['TYPE'] == 'CHECKBOX' || $field['TYPE'] == 'LIST')
{
$required = isset($field['REQUIRED']) && $field['REQUIRED'] ? ' class="adm-detail-required-field"' : '';
$disabled = isset($field['DISABLED']) && $field['DISABLED'] ? ' disabled' : '';
$value = isset($field['VALUE']) ? ' value="'.$field['VALUE'].'"' : '';
$name = isset($field['NAME']) ? ' name="'.$field['NAME'].'"' : '';
$title = isset($field['TITLE']) ? ' title="'.$field['TITLE'].'"' : '';
$result .= '<tr'.$required.'><td width="40%">'.$field['TITLE'].':</td><td width="60%">';
if ($field['TYPE'] == 'TEXT')
{
$result .= '<input type="text" size="45" maxlength="255"'.$name.$value.$disabled.$title.'>';
}
elseif ($field['TYPE'] == 'CHECKBOX')
{
$checked = isset($field['CHECKED']) && $field['CHECKED'] ? ' checked' : '';
$result .= '<input type="checkbox"'.$name.$value.$checked.$disabled.$title.'>';
}
else
{
$result .= '<select' . $name . $disabled . $title . '>';
if (is_array($field['OPTIONS']))
{
foreach ($field['OPTIONS'] as $key => $val)
{
$result .= '<option value="' . $key . '"' . ($key == $field['VALUE'] ? ' selected' : '') . '>' . $val . '</option>';
}
}
$result .= '</select>';
}
}
$result .= '</td></tr>';
}
return $result;
}
protected static function getCacheTtl(): int
{
static $cacheTtl = null;
if($cacheTtl === null)
{
$cacheFlags = Configuration::getValue('cache_flags');
$cacheTtl = $cacheFlags['geoip_manager'] ?? 604800; // a week
}
return $cacheTtl;
}
public static function cleanCache(): void
{
$cache = Application::getInstance()->getManagedCache();
$cache->cleanDir(static::CACHE_DIR);
}
protected static function getLogger()
{
if (static::$logger === null)
{
$logger = Diag\Logger::create('main.GeoIpManager');
if ($logger !== null)
{
static::$logger = $logger;
}
}
return static::$logger;
}
}