Причины создания


Недавно попросили сделать геотаргетинг по городам для сайта на wordpress. Пересмотрев существующие геотаргетинг-плагины (в том числе платные), не нашёл не одного работающего с городами (только страны). Поэтому решил сделать свой, используя какую-нибудь существующую базу для определения местоположения по IP-адресу. Сначала начал с разработки функции в шаблоне, но потом решил создать плагин и выложить на github, так как думаю, что он может пригодиться кому-нибудь ещё.

Выбор базы


Первым делом нужно было выбрать базу. И это, наверное, одна из самых сложных проблем. В процессе разработки пробовал много вариантов, даже делал CURL-запросы на сайты, определяющие местоположение по ip, и парсил их. Но все они были не точны, к примеру, один Московский IP определяло как Москву, другой — просто Россия. Также нужно было, чтобы плагин работал не только с Россией, но и с Беларусью и Украиной. Перепробовав кучу множество баз, я остановился на Sypex Geo. У них есть условно-платная и бесплатная версии баз. Условно-платная использует REST API и возвращает данные в виде xml, json и jsonp. Бесплатную можно скачать в виде файла, также можно скачать класс для работы с ней. Условно-платная версия более точная, но бесплатно предоставляет только 10 000 запросов. Бесплатная версия тоже достаточно точна и обновляется на сайте.

Функционал


Как я уже писал, начинал я делать не с плагина, а с функции в шаблоне. Сначала думал только об условно-платной версии базы, но количество бесплатных запросов закончилось за день-два. Поэтому перешёл на бесплатную, используя готовый класс для работы с файлом базы.

При разработке плагина я решил сделать выбор между локальной базой и REST API. В будущем планирую сделать кнопку автоматического обновления базы.

Также из базы можно вернуть русские и английские имена городов (стран, регионов). В связи с этим сделал выбор языка.
Ещё столкнулся с тем, что нужно включить список несколько городов или наоборот исключить какой-то.


Настройка плагина в админке

Реализация


Так как с Wordpress я работаю редко (как и со всеми CMS) и написанием плагинов для него никогда не занимался, начал читать, как их писать и смотреть, как устроены существующие. Идею реализации взял с существующих гео-плагинов. Ещё долго думал писать функционально или объектно-ориентированно. Решил функционально, так как плагин не большой.

Вкратце опишу его работу.

При активации плагина инициируем две опции: тип бд и язык на котором будем вводить названия.

register_activation_hook(__FILE__, 'wp_sypexgeo_activation');
function wp_sypexgeo_activation() {
	update_option('sgeo_language', 'en');
	update_option('sgeo_dbase', 'loc');
}

Далее
    add_filter('the_content', 'geotargeting_filter');
	add_filter('the_content_rss', 'geotargeting_filter');
	add_filter('the_excerpt', 'geotargeting_filter');
	add_filter('the_excerpt_rss', 'geotargeting_filter');
вызывают функцию
geotargeting_filter
function geotargeting_filter($s) {
		//parse Country
		preg_match_all("#\[" . GEOTARGETING_COUNTY . "\s*(in|out)=([^\]]+)\](.*?)\[/" . GEOTARGETING_COUNTY . "\]#isu", $s, $country);
		//parse Country
		preg_match_all("#\[" . GEOTARGETING_REGION . "\s*(in|out)=([^\]]+)\](.*?)\[/" . GEOTARGETING_REGION . "\]#isu", $s, $region);
		//parse Country
		preg_match_all("#\[" . GEOTARGETING_CITY . "\s*(in|out)=([^\]]+)\](.*?)\[/" . GEOTARGETING_CITY . "\]#isu", $s, $city);
		if (empty($country) && empty($region) && empty($city)) {
			return $s;
		}
		$base_type = get_option('sgeo_dbase');
		if ($base_type == 'loc') {
			$ipdata = getLocInfo();
		} elseif ($base_type == 'rm') {
			$ipdata = getRemInfo();
		}
		if (!empty($country)) {
			foreach ($country[0] as $i => $raw) {
				$type = strtolower($country[1][$i]);
				$countries = strtolower(trim(str_replace(array("\"", "'", "\n", "\r", "\t", " "), "", $country[2][$i])));
				$content = $country[3][$i];
				$countries = explode(",", $countries);
				$replacement = "";
				if ((($type == "in") && in_array($ipdata['country'], $countries)) || (($type == "out") && !in_array($ipdata['country'], $countries))) {
					$replacement = $content;
				}
				$s = str_replace($raw, $replacement, $s);
			}
		}
		if (!empty($region)) {
			foreach ($region[0] as $i => $raw) {
				$type = strtolower($region[1][$i]);
				$regions = strtolower(trim(str_replace(array("\"", "'", "\n", "\r", "\t"), "", $region[2][$i])));
				$content = $region[3][$i];
				$regions = explode(",", $regions);
				$replacement = "";
				if ((($type == "in") && in_array($ipdata['region'], $regions)) || (($type == "out") && !in_array($ipdata['region'], $regions))) {
					$replacement = $content;
				}
				$s = str_replace($raw, $replacement, $s);
			}
		}
		if (!empty($city)) {
			foreach ($city[0] as $i => $raw) {
				$type = strtolower($city[1][$i]);
				$cities = strtolower(trim(str_replace(array("\"", "'", "\n", "\r", "\t", " "), "", $city[2][$i])));
				$content = $city[3][$i];
				$cities = explode(",", $cities);
				$replacement = "";
				if ((($type == "in") && in_array($ipdata['city'], $cities)) || (($type == "out") && !in_array($ipdata['city'], $cities))) {
					$replacement = $content;
				}
				$s = str_replace($raw, $replacement, $s);
			}
		}
		return $s;
	}
В которую приходит текущий контент. В зависимости от типа базы вызывается функция для получения данных. Затем идёт поиск специальных тегов в шаблоне и сверка их с данными о местоположении. Если данные совпадают то конструкция заменяется на содержимое из тегов, если нет — она удаляется.

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


Для указания списка стран:
[GeoCountry in=Belarus,Russia]Привет Belarus,Russia![/GeoCountry]

Для указания списка регионов:
[GeoRegion in=Moscow]Привет Moscow Region![/GeoRegion]

Для указания списка городов:
[GeoCity in=Минск,Брест]Привет Минск,Брест![/GeoCity]

Если вы хотите выбрать страны (регионы, города) за исключением указанных, используйте «out»:
[GeoRegion out=Minsk,Brest]Привет всем, кроме Minsk,Brest![/GeoRegion]


Пример использования в шаблоне:

Добро пожаловать в WordPress. Это ваша первая запись. Отредактируйте или удалите её, затем пишите! Наши контакты: [GeoCity in=Minsk]+375295552255[/GeoCity][GeoCity out=Minsk]+375475552255[/GeoCity]

Заключение


Надеюсь, мой плагин кому-нибудь пригодится. Если есть вопросы или предложение — пишите. Также, если кто-то считает, что нужно использовать другую базу (геосервис или ещё что-нибудь), предлагайте, я добавлю или можете добавить сами.

Ссылка на плагин: wp-sypexgeo

Комментарии (4)


  1. olen
    25.05.2015 23:03

    Для шорткодов правильнее и проще использовать add_shortcode().

    Только в чем смысл статьи? Вы взяли готовую базу и с помощью готового класса получаете информацию. Что полезного для читателей?

    И вообще, плагин довольно специфичный получился. Тот же номер телефона с его помощью в шапке сайта или в футере не получится выводить.


    1. Apathetic
      26.05.2015 01:26

      Нет никакой проблемы в том, чтобы напрямую вызывать функцию geotargeting_filter.

      Но согласен с вами, что использовать add_shortcode — правильней.


    1. DEMONofNIGHT Автор
      26.05.2015 10:02

      Про add_shortcode спасибо, учту.

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

      Про футер и хедер согласен, в коде плагин не используешь. Это проблема, тогда уже нужно писать эти функции в код шаблона к примеру.


      1. olen
        26.05.2015 13:40

        Если вы хотели поделиться плагином, то для этого есть соответствующий каталог.

        > ему не надо будет искать базу и класс для неё
        Это вы взяли уже готовое. Более того, база в вашем плагине через некоторое время устареет.

        > писать парсер шаблона
        Парсер шаблона писать не надо, надо использовать add_shortcode.

        > и тд
        Что и т.д.? В том то и дело, что тут ничего нет.

        В статье на Хабре хотелось бы видеть какие-то не совсем очевидные вещи.