Автоопределение города пользователя при оформлении заказа (на основе GeoIP)

#
Re: Автоопределение города пользователя при оформлении заказа (на основе GeoIP)
Ну и вдогонку - забавный скриншот, который не лучшим образом характеризует базу стран и городов на ip2city.ru - http://floomby.ru/content/3ldxD7zBp0/
Заказов не беру. Консультирую редко.
#
Re: Автоопределение города пользователя при оформлении заказа (на основе GeoIP)
Закрыт порт на новом хостинге 8090 надо выделенный ip брать.
в связи с чем выскакивает ошибка.
заменил
if (!$socket = fsockopen(IPGEO_SERVER, IPGEO_SERVER_PORT))
            return false;

на
$socket = @fsockopen(IPGEO_SERVER, IPGEO_SERVER_PORT)
if (!$socket)
            return false;


так корректно делать?

пока порт закрыт, можно попытку подключения совсем убрать, но если проблема в чем-то другом будет, то уж слишком длительная попытка идет. можно как-то сократить время настройками?

И нет ли у кого шлюза с порта 80 или 443 на 8090? для этого сервиса?
#
Re: Автоопределение города пользователя при оформлении заказа (на основе GeoIP)
VAshot, по коду - синтаксически корректно. Логически использовать @ вообще-то не рекомендуется (ну это примерно как давние холивары про оператор Goto ) но в данном случае в прицнипе резонно.

По остальным же вопросам вам скорее вот сюда http://blog.ipgeobase.ru/?p=37 и вот сюда http://www.it2k.ru/projects/class-ipgeo/ т.к. исходный код класса не мой, я лишь чуть-чуть подработал его напильничком, чтобы мне было удобнее взаимодействовать с этим классом из HostCMS.
Заказов не беру. Консультирую редко.
#
Re: Автоопределение города пользователя при оформлении заказа (на основе GeoIP)
В связи с тем, что люди интересуются и спрашивают, выкладываю обновленную версию, для работы с utf-8 версиями HostCMS. В ней есть небольшие, но все же изменения.

  • Во1х, в коде класса исключен перевод результирующего значения из utf-8 в win1251 с помощью iconv - теперь результат отдается непосредственно в utf-8. Код класса предназначен только для работы под php5.
  • Во2х, в ТДС метод strtolower() заменен на mb_strtolower().
  • В3х, в ТДС исключен последовательный перебор городов в поисках совпадения. Вместо этого используется прямой запрос к БД. В некотором плане это чуть менее кошерно, но зато в ряде случаев заметно быстрее.
  • В4х, обновлен xsl-шаблон (исправлена кодировка с win-1251 на utf-8).


Не забываем, что создавая файл с классом geoip.class.php сохранять его надо в кодировке utf-8!
Итак, коды:
geoip.class.php (сохранять в папку modules)
// файл /modules/geoip.class.php

<?php
/*
This program is free software. You can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License.

Home:   http://www.it2k.ru/projects/class-ipgeo/
Author: Egor N. Zuskin

Adapted for HostCMS by James V. Kotoff

Simple for php:
$ipList = new IPGeo("xxx.xxx.xxx.xxx,xxx.xxx.xxx.xxx");
print $ipList->ip("xxx.xxx.xxx.xxx"); // city: xxxx
or
$ipList = new IPGeo(array("xxx.xxx.xxx.xxx", "xxx.xxx.xxx.xxx"));
print $ipList->ip("xxx.xxx.xxx.xxx","region"); // region: xxxx
or
$ipList = new IPGeo("xxx.xxx.xxx.xxx");
print $ipList->ip("xxx.xxx.xxx.xxx", "district"); // district: xxxx
*/

DEFINE("IPGEO_SERVER", "194.85.91.253"); // сервер ip geo
DEFINE("IPGEO_SERVER_PORT", 8090); // порт
DEFINE("IPGEO_DEFAULT_PARAM", "city"); // поле возвращаемое поумолчанию
DEFINE("IPGEO_DEBUG", false); // признак отладки (не обращается к серверу)

/**
* @author ice
* Класс для получения ip адресов с сервиса ipgeobase.ru
*/
class IPGeo
{
    var $xml = ""; // текст возвращаемого xml
    var $ip_arr = array(); // массив ip адресов
    var $fields_arr = array("all"); // список запрашиваемых полей    
    var $cache = array(); // кешь ответа

    /**
     * Создание класса и запрос к серверу
     * @param $AIpList список ip адресов, строкой либо строкой через запятую либо массивом
     * @return bool
     */
    function IPGeo($AIpList)
    {

        if (IPGEO_DEBUG) {
            return true;
        }

        if (is_array($AIpList)) {
            $ip_arr = $AIpList;
        } else {
            if (strpos($AIpList, ",") === false) {
                $ip_arr = array(trim($AIpList));
            } else {
                $ip_arr = explode(",", trim($AIpList));
            }
        }

        $ip_arr = array_unique($ip_arr);
        $ip_arr = $this->check_ip_list_valid($ip_arr);

        if (count($ip_arr) == 0)
            return false;

        $ips = "<ip>" . implode("</ip><ip>", $ip_arr) . "</ip>";
        $fields = "<" . implode("/><", $this->fields_arr) . "/>";
        $post_string = "<ipquery><fields>" . $fields . "</fields><ip-list>" . $ips .
            "</ip-list></ipquery>";

        if (!$socket = fsockopen(IPGEO_SERVER, IPGEO_SERVER_PORT))
            return false;

        $query = "POST /geo/geo.html HTTP/1.1\r\n";
        $query .= "Content-Length: " . strlen($post_string) . "\r\n";
        $query .= "\r\n";
        $query .= $post_string;
        $query .= "\r\n\r\n";

        $response = "";
        fwrite($socket, $query);
        while (!feof($socket)) {
            $response .= fgets($socket, 2048);
        }
        fclose($socket);
        $this->xml = trim(substr($response, strpos($response, "\r\n\r\n")));

        return true;
    }

    /**
     * Возвращает запрошенное поле для ip адреса
     * @param $AIp        IP адрес
     * @param $AFieldName Поле
     * @return string
     */
    function ip($AIp, $AFieldName = IPGEO_DEFAULT_PARAM)
    {
        if (IPGEO_DEBUG) {
            return false;
        }

        if (isset($this->cache[$AIp][$AFieldName])) {
            return $this->cache[$AIp][$AFieldName];
        } else {
            if ($this->xml) {
                $doc = new DOMDocument;
                $doc->loadXML($this->xml);
                $xmlpath = new domxpath($doc);
                $ip_ansver = $doc->getElementsByTagName("ip-answer")->item(0);
                $items = $xmlpath->query("ip", $ip_ansver);
                foreach ($items as $it) {
                    $ip = $it->getAttribute('value');
                    if ($ip == $AIp) {
                        $message = @$xmlpath->query("message", $it)->item(0)->nodeValue;
                        $field_value = ($message <> "") ? false : $xmlpath->query($AFieldName, $it)->item(0)->nodeValue;
                        $this->cache[$AIp][$AFieldName] = $field_value;
                        return $field_value;
                    }
                }
            }
        }
        return false;
    }

    /**
     * Возвращает список правильных ip адресов проверенных по маске xxx.xxx.xxx.xxx < 256
     * @param $AIpList масив ip адресов
     * @return array
     */
    function check_ip_list_valid($AIpList)
    {
        $return = array();

        foreach ($AIpList as $ip) {
            if (ereg("([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3}).([0-9]{1,3})", $ip, $par)) {
                if ($par[1] < 256 && $par[2] < 256 && $par[3] < 256 && $par[4] < 256) {
                    $return[] = $ip;
                }
            }
        }
        return $return;
    }
}
?>


В ТДС «Интернет-магазин корзина» находим следующий фрагмент:
...
      }

      /* Запоминаем купон */
      $_SESSION['shop_coupon_text'] = to_str($_POST['shop_coupon_text']);

      /* Отображаем форму ввода адреса */
      // Не выбираем show_location, show_city и show_city_area, т.к. подгружаются через AJAX
      $shop->ShowAddress(to_str($GLOBALS['LA']['xsl_delivery_address']), $shop_id, array('show_location' => false, 'show_city' => false, 'show_city_area' => false), $external_propertys);


   }
...

и меняем его на следующий фрагмент:
...
}
/* Запоминаем купон */
        $_SESSION['shop_coupon_text'] = to_str($_POST['shop_coupon_text']);

        // Определяем город пользователя

        @include_once (CMS_FOLDER . '/modules/geoip.class.php');
        if (defined('IPGEO_SERVER'))
        {
            $user_ip = ($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : getenv("HTTP_X_FORWARDED_FOR");
            $IPGeo = new IPGeo($user_ip);
            $city = trim(to_str($IPGeo->ip($user_ip)));
            if ($city)
            {
                $city = mb_strtolower($city);
                $query = "SELECT * FROM `shop_city_table` WHERE `shop_city_name` = '{$city}'";
                $DataBase = &singleton('DataBase');
                $city_resource = $DataBase->select($query);
                if ($city_resource)
                {
                    $city_row = mysql_fetch_assoc($city_resource);
                    $city_id = $city_row['shop_city_id'];
                    $location_id = $city_row['shop_location_id'];
                }              
                if (isset($city_id))
                {
                    $location_row = $shop->GetLocation($location_id);
                    $country_id = $location_row['shop_country_id'];
                }
            }
        }
        /* Отображаем форму ввода адреса */
        if (isset($city_id))
        {
            $external_propertys['city_id'] = $city_id;
            $external_propertys['location_id'] = $location_id;
            $external_propertys['country_id'] = $country_id;
            $shop->ShowAddress(to_str($GLOBALS['LA']['xsl_delivery_address']), $shop_id,
                array('show_location' => true, 'show_city' => true, 'show_city_area' => true), $external_propertys);
        } else
        {
            // Не выбираем show_location, show_city и show_city_area, т.к. подгружаются через AJAX
            $shop->ShowAddress(to_str($GLOBALS['LA']['xsl_delivery_address']), $shop_id,
                array('show_location' => false, 'show_city' => false, 'show_city_area' => false),
                $external_propertys);
        }

    }
...

После этого меняем код xsl-шаблона «МагазинАдресДоставки» на следующий:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output xmlns="http://www.w3.org/TR/xhtml1/strict" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" encoding="UTF-8" indent="yes" method="html" omit-xml-declaration="no" version="1.0" media-type="text/xml"/>

   <xsl:template match="/locations">
      <!-- Строка шага заказа -->
      <ul class="shop_navigation gray">
         <li class="shop_navigation_current">
            <span>Адрес доставки</span>&#x2192;</li>
         <li>
            <span>Способ доставки</span>&#x2192;</li>
         <li>
            <span>Форма оплаты</span>&#x2192;</li>
         <li>
            <span>Данные доставки</span>
         </li>
      </ul>

      <SCRIPT type="text/javascript" language="JavaScript">
         <xsl:comment>
            <xsl:text disable-output-escaping="yes">
               <![CDATA[
               location_select_id = "location";
               city_select_id = "sel_city";
               cityarea_select_id = "sel_city_area";
               ]]>
            </xsl:text>
         </xsl:comment>
      </SCRIPT>

      <xsl:variable name="country_id">
         <xsl:choose>
            <xsl:when test="/locations/external_propertys/country_id/node()">
               <xsl:value-of select="/locations/external_propertys/country_id"/>
            </xsl:when>
            <xsl:otherwise>
               <xsl:value-of select="/locations/country[@select = 1]/@id"/>
            </xsl:otherwise>
         </xsl:choose>
      </xsl:variable>


      <form name="address" id="address" method="POST">
         <h1>Адрес доставки</h1>
         <p>
            <a href="{shop/path}cart/">Корзина</a>
         </p>
         <table>
            <tr>
               <td>Страна:</td>
               <td>
                  <select id="country" style="width: 390px;" name="country" onchange="doSetLocation(this.options[this.selectedIndex].value, '{/locations/shop/path}cart/')">
                     <option value="0">..</option>
                     <xsl:apply-templates select="country"/>
                  </select>
                  <span class="red_star" style="position: relative; top: 4px;">*</span>
               </td>
            </tr>

            <tr>
               <td>Область:</td>
               <td>
                  <select name="location" style="width: 390px;" id="location" onchange="doSetCity(this.options[this.selectedIndex].value, '{/locations/shop/path}cart/')">
                     <option value="0">..</option>
                     <xsl:apply-templates select="location[@parent = $country_id]"/>
                  </select>
                  <span class="red_star" style="position: relative; top: 4px;">*</span>
               </td>
            </tr>
            <tr>
               <td>Город:</td>
               <td>
                  <select name="sel_city" style="width: 390px;" id="sel_city" onchange="doSetCityArea(this.options[this.selectedIndex].value, '{/locations/shop/path}cart/')">
                     <option value="0">..</option>
                     <xsl:variable name="location_id">
                        <xsl:choose>
                           <xsl:when test="/locations/external_propertys/location_id/node()">
                              <xsl:value-of select="/locations/external_propertys/location_id"/>
                           </xsl:when>
                           <xsl:otherwise>
                              <xsl:value-of select="location[@parent = $country_id]/@id"/>
                           </xsl:otherwise>
                        </xsl:choose>
                     </xsl:variable>                    
                     <xsl:apply-templates select="city[@parent = $location_id]"/>
                  </select>
               </td>
            </tr>
            <tr>
               <td>Район города:</td>
               <td>
                  <select name="sel_city_area" style="width: 390px;" id="sel_city_area">
                     <option value="0">..</option>
                  </select>
               </td>
            </tr>
            <tr>
               <td style="vertical-align: middle;">Индекс:</td>
               <td>
                  <input type="text" size="5" class="large_input" style="width: 90px;" name="index" value="{external_propertys/site_users_postcode}"/>
               </td>
            </tr>
            <tr>
               <td style="vertical-align: middle;">Улица, дом, квартира:<br/>
               (город, район, если не выбраны)</td>
               <td>
                  <input type="text" size="30" class="large_input" style="width: 390px;" name="full_address" value="{external_propertys/site_users_address}"/>
               </td>
            </tr>
            <tr>
               <td style="vertical-align: middle;">Фамилия, Имя, Отчество:</td>
               <td>
                  <input type="text" size="30" class="large_input" style="width: 124px; margin-right: 5px;" name="site_users_surname" value="{external_propertys/site_users_surname}"/>
                  <input type="text" size="30" class="large_input" style="width: 124px; margin-right: 5px;" name="site_users_name" value="{external_propertys/site_users_name}"/>
                  <input type="text" size="30" class="large_input" style="width: 124px; margin-right: 5px;" name="site_users_patronymic" value="{external_propertys/site_users_patronymic}"/>
               </td>
            </tr>
            <tr>
               <td style="vertical-align: middle;">Компания:</td>
               <td>
                  <input type="text" size="30" class="large_input" style="width: 390px;" name="site_users_company" value="{external_propertys/site_users_company}"/>
               </td>
            </tr>
            <tr>
               <td style="vertical-align: middle;">Телефон:</td>
               <td>
                  <input type="text" size="30" class="large_input" style="width: 390px;" name="site_users_phone" value="{external_propertys/site_users_phone}"/>
               </td>
            </tr>
            <tr>
               <td style="vertical-align: middle;">Факс:</td>
               <td>
                  <input type="text" size="30" class="large_input" style="width: 390px;" name="site_users_fax" value="{external_propertys/site_users_fax}"/>
               </td>
            </tr>
            <tr>
               <td style="vertical-align: middle;">E-mail:</td>
               <td>
                  <input type="text" size="30" class="large_input" style="width: 390px;" name="site_users_email" value="{external_propertys/site_users_email}"/>
               </td>
            </tr>
            <tr>
               <td style="vertical-align: middle;">Комментарий к заказу:</td>
               <td>
                  <textarea rows="2" class="large_input" style="width: 390px;" name="description"></textarea>
               </td>
            </tr>
            <tr>
               <td>
                  <div class="gray_button">
                     <div>
                        <input name="step_2" value="Далее &#x2192;" type="submit"></input>
                     </div>
                  </div>
               </td>
            </tr>
         </table>
      </form>

      <!-- Автоматически заполняем все дочерние элементы страны, если город пользователя не определился по GeoIP-->
      <xsl:if test="not(/locations/external_propertys/country_id/node())" >
      <SCRIPT type="text/javascript" language="JavaScript">var oldHandler=window['onload'];
         window['onload']=function(){if(typeof(oldHandler)=='function'){oldHandler();}newHandler();};
         function newHandler(){
           doSetLocation(document.getElementById('country').options[document.getElementById('country').selectedIndex].value, '<xsl:value-of select="/locations/shop/path"/>cart/');
         }</SCRIPT>
      </xsl:if>
   </xsl:template>

   <!-- Шаблон заполняет options для стран -->
   <xsl:template match="country">
      <xsl:choose>
         <!-- Если страна задана по умолчанию -->
         <xsl:when test="(not(/locations/external_propertys/country_id/node()) and @select=1) or (/locations/external_propertys/country_id/node() and @id = /locations/external_propertys/country_id)">
            <option value="{@id}" selected="selected" style="font-weight: bold;">
               <xsl:value-of disable-output-escaping="yes" select="name"/>
            </option>
         </xsl:when>
         <xsl:otherwise>
            <option value="{@id}">
               <xsl:value-of disable-output-escaping="yes" select="name"/>
            </option>
         </xsl:otherwise>
      </xsl:choose>
   </xsl:template>

   <!-- Шаблон заполняет options для местоположений (областей) -->
   <xsl:template match="location">
      <xsl:choose>
         <!-- Если страна задана по умолчанию -->
         <xsl:when test="/locations/external_propertys/location_id/node() and @id = /locations/external_propertys/location_id">
            <option value="{@id}" selected="selected" style="font-weight: bold;">
               <xsl:value-of disable-output-escaping="yes" select="name"/>
            </option>
         </xsl:when>
         <xsl:otherwise>
            <option value="{@id}">
               <xsl:value-of disable-output-escaping="yes" select="name"/>
            </option>
         </xsl:otherwise>
      </xsl:choose>
   </xsl:template>

   <!-- Шаблон заполняет options для городов -->
   <xsl:template match="city">
      <xsl:choose>
         <!-- Если страна задана по умолчанию -->
         <xsl:when test="/locations/external_propertys/city_id/node() and @id = /locations/external_propertys/city_id">
            <option value="{@id}" selected="selected" style="font-weight: bold;">
               <xsl:value-of disable-output-escaping="yes" select="name"/>
            </option>
         </xsl:when>
         <xsl:otherwise>
            <option value="{@id}">
               <xsl:value-of disable-output-escaping="yes" select="name"/>
            </option>
         </xsl:otherwise>
      </xsl:choose>
   </xsl:template>

   <!-- Шаблон заполняет options для районов -->
   <xsl:template match="cityarea">
      <option value="{@id}">
         <xsl:value-of disable-output-escaping="yes" select="name"/>
      </option>
   </xsl:template>
</xsl:stylesheet>


Также, весь вышеприведенный код можно скачать одним rar-архивом отсюда.
Заказов не беру. Консультирую редко.
#
Re: Автоопределение города пользователя при оформлении заказа (на основе GeoIP)
А как бы данную нужную плюшку прикрутить так чтобы при заходе на основной сайт редиректило на сайт города согласно данным из базы GEOIp?? т.е. есть сайт http://site.ru при заходе на него ип пользователя сравнивался с базой GEOIp и если город например Курск, редиректило бы пользователя на http://kursk.site.ru
Жизнь не преодоление сложностей, а поиск возможностей © DSergeev
#
Re: Автоопределение города пользователя при оформлении заказа (на основе GeoIP)
В принципе - вполне возможно.
Это будет своеобразный гибрид из двух решений - GeoIP и Массовый 301й редирект.
Идея примерно следующая:
Вы создаете в админке несколько разных сайтов для городов, и "развешиваете" их по разным субодоменам.
У сайтов есть свой ID, а GeoIP возвращаем нам идентификаторы страны, области и города. Будем мыслить широко, и определять нужный сайт по id области.
Для этого нам придется _вручную_ создать массив, в котором ключами элементов будут id областей, а значениями элементов - id сайтов.
Этот массив мы сохраняем в php-файлик.
Затем меняем содержимое redirecter.php, подключаем в нем GeoIP определялку аналогично примерам в начале темы, и анализируем результат определения - если в массиве есть элемент с индексом определившейся области - получаем id нужного сайта, вытягиваем его домен и редиретимся туда. А если в массиве нет такого элемента, или по GeoIP определить ничего не удалось, то редиректим посетителя на основной сайт (или ничего не делаем, если он и так туда зашел).
Это черновая схема алгоритма, там на самом деле надо еще учесть, что в системе могут быть сайты, с которых никого никуда редиретить не надо, но суть примерно такова.
Готового решения, разумеется, нет.
Заказов не беру. Консультирую редко.
#
Re: Автоопределение города пользователя при оформлении заказа (на основе GeoIP)
ех еще бы города в области определяло.. было бы здорово... а то нахожусьв  краснодарском крае, г.тихорецк,а определяет краснодар... (ну понятно что по IP привязке идет) но все таки.. ))))
Kotoff, скажите или у меня глюк в системе или что не так: сделал по последней версии
теперь в корзине при указании адреса выдаает  // /modules/geoip.class.php в вверху.... чтот найти не могу где это прописано????
Программист без пива как шампанское без пробки- быстро выдыхается.
#
Re: Автоопределение города пользователя при оформлении заказа (на основе GeoIP)
asys-log, города в области - это не ко мне, сервис-то совершенно посторонний. Да и вряд-ли кто-то другой вам такую точность предложит на данный момент.

По поводу ошибки - стукнитесь в аську или в почту с доступами, по приведенному вами сообщению я установить ошибку не могу.
Заказов не беру. Консультирую редко.
#
Re: Автоопределение города пользователя при оформлении заказа (на основе GeoIP)
Я продолжаю постепенно наводить порядок в архиве своих наработок
Репозиторий дополнения для GeoIP выложен на BitBucket — теперь там можно всегда скачать последнюю версию, следить за изменениями и выходами апдейтов, а в разделе wiki можно прочитать самую последнюю версию инструкции по установке дополнения.
Скачивайте и следите за обновлениями! — https://bitbucket.org/JamesKotov/geoip-v1-for-hostcms-v5/

На сегодняшний день, данная первая версия дополнения считается устаревший, поэтому я рекомендую всем пользоваться более новой второй версией, ее вы можете найти здесь: https://bitbucket.org/JamesKotov/geoip-v2-for-hostcms-v5/
Заказов не беру. Консультирую редко.
#
Re: Автоопределение города пользователя при оформлении заказа (на основе GeoIP)
Сегодня перестала работать первая версия модуля. Не может достучаться до порта 8090 геосервера (вероятно уже и не будет работать).
Решил все поменять на вторую. Все сделано по мануалу, но ничего не происходит. Абсолютно. Ни ошибок, ни геоопределения. В программизме слабоват, не подскажете, как можно проверить в чем косяк?
Авторизация