Каталог мест и организаций для журнала Doberman Media
проекте
Каталог на doberman.media/catalog — это карта и список из 295 ЛГБТК+-мест в 25 странах: клубы, сауны, отели, кафе, медицинская и юридическая помощь. Я сделал его целиком как WordPress-плагин sdwski-directory (версия 1.28.0, около 14 тысяч строк PHP, JS и CSS), без конструкторов и внешних API-ключей. Ниже — как каталог устроен и что о нём можно рассказать на странице проекта.
О проекте. Журналу Doberman.media нужен был каталог мест, где читателю можно безопасно и быстро найти клуб, сауну, врача или юриста в любом городе. Я спроектировал и написал его целиком: данные, карту, модерацию заявок, кабинет для владельцев мест и рекламные места.

Посетитель выбирает страну, город и категорию и видит все места на карте, а не только первую страницу списка. Карта рисует тысячи точек быстро, а вблизи превращает их в значки категорий. На странице места — фото, часы работы с статусом «открыто сейчас», маршрут в пяти картах, оценки и отзывы без пароля, форма «Сообщить об ошибке». Владельцы заведений сами правят свои страницы через отдельный кабинет, а я вижу каждое изменение.
- Фильтры. Выбор страны, затем города и категории; вкладки категорий со счётчиками (на сегодня: клубы и бары — 154, отели — 58, сауны и бани — 52, Cruising — 37, общественные организации — 25). Сверху итог «Найдено: 295».
- Поиск по названию, в том числе по переведённым названиям на другом языке сайта.
- Карточки. Фото, категория, название, рейтинг и число отзывов, адрес, график работы, кнопки телефона, Instagram, Facebook, сайта и «Отзывы».
- Переключатель «Список / Карта» на телефоне и плавающая панель фильтров поверх карты.
- «Обновлять при перемещении карты». Список подстраивается под видимую область карты.
- «Показать, где я». Геопозиция посетителя и сортировка списка от ближайших мест.
- Избранное. Сохранённые места лежат в браузере, без регистрации.
- Полноэкранный режим без шапки и подвала сайта и киоск-режим для демонстрации на экране.
- Кнопка «Добавить в каталог» и блок «Нет вашего места в каталоге?» в конце списка.
Реклама. В каталог встроена реклама: баннер между карточками после каждых N мест, вертикальный баннер у карты и блоки на странице места. Всё помечено честно, баннеры меняются из админки без правки кода. Также реклама есть в карте каталога.
Технологии. PHP и WordPress, Leaflet и MapLibre GL, OpenFreeMap, чистый JavaScript, REST API, schema.org, адаптивная вёрстка без фреймворков. Без ключей API и лимитов на показы.
Карта и поиск
Карта — главная часть интерфейса, и делает она не то, что обычный плагин с точками. Её движок — Leaflet, а векторные тайлы рисует MapLibre GL; аккаунты и ключи API не нужны, тайлы берутся из OpenFreeMap.
- Все места на карте, а не только текущая страница. Отдельный запрос без пагинации отдаёт координаты всех найденных мест (потолок — 5000), поэтому карта не зависит от того, на какой странице списка человек.
- Три уровня детализации. Далеко — маленькие цветные точки, нарисованные одним canvas, это быстро. Со среднего масштаба «пинами» становятся проверенные места; вблизи пины с иконкой категории получают все места в видимой области (не более 300 одновременно).
- Цвет и иконка категории. Из библиотеки встроенных иконок выбирается подходящая по названию (пламя для саун, бокал для баров, крест для медицины), и её можно поменять вручную.
- Стартовый вид — Европа. Если ничего не выбрано, карта открывается на ЕС, а после фильтра подстраивается под найденные места. Карта не повторяет мир по бокам и не уходит за полюса.
- Три стиля («Цветная», «Светлая», «Тёмная») с запоминанием выбора в браузере; в тёмной теме сайта карта сама берёт светлый стиль. Если у посетителя нет WebGL или векторные тайлы не загрузились за 12 секунд, карта автоматически переключается на растр OpenStreetMap.
- Попап места по клику на точку связан со списком: карточка слева подсвечивается и прокручивается к нужному месту.
- «Показать, где я». Точка посетителя с кругом точности рисуется поверх карты, а список сортируется по расстоянию, с подписью «км / м от вас». Геопозиция работает только по запросу посетителя и только по https.
- Список следит за картой. Если отмечено «Обновлять при перемещении карты», в списке остаются места из видимой области, с кнопкой «Показать ещё» и сообщением, если в этой части карты мест нет.
- Страница на всю ширину окна даже внутри узкой колонки темы; карта «прилипает» под шапкой сайта, высоту шапки скрипт определяет сам.
Страница места
Каждое место — это отдельная страница со своим шаблоном: в левой колонке текст, фото и отзывы, в правой — контакты, карта и часы работы. Шаблон работает и в классических, и в блочных темах WordPress; тема может подменить его своим файлом.

- Галерея с просмотром фото на весь экран и три видео YouTube. Видео сначала показывается как постер, а плеер загружается по клику, поэтому страница не тянет чужие скрипты.
- Удобства. Список того, что есть, и того, чего нет: недоступное отмечается красным крестиком, например «нет безбарьерного входа».
- Часы работы по дням недели (открыто, закрыто, круглосуточно). Статус «Сейчас открыто» считается в браузере по часовому поясу сайта, чтобы кэш страницы не показывал вчерашний статус. Ночные графики, когда клуб работает до утра, учтены.
- Маршрут. Посетитель выбирает «на машине / общественным транспортом / пешком», и ссылки на Google Maps, Яндекс Карты, Apple Maps, Waze и OpenStreetMap перестраиваются под этот режим; старт — текущая позиция человека.
- Карта места — тот же движок, что и в каталоге, с теми же стилями и запасным вариантом.
- «Сохранить» и «Поделиться». Избранное общее с главной страницей каталога: сохранённые места показывает одна панель в левом нижнем углу.
- Плашка «Данные не подтверждены». Если место не отмечено как «Проверено», посетитель честно увидит, что часы и контакты могут быть неактуальны, а рядом — кнопка «Это моя организация».

Отзывы и рейтинг
Оценки и отзывы работают поверх плагина комментариев SDWSKi Comments. Вход без пароля: код приходит на e-mail, один подтверждённый адрес — одна оценка на место, её можно изменить. После оценки открывается форма отзыва. Рейтинг виден и в карточках списка, а кнопка «Отзывы» ведёт прямо к блоку комментариев. Для одного места оценки можно отключить.
Обратная связь
В карточке «Обратная связь» пять причин: сообщить об ошибке, «это моя организация», «закрыто или переехало», пожаловаться, оставить комментарий. Сообщения сохраняются в админке и приходят на почту; если отправитель оставил почту, ответить можно прямо из письма. Защита от спама — скрытое поле-ловушка, одноразовый токен и лимит 5 сообщений в час с одного IP (хранится только хэш адреса).
Заявки, модерация и владельцы мест
Каталог наполняют три источника: я вручную или из CSV, посетители через форму «Добавить в каталог» и сами владельцы. Публичным списком и картой пользуются только опубликованные записи.
Форма «Добавить место»
- Посетитель заполняет форму; от ботов защищают поле-ловушка и лимит — 3 заявки в час с одного IP.
- Заявка сохраняется со статусом «На утверждении», а администратору уходит письмо со ссылкой.
- Адрес в фоне превращается в координаты. Существующие страна и город прикрепляются сразу, новые только запоминаются и создаются при публикации — список городов нельзя засорить заявками.
- Я проверяю заявку, правлю, ставлю «Проверено» и публикую.
Владельцы мест
Ресторатор, директор клуба или представитель НКО нажимает «Это моя организация». Этот повод сам по себе никаких прав не даёт: я проверяю человека и одним нажатием выдаю доступ.
- Создаётся аккаунт с ролью «Владелец места» без доступа в админку, и человеку приходит письмо со ссылкой для пароля.
- В кабинете владелец видит свои места и меняет название, описание, контакты, часы, удобства, точку на карте и фото.
- Категория, страна и город, отметка «Проверено» и включение отзывов остаются за администратором.
- Правки публикуются сразу, а я получаю письмо «было → стало» по каждому полю: ничего не происходит незаметно.
- Владелец может публиковать короткие новости места: не больше трёх, четвёртая вытесняет самую старую. Новости показываются на странице места и в любом блоке сайта шорткодом.
Инструменты администратора
В админке есть встроенная документация с быстрым стартом, всеми шорткодами и готовыми примерами; раздел «Группировка городов по странам» и инструмент латинских адресов; настройки страницы места и рекламы.
Рекламные места
Реклама встроена в плагин и настраивается в одном разделе админки «Каталог → Реклама». В каталоге три типа размещения, и каждый помечен надписью «Реклама», чтобы посетитель не путал её с местами.
| Размещение | Где видно | Как работает |
|---|---|---|
| Баннер в списке | между карточками мест, на всю ширину колонки | после каждых N карточек (N от 2 до 30, по умолчанию 4); баннеры идут по кругу, после последней карточки рекламы нет |
| Вертикальный баннер у карты | высокая колонка у правого края карты, на экранах от 1024 px | картинка со ссылкой или готовый HTML-файл; ширина задаётся вручную, у HTML-файла по умолчанию 300 px |
| Блок на странице места | в правой колонке и под отзывами | HTML-файлы sidebar.html и comments.html из папки wp-content/lgbtd-ads/; тот же блок можно вставить куда угодно на сайте |
Каждый баннер в списке — это либо картинка со ссылкой, либо код рекламной сети. Под баннером всегда стоит кнопка «Разместить баннер здесь» со ссылкой на страницу покупки рекламы журнала, поэтому каталог сам приводит рекламодателей к медиапредложению.
Детали, которые стоят отдельного упоминания
- Реклама живёт вместе со списком. Когда посетитель фильтрует места, двигает карту или сортирует по расстоянию, баннеры заново расставляются после каждых N видимых карточек, а не остаются на местах, где их оставил сервер.
- Блокировщики рекламы. У блока у карты нейтральные имена CSS-классов без слов «ad» и «banner», чтобы его не скрывали списки фильтров.
- Честная разметка. Ссылки баннеров открываются в новой вкладке с
rel="sponsored nofollow noopener", картинки грузятся лениво. - Кэш. После смены рекламы плагин sdwski-directory сам сбрасывает кэш популярных плагинов (WP Rocket, W3 Total Cache, LiteSpeed, SG Optimizer и др.), иначе посетители видели бы старый баннер.
- Диагностика для администратора. На странице настроек показано, что именно сохранено и почему баннер может не появляться; в исходном коде страницы для администратора оставляется служебный комментарий.
- Безопасность. Произвольный код в баннерах может сохранить только тот, у кого есть право
unfiltered_html; остальным скрипты вырезаются. - HTML-файлы по FTP. Рекламный блок можно не набирать в админке, а залить готовым файлом с анимацией и вёрсткой рекламодателя; имя файла проверяется (латиница, цифры,
-и_), чтобы нельзя было выйти за пределы папки.
Технические решения
Весь каталог — один плагин без зависимостей от конструкторов и тяжёлых фреймворков: PHP для серверной части, чистый JavaScript для карты и интерфейса, свой CSS. Из чужого — только Leaflet и MapLibre GL, оба лежат внутри плагина.
| Слой | Что используется |
|---|---|
| Сервер | WordPress, PHP: типы записей, таксономии, метаполя, REST API, AJAX, собственная таблица рейтингов |
| Карта | Leaflet 1.9.4, MapLibre GL 4.7.1, тайлы OpenFreeMap, резерв — OpenStreetMap |
| Геокодинг | Nominatim (OpenStreetMap), фоновая задача WordPress |
| Клиент | без фреймворков: directory.js (около 1300 строк), single.js, избранное в localStorage |
| Отзывы | плагин SDWSKi Comments, оценки привязаны к его подтверждённым аккаунтам |
| Перевод | файлы перевода ru_RU + стыковка с плагином перевода сайта (поиск по переведённым названиям, параметр lang в API) |
Производительность
- Тысячи точек рисуются одним canvas, полноценные пины — только для видимой области и не больше 300 штук.
- Стили каталога (46 КБ) подключаются только на страницах, где есть шорткод каталога, место или архив; на остальных страницах журнала они не грузятся.
- Версия файла скрипта собирается из версии плагина и времени изменения файла: браузер не держит старую копию после обновления.
- Данные, зависящие от времени («Открыто сейчас»), считаются в браузере, поэтому кэширование страниц не мешает.
- Карта не зависит от платных API: нет ключей Google или Mapbox и нет лимитов на показы.
Приватность и безопасность
- Избранное и позиция посетителя не уходят на сервер: первое хранится в браузере, вторая нужна только на карте и по запросу.
- Для лимитов на формы хранится только хэш IP; ответы форм защищены одноразовыми токенами и полем-ловушкой.
- Владельцы мест не получают доступа в админку, а каждое их изменение дублируется письмом администратору.
- Для неподтверждённых мест на странице стоит прямое предупреждение; в каталоге допускаются только вечные данные, без цен и анонсов событий.
- При удалении плагина места, категории и города остаются, пока я сам не включу полное удаление в
wp-config.php. - Плагин расширяется фильтрами WordPress (
lgbtd_rest_place,lgbtd_utm_params,lgbtd_schema_type,lgbtd_map_max_markers,lgbtd_kiosk_paramи другими), чтобы поведение менялось без правки ядра.