07.07.2019

Использование геолокации Битрикс, написание собственного обработчика

Одна из распространенных задач в Bitrix - это определение местоположения пользователя по его ip (геолокация). В компоненте оформления заказа sale.order.ajax есть возможность включить геолокацию, значит где то в составе фреймворка она уже реализована.

Если зайти в админку Настройки > Настройки продукта > Геолокация, то можно увидеть такой список:
Список обработчиков геолокации

Для Sypex Geo и MaxMind нужны ключи для доступа к сервисам. Сами обработчики являются классами наследниками от \Bitrix\Main\Service\GeoIp\Base
Структура классов Bitrix\Main\Service\GeoIp\Base

Свои обработчики можно добавить с помощью события onMainGeoIpHandlersBuildList модуля main. Событие вызывается при инициализации списка обработчиков геолокации. Ожидается получение результата тип \Bitrix\Main\EventResult тип - \Bitrix\Main\EventResult::SUCCESS в качестве параметров - массив со списком обработчиков.
Пример:

<?
function addCustomGeoIpHandler()
{
    return new \Bitrix\Main\EventResult(
        \Bitrix\Main\EventResult::SUCCESS,
        array(
            '\Custom\GeoIP\Handler' => '/local/custom/geoip/handler.php'
        ),
        'main'
    );
}

$eventManager->addEventHandler('main', 'onMainGeoIpHandlersBuildList', 'addCustomGeoIpHandler');
?>

Использование

Чтобы определить местоположение с помощью этих обработчиков требуется IP пользователя. Его можно получить так:

$ip = \Bitrix\Main\Service\GeoIp\Manager::getRealIp();

Чтобы получить информацию по этому ip от обработчиков нужно исползовать следующий метод:

$geolocationResult = \Bitrix\Main\Service\GeoIp\Manager::getDataResult($ip, "ru");

В результате будет возвращен объект класса \Bitrix\Main\Service\GeoIp\Result
\Bitrix\Main\Service\GeoIp\Manager будет перебирать доступные обработчики геолокации до тех пор пока не получит результат на заданном языке (второй параметр) с заданными полями (3й параметр).

А вот так вот может выглядеть готовый обработчик:

<?

namespace Gricuk\GeoIp;

use Bitrix\Main;
use Bitrix\Main\Error;
use Bitrix\Main\Text\Encoding;
use Bitrix\Main\Web\HttpClient;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Service\GeoIp\Result;
use Bitrix\Main\Service\GeoIp\Data;
use Bitrix\Main\Service\GeoIp\ProvidingData;

class IpApi extends \Bitrix\Main\Service\GeoIp\Base
{
    public static function onMainGeoIpHandlersBuildListHandlers()
    {
        return new \Bitrix\Main\EventResult(
            \Bitrix\Main\EventResult::SUCCESS,
            array(
                '\Gricuk\GeoIp\IpApi' => "local/php_interface/lib/Gricuk/GeoIp/IpApi.php",
            )
        );
    }


    /**
     * @return string Title of handler.
     */
    public function getTitle()
    {
        return 'ip-api.com';
    }

    /**
     * @return string Handler description.
     */
    public function getDescription()
    {
        return "ip-api.com";
    }

    /**
     * @param string $ipAddress Ip address
     * @param string $userId user identifier obtained from www.maxmind.com
     * @param string $licenseKey
     * @return Main\Result
     */
    protected function sendRequest($ipAddress, $userId, $licenseKey)
    {
        $result = new Main\Result();
        $httpClient = $this->getHttpClient();
        $httpRes = $httpClient->get("http://ip-api.com/php/" . $ipAddress . '?lang=ru');

        $errors = $httpClient->getError();

        if (!$httpRes && !empty($errors)) {
            $strError = "";

            foreach ($errors as $errorCode => $errMes)
                $strError .= $errorCode . ": " . $errMes;

            $result->addError(new Error($strError));
        } else {
            $status = $httpClient->getStatus();
            if ($status != 200)
                $result->addError(new Error('Http status: ' . $status));

            $arRes = @unserialize($httpRes);

            if (is_array($arRes)) {
                if (strtolower(SITE_CHARSET) != 'utf-8')
                    $arRes = Encoding::convertEncoding($arRes, 'UTF-8', SITE_CHARSET);

                if ($arRes["status"] == "success") {
                    $result->setData($arRes);
                } else {
                    $result->addError(new Error('[' . $arRes['code'] . '] ' . $arRes['error']));
                }
            } else {
                $result->addError(new Error('Can\'t unserialize result'));
            }
        }

        return $result;
    }

    /**
     * @return HttpClient
     */
    protected static function getHttpClient()
    {
        return new HttpClient(array(
            "version" => "1.1",
            "socketTimeout" => 5,
            "streamTimeout" => 5,
            "redirect" => true,
            "redirectMax" => 5,
        ));
    }

    /**
     * Languages supported by handler ISO 639-1
     * @return array
     */
    public function getSupportedLanguages()
    {
        return array('ru');
    }

    /**
     * @param string $ipAddress Ip address
     * @param string $lang Language identifier
     * @return Result | null
     */
    public function getDataResult($ipAddress, $lang = '')
    {
        $dataResult = new Result();
        $geoData = new Data();

        $geoData->ip = $ipAddress;
        $geoData->lang = $lang = strlen($lang) > 0 ? $lang : 'en';

        $res = $this->sendRequest($ipAddress, $this->config['USER_ID'], $this->config['LICENSE_KEY']);

        if ($res->isSuccess()) {
            $data = $res->getData();

            if (!empty($data['country']))
                $geoData->countryName = $data['country'];

            if (!empty($data['countryCode']))
                $geoData->countryCode = $data['countryCode'];

            if (!empty($data["regionName"]))
                $geoData->regionName = $data['regionName'];

            if (!empty($data["region"]))
                $geoData->regionCode = $data['region'];

            if (!empty($data["city"]))
                $geoData->cityName = $data['city'];

            if (!empty($data['org']))
                $geoData->organizationName = $data['org'];

            if (!empty($data['isp']))
                $geoData->ispName = $data['isp'];

            if (!empty($data['timezone']))
                $geoData->timezone = $data['timezone'];

            if (!empty($data['lat']))
                $geoData->latitude = $data['lat'];

            if (!empty($data['lon']))
                $geoData->longitude = $data['lon'];

            if (!empty($data['zip']))
                $geoData->zipCode = $data['zip'];


        } else {
            $dataResult->addErrors($res->getErrors());
        }

        $dataResult->setGeoData($geoData);
        return $dataResult;
    }

    /**
     * Is this handler installed and ready for using.
     * @return bool
     */
    public function isInstalled()
    {
        return true;
    }

    /**
     * @param array $postFields Admin form posted fields during saving process.
     * @return array Field CONFIG for saving to DB in admin edit form.
     */
    public function createConfigField(array $postFields)
    {
        return array(
        );
    }

    /**
     * @return array Set of fields description for administration purposes.
     */
    public function getConfigForAdmin()
    {
        return array(
        );
    }

    /**
     * @return ProvidingData Geolocation information witch handler can return.
     */
    public function getProvidingData()
    {
        $result = new ProvidingData();
        $result->countryName = true;
        $result->countryCode = true;
        $result->regionName = true;
        $result->regionCode = true;
        $result->cityName = true;
        $result->latitude = true;
        $result->longitude = true;
        $result->timezone = true;
        $result->zipCode = true;
        $result->ispName = true;
        $result->organizationName = true;
        return $result;
    }
}