DEV Community

Андрей Викулов (VProger)
Андрей Викулов (VProger)

Posted on • Originally published at viku-lov.ru on

WordPress модуль для Яндекс.Карт: проекты на карте с ACF и кластеризацией

WordPress модуль для Яндекс.Карт: проекты на карте с ACF и кластеризацией

Если тебе нужно показать реализованные проекты на карте — например, установленное оборудование в разных городах — обычные решения либо слишком сложные, либо не дают нужной гибкости. Модуль проектов для WordPress решает эту задачу просто: кастомный тип записи для проектов, ACF поля для координат и адресов, интеграция с Яндекс.Картами с кластеризацией меток. Всё готово к использованию, легко переносится в другой сайт. (GitHub)

Главное преимущество: проекты не имеют публичных страниц — они используются только для отображения на карте. Это идеально для случаев, когда нужно показать локации без создания отдельных страниц для каждого проекта.


Задача: что нужно было решить

Типичная ситуация: у тебя есть список реализованных проектов (например, установленное оборудование), и нужно показать их на карте с информацией:

  • координаты (широта/долгота)
  • адрес
  • тип и модель оборудования
  • описание проекта

При этом:

  • проекты должны управляться через админку WordPress
  • карта должна автоматически группировать близкие метки (кластеризация)
  • решение должно быть переносимым в другой сайт
  • если проектов нет — показывать тестовые данные для демонстрации

Архитектура решения

Модуль состоит из четырёх основных файлов:

  1. projects.php — регистрация кастомного типа записи и таксономии
  2. projects-acf.php — ACF поля для координат и данных проектов
  3. projects-map.php — функции для работы с картой, подключение скриптов
  4. projects-loader.php — главный файл, который подключает всё остальное

Почему именно такая структура

Разделение на файлы делает модуль:

  • понятным — каждый файл отвечает за свою задачу
  • переносимым — можно скопировать папку и подключить в другом сайте
  • расширяемым — легко добавить новые поля или функции

Шаг 1: Регистрация кастомного типа записи

Файл: projects.php


<?php

add\_action('init', function(){

register\_post\_type('projects', array(

'labels' => array(

'name' => 'Проекты',

'singular\_name' => 'Проект',

'add\_new' => 'Добавить новый',

'add\_new\_item' => 'Добавить новый проект',

'edit\_item' => 'Редактировать проект',

'menu\_name' => 'Проекты',

),

'public' => false, // Проекты только для карты

'publicly\_queryable' => false, // Нельзя открыть отдельную страницу

'show\_in\_nav\_menus' => false, // Не показывать в меню

'supports' => ['title', 'editor', 'thumbnail'],

'show\_ui' => true, // Показывать в админке

'has\_archive' => false, // Нет архива

'show\_in\_rest' => true,

'menu\_icon' => 'dashicons-location-alt'

));

});

Enter fullscreen mode Exit fullscreen mode

Важно: 'public' => false означает, что проекты не имеют публичных URL. Они используются только для карты, что идеально для локаций без отдельных страниц.

Регистрация таксономии для типов оборудования


add\_action('init', function(){

register\_taxonomy('project-equipment', ['projects'], [

'label' => 'Тип оборудования',

'public' => false, // Таксономия не публичная

'show\_ui' => true,

'show\_in\_rest' => true,

'hierarchical' => true,

'show\_admin\_column' => true

]);

});

Enter fullscreen mode Exit fullscreen mode

Шаг 2: ACF поля для координат и данных

Файл: projects-acf.php

ACF поля создаются программно через acf_add_local_field_group(). Это удобно, потому что поля автоматически создаются при активации темы — не нужно настраивать их вручную.


<?php

if(function\_exists('acf\_add\_local\_field\_group')):

acf\_add\_local\_field\_group(array(

'key' => 'group\_projects\_fields',

'title' => 'Поля проекта',

'fields' => array(

array(

'key' => 'field\_project\_latitude',

'label' => 'Широта (Latitude)',

'name' => 'project\_latitude',

'type' => 'text',

'required' => 1,

'placeholder' => '55.751574',

),

array(

'key' => 'field\_project\_longitude',

'label' => 'Долгота (Longitude)',

'name' => 'project\_longitude',

'type' => 'text',

'required' => 1,

'placeholder' => '37.573856',

),

array(

'key' => 'field\_project\_address',

'label' => 'Адрес',

'name' => 'project\_address',

'type' => 'text',

),

array(

'key' => 'field\_project\_equipment\_type',

'label' => 'Тип оборудования',

'name' => 'project\_equipment\_type',

'type' => 'text',

),

array(

'key' => 'field\_project\_equipment\_model',

'label' => 'Модель оборудования',

'name' => 'project\_equipment\_model',

'type' => 'text',

),

),

'location' => array(

array(

array(

'param' => 'post\_type',

'operator' => '==',

'value' => 'projects',

),

),

),

));

endif;

Enter fullscreen mode Exit fullscreen mode

Почему программное создание полей:

  • Поля создаются автоматически при активации темы
  • Не нужно настраивать их вручную в админке
  • Легко перенести модуль в другой сайт

Шаг 3: Функция получения проектов для карты

Файл: projects.php


/\*\*

- Получить все проекты для карты
- @return array Массив проектов с координатами

\*/

function get\_projects\_for\_map() {

$projects = get\_posts(array(

'post\_type' => 'projects',

'posts\_per\_page' => -1,

'post\_status' => 'publish'

));

$projects\_data = array();

foreach ($projects as $project) {

$latitude = get\_field('project\_latitude', $project->ID);

$longitude = get\_field('project\_longitude', $project->ID);

$address = get\_field('project\_address', $project->ID);

$equipment\_type = get\_field('project\_equipment\_type', $project->ID);

$equipment\_model = get\_field('project\_equipment\_model', $project->ID);

// Пропускаем проекты без координат

if (empty($latitude) || empty($longitude)) {

continue;

}

$projects\_data[] = array(

'id' => $project->ID,

'title' => get\_the\_title($project->ID),

'latitude' => floatval($latitude),

'longitude' => floatval($longitude),

'address' => $address ?: '',

'equipment\_type' => $equipment\_type ?: '',

'equipment\_model' => $equipment\_model ?: '',

'description' => wp\_strip\_all\_tags(get\_the\_excerpt($project->ID))

);

}

return $projects\_data;

}

Enter fullscreen mode Exit fullscreen mode

Что делает функция:

  • Получает все опубликованные проекты
  • Извлекает ACF поля (координаты, адрес, оборудование)
  • Пропускает проекты без координат
  • Возвращает массив, готовый для передачи в JavaScript

Шаг 4: Подключение Яндекс.Карт и скриптов

Файл: projects-map.php


/\*\*

- Подключение скриптов и стилей для карты проектов

\*/

function enqueue\_projects\_map\_assets() {

// Подключаем только на странице с шаблоном карты

if (is\_page\_template('template-projects-map.php')) {

// Получаем API ключ из настроек ACF

$yandex\_api\_key = get\_field('yandex\_maps\_api\_key', 'options') ?: '';

if (empty($yandex\_api\_key)) {

$yandex\_api\_key = 'YOUR\_API\_KEY'; // Замените на ваш API ключ

}

// Регистрируем API Яндекс.Карт

wp\_register\_script(

'yandex-maps-api',

'https://api-maps.yandex.ru/2.1/?apikey=' . esc\_attr($yandex\_api\_key) . '&lang=ru\_RU',

array(),

'2.1',

false

);

// Получаем данные проектов

$projects\_data = get\_projects\_map\_data();

// Передаем данные в JavaScript

wp\_localize\_script('yandex-maps-api', 'projectsMapData', array(

'projects' => $projects\_data

));

// Подключаем скрипты

wp\_enqueue\_script('yandex-maps-api');

wp\_enqueue\_script(

'projects-map-js',

get\_template\_directory\_uri() . '/inc/modules/projects/assets/js/projects-map.js',

array('yandex-maps-api'),

'1.0.0',

true

);

// Подключаем стили

wp\_enqueue\_style(

'projects-map-css',

get\_template\_directory\_uri() . '/inc/modules/projects/assets/css/projects-map.css',

array(),

'1.0.0'

);

}

}

add\_action('wp\_enqueue\_scripts', 'enqueue\_projects\_map\_assets');

Enter fullscreen mode Exit fullscreen mode

Важные моменты:

  • Скрипты подключаются только на странице с шаблоном карты
  • API ключ берётся из настроек ACF (можно настроить в админке)
  • Данные проектов передаются в JavaScript через wp_localize_script()

Шаг 5: JavaScript для инициализации карты

Файл: assets/js/projects-map.js


(function () {

"use strict";

document.addEventListener("DOMContentLoaded", function () {

if (

typeof ymaps !== "undefined" &&

typeof projectsMapData !== "undefined"

) {

ymaps.ready(function () {

var projectsData = projectsMapData.projects || [];

// Создаем карту

var myMap = new ymaps.Map("yandex-map", {

center: [55.751574, 37.573856], // Москва по умолчанию

zoom: 5,

controls: ["zoomControl", "typeSelector", "fullscreenControl"],

});

// Создаем кластеры для меток

var clusterer = new ymaps.Clusterer({

preset: "islands#invertedBlueClusterIcons",

groupByCoordinates: false,

clusterDisableClickZoom: true,

clusterHideIconOnBalloonOpen: false,

geoObjectHideIconOnBalloonOpen: false,

});

// Добавляем метки для каждого проекта

projectsData.forEach(function (project) {

var placemark = new ymaps.Placemark(

[project.latitude, project.longitude],

{

balloonContentHeader:

"<strong>" + escapeHtml(project.title) + "</strong>",

balloonContentBody:

(project.equipment\_type

? "<p><strong>Тип оборудования:</strong> " +

escapeHtml(project.equipment\_type) +

"</p>"

: "") +

(project.equipment\_model

? "<p><strong>Модель:</strong> " +

escapeHtml(project.equipment\_model) +

"</p>"

: "") +

(project.address

? "<p><strong>Адрес:</strong> " +

escapeHtml(project.address) +

"</p>"

: "") +

(project.description

? "<p>" + escapeHtml(project.description) + "</p>"

: ""),

hintContent: escapeHtml(project.title),

},

{

preset: "islands#blueIcon",

}

);

clusterer.add(placemark);

});

myMap.geoObjects.add(clusterer);

// Устанавливаем границы карты, чтобы показать все метки

if (projectsData && projectsData.length > 0) {

myMap.setBounds(clusterer.getBounds(), {

checkZoomRange: true,

duration: 300,

});

}

});

}

});

// Функция для экранирования HTML (защита от XSS)

function escapeHtml(text) {

if (!text) return "";

var map = {

"&": "&amp;",

"<": "&lt;",

">": "&gt;",

'"': "&quot;",

"'": "&#039;",

};

return text.replace(/[&<>"']/g, function (m) {

return map[m];

});

}

})();

Enter fullscreen mode Exit fullscreen mode

Что делает код:

  • Инициализирует карту Яндекс.Карт
  • Создаёт кластеры для группировки близких меток
  • Добавляет метки для каждого проекта с балунами
  • Автоматически подстраивает границы карты под все метки
  • Экранирует HTML для защиты от XSS

Шаг 6: Fallback данные для демонстрации

Если проектов нет в базе данных, модуль автоматически использует тестовые данные. Это удобно для демонстрации функционала.

Файл: projects-map.php


/\*\*

- Получить данные проектов для карты (с fallback на тестовые данные)

\*/

function get\_projects\_map\_data() {

// Пытаемся получить данные из базы

if (function\_exists('get\_projects\_for\_map')) {

$projects\_data = get\_projects\_for\_map();

if (!empty($projects\_data)) {

return $projects\_data;

}

}

// Если проектов нет, возвращаем тестовые данные

return array(

array(

'id' => 1,

'title' => 'Крематор КД-300',

'latitude' => 55.751574,

'longitude' => 37.573856,

'address' => 'г. Москва, ул. Красная площадь, д. 1',

'equipment\_type' => 'Крематор',

'equipment\_model' => 'КД-300',

'description' => 'Установлен крематор КД-300 для утилизации отходов.'

),

// ... другие тестовые проекты

);

}

Enter fullscreen mode Exit fullscreen mode

Зачем это нужно:

  • Можно показать карту клиенту до добавления реальных проектов
  • Удобно для тестирования и разработки
  • Не нужно создавать тестовые записи в базе

Использование: создание страницы с картой

  1. Создай шаблон страницы

Файл: template-projects-map.php (в корне темы)


<?php

/\*\*

- Template Name: Карта проектов

\*/

get\_header();

?>

<div class="projects-map-page">

<?php

// Вывод заголовка и описания из ACF

$header = get\_field('header');

$description = get\_field('description');

if ($header) {

echo '<h1>' . esc\_html($header) . '</h1>';

}

if ($description) {

echo '<div class="projects-map-description">' . wp\_kses\_post($description) . '</div>';

}

// Вывод карты

if (function\_exists('render\_projects\_map')) {

render\_projects\_map();

}

?>

</div>

<?php

get\_footer();

Enter fullscreen mode Exit fullscreen mode
  1. Создай страницу в WordPress

  2. Перейди в СтраницыДобавить новую

  3. Название: "Реализованные проекты"

  4. Выбери шаблон: Карта проектов

  5. Заполни поля ACF (заголовок, описание)

  6. Опубликуй страницу

  7. Добавь проекты

  8. Перейди в ПроектыДобавить новый

  9. Заполни обязательные поля:

  • Название проекта: например, "Крематор КД-300"
  • Широта: 55.751574
  • Долгота: 37.573856
  • Адрес: "г. Москва, ул. Красная площадь, д. 1"
  • Тип оборудования: "Крематор"
  • Модель оборудования: "КД-300"
  1. Опубликуй проект

Перенос модуля в другой сайт

Модуль легко переносится в другой сайт:

  1. Скопируй папку модуля

cp -r projects /path/to/new/theme/inc/modules/

Enter fullscreen mode Exit fullscreen mode
  1. Подключи модуль в functions.php

require\_once get\_template\_directory() . '/inc/modules/projects/projects-loader.php';

Enter fullscreen mode Exit fullscreen mode
  1. Установи ACF (если ещё не установлен)

  2. Настрой API ключ

  • Получи ключ на developer.tech.yandex.ru
  • Установи в настройках темы: Настройки контентаНастройки Яндекс.Карт
  1. Создай шаблон страницы (см. раздел "Использование")

  2. Создай страницу с картой и добавь проекты


Кастомизация

Изменение внешнего вида карты

Файл: assets/css/projects-map.css


.projects-map\_\_container {

width: 100%;

height: 600px;

margin: 20px 0;

}

.projects-map\_\_map {

width: 100%;

height: 100%;

border-radius: 8px;

box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);

}

Enter fullscreen mode Exit fullscreen mode

Изменение настроек карты

Файл: assets/js/projects-map.js


// Изменить центр карты

center: [59.934280, 30.335098], // Санкт-Петербург

// Изменить масштаб

zoom: 10,

// Изменить иконки меток

preset: 'islands#redIcon'

Enter fullscreen mode Exit fullscreen mode

Добавление дополнительных полей

  1. Добавь поле в ACF (projects-acf.php)
  2. Добавь поле в функцию get_projects_for_map() (projects.php)
  3. Используй поле в JavaScript (assets/js/projects-map.js)

Безопасность

Модуль включает базовые меры безопасности:

  • Проверка прав доступа — все функции проверяют права доступа
  • Экранирование данных — все выходные данные экранируются через esc_html(), esc_attr(), wp_kses_post()
  • Валидация координат — проверка корректности координат перед добавлением на карту
  • Защита от XSS — экранирование HTML в JavaScript через функцию escapeHtml()

Частые проблемы и решения

Проблема: Карта не отображается

  • Решение: Проверь API ключ Яндекс.Карт, проверь консоль браузера на ошибки

Проблема: Метки не появляются на карте

  • Решение: Убедись, что проекты опубликованы и имеют координаты

Проблема: Поля ACF не отображаются

  • Решение: Убедись, что ACF установлен и активирован

Проблема: Скрипты не подключаются

  • Решение: Проверь, что страница использует шаблон template-projects-map.php

Итог

Модуль проектов для WordPress — это готовое решение для отображения локаций на карте:

  • ✅ Кастомный тип записи для управления проектами
  • ✅ ACF поля для координат и данных
  • ✅ Интеграция с Яндекс.Картами с кластеризацией меток
  • ✅ Адаптивный дизайн
  • ✅ Fallback данные для демонстрации
  • ✅ Легкий перенос в другой сайт

Если тебе нужно показать реализованные проекты на карте без создания отдельных страниц для каждого проекта — этот модуль решает задачу просто и эффективно.


Ссылки

Read more on viku-lov.ru

Top comments (0)