WP Manifestindependent plugin directory
manifest / content / partner-catalog

Partner Catalog

WordPress-плагин: каталог поставщиков/подрядчиков — типы, специализации, регионы, UTM-метки.

by llm-agent.ru · github.com/geliorn/partner-catalog

★ 0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/geliorn/partner-catalog/archive/refs/heads/main.zip

Partner Catalog (partner-catalog)

Версия 0.2.0 · Самодостаточный WordPress-плагин: каталог подрядчиков и сервис-провайдеров (интеграторы, дистрибьюторы, хостинги, облачные платформы и т.д.) с полностью ручным наполнением карточек через админку. Никакого коллектора/парсинга/внешнего API — в этом ключевое архитектурное отличие от llm-agent-github-repos. Не зависит от ACF/CPT UI — переносим на любой сайт копированием папки.

Проектный план и обоснование решений (референсы startpack.ru/workspace.ru, разбор полей, макеты) — docs/partner-catalog-plan.md в корне репозитория. Этот файл — справочник по тому, что реально реализовано в коде.


1. Структура данных

Тип записи (class-pc-cpt.php)

  • contractor (Подрядчики) — публичный, с архивом (/contractors/), show_in_rest. Поддержка: title, editor (полное описание компании через post_content), thumbnail (логотип), revisions.

Таксономии

  • contractor_type — тип сервиса/партнёра. Открытый список терминов, множественный выбор. Это ключевой механизм универсальности плагина: чтобы добавить новый тип сервиса (хостинг, облачная платформа, что угодно ещё) — не нужно трогать код, достаточно завести новый термин через админку. Компания может одновременно быть, например, и интегратором, и дистрибьютором.
  • contractor_specialization — специализация/платформы/технологии подрядчика (1С, Bitrix24, AWS, MCP/AI-агенты и т.д.). Множественный выбор, растущий тег-подобный список.
  • contractor_region — регион/город присутствия. Множественный выбор (компания может работать в нескольких регионах). В отличие от каталога GitHub-репозиториев (где регион осмысленно убрали как неприменимый), здесь это реальный, часто используемый фильтр — подтверждено обоими изученными референсами.

Поля (register_post_meta, show_in_rest: true — Block Bindings в паттернах работают сразу)

Полный список — PC_Meta_Fields::meta_keys():

Meta-ключ Назначение
_pc_short_description Короткое описание (1–2 предложения) для карточки в архиве — отдельное поле, не excerpt, чтобы не путать с SEO-описанием
_pc_website Сайт партнёра — никогда не выводится напрямую в разметку, см. раздел 4
_pc_phone, _pc_email, _pc_telegram, _pc_vk Контакты. Фиксированный набор полей, не универсальный репитер — см. «Осознанные упрощения» ниже
_pc_price_type, _pc_price_value Универсальное поле цены — см. раздел 2
_pc_features Особенности сервиса, тезисно — каждая мысль с новой строки, на выходе немаркированный список (блок partner-catalog/meta-list, раздел 3)
_pc_partnership Партнёрства/сертификации, каждая с новой строки — на выходе строка бейджей (тот же блок, стиль badges)
_pc_founded_year Год основания
_pc_team_size Размер команды (свободный текст, например «10–50 человек»)
_pc_clients Ключевые клиенты, каждый с новой строки — тот же список-блок
_pc_utm_override Ручные UTM-параметры для конкретной записи — см. раздел 4

Полное описание (post_content) — обычный Gutenberg-контент, редактируется в стандартном редакторе записи, не отдельное meta-поле. Так работает core/post-content в single-паттерне.


2. Цена — универсальное поле (_pc_price_type + _pc_price_value)

Разные типы сервисов озвучивают цену по-разному (интегратор — «от», хостинг — тариф/подписка, разработка под ключ — фиксированная сумма, консалтинг — почасовая ставка). Поэтому цена — это тип (select) + значение (текст), а не один свободный текст:

Тип (_pc_price_type) Как выводится
from «от {value} ₽»
range «{value} ₽» (значение уже вида «150 000 – 500 000»)
fixed «{value} ₽ за проект»
hourly «{value} ₽/час»
on_request «Цена по запросу» (значение не нужно)

В meta-боксе под полем — подсказка с примерами по каждому типу. Единицы измерения вроде «/мес» дописываются прямо в значении — форматтер их не навязывает (см. blocks/price/render.php).

Форматирование — не core/post-meta binding (он не умеет условной логики), а собственный маленький динамический блок partner-catalog/price (раздел 3).


3. Динамические блоки (class-pc-blocks.php, blocks/)

core/post-meta binding умеет только отдать значение поля как есть — не может превратить многострочный текст в список или применить условие по типу цены. Плюс два блока, заменяющие сторонние плагины (раздел 3а). Отсюда четыре маленьких серверных блока:

Блок Назначение
partner-catalog/meta-list Читает multiline meta-поле (_pc_features, _pc_partnership, _pc_clients), разбивает по строкам, рендерит <ul><li>. Атрибут style: bullet (обычный список) или badges (строка пилюль без маркеров — для партнёрств)
partner-catalog/price Форматирует _pc_price_type + _pc_price_value в готовую строку, см. раздел 2
partner-catalog/rating Свой рейтинговый виджет — см. раздел 3а
partner-catalog/related-posts Свой список похожих подрядчиков — см. раздел 3а

Все четыре — без сборки (JS через wp.blocks.registerBlockType, не собранный React/JSX), рендер — чистый PHP (render.php), поэтому корректно работают внутри Query Loop (пересчитываются на каждый элемент цикла, как core/post-title) — в отличие от шорткода, который резолвится один раз на всю страницу (см. известную находку про [ratings] в README llm-agent-github-repos, раздел 8б — та же причина, тот же класс проблемы, из-за которой рейтинг и похожие подрядчики тоже сделаны блоками, а не шорткодами).

3а. Свой рейтинг и «Похожие подрядчики» (class-pc-ratings.php, class-pc-related.php) — с версии 0.2.0

Решение 2026-09-04 (одновременно с тем же изменением в llm-agent-github-repos): WP-PostRatings и Contextual Related Posts убраны из зависимостей — обе функции по объёму оказались достаточно маленькими, чтобы держать свой код было разумнее, чем тянуть два сторонних плагина на каждый из 20 сайтов сети. Filter Everything остался сторонним (раздел 5) — там объём для самостоятельной переделки несоразмерный.

  • Рейтинг. Два postmeta-счётчика (_pc_rating_total, _pc_rating_count), инкремент атомарным UPDATE ... SET meta_value = meta_value + N (не read-then-write — иначе одновременные голоса теряются), голосование кликом по сердечку через AJAX (wp_ajax_pc_rate), один голос с браузера через cookie pc_voted_{id} — без другой защиты от накрутки, тот же уровень простоты, что у счётчика кликов (раздел 4.2).
  • Похожие подрядчики. get_posts() с tax_query (relation: OR) по общим терминам contractor_type ИЛИ contractor_specialization — точнее, чем generic keyword-matching CRP, раз у записи уже есть две структурные оси похожести.
  • Код не переиспользуется между partner-catalog и llm-agent-github-repos (дублирован) — оба плагина обязаны оставаться независимо переносимыми по отдельности.

4. Аналитика и UTM-метки

Плагин не только показывает карточки, но и считает переходы/клики по каждой и размечает исходящие ссылки UTM-метками. Без защиты от фрода/ботов — простой внутренний дашборд, не CPA-платформа.

4.1. UTM — авто или вручную, с приоритетом override (class-pc-links.php::build_final_url())

  • Авто (по умолчанию). Настройки плагина задают шаблон: utm_source, utm_medium, utm_campaign/utm_content с токенами {slug} и {contractor_type}.
  • Вручную (опционально, на уровне записи). Поле «Ручные UTM-параметры» (_pc_utm_override) в карточке подрядчика — строка вида utm_source=vk&utm_medium=cpc. Если заполнено — полностью заменяет автогенерацию для этой записи.
  • Реальный URL партнёра (_pc_website) никогда не печатается в разметке напрямую — только через генератор.

4.2. Счётчик переходов/кликов (class-pc-stats.php)

Своя таблица {prefix}pc_stats (contractor_id, event_type — view/click, stat_date, count; уникальный ключ на все три, инкремент через INSERT … ON DUPLICATE KEY UPDATE):

  • view — на каждый реальный показ single-contractor (template_redirect, без дедупликации по IP/сессии — намеренно просто).
  • click — все ссылки «на сайт партнёра» в паттернах ведут не на реальный сайт напрямую, а на собственный редирект-эндпоинт /out/{id}/ (class-pc-links.php). Обработчик: проверка записи → инкремент клика → подстановка UTM (раздел 4.1) → wp_redirect() (302). Один эндпоинт закрывает обе задачи.

Кнопка на сайте партнёра биндится не на реальное meta-поле, а на виртуальный _pc_outbound_url — оно не хранится в БД, значение подставляется на лету через short-circuit фильтра get_post_metadata (нужно, чтобы Block Bindings внутри Query Loop могли получить готовый URL редиректа для каждой карточки отдельно — то же ограничение шорткодов внутри цикла, что и в разделе 3).

4.3. Дашборд в админке (class-pc-admin.php, меню «Подрядчики → Аналитика»)

KPI (переходы, клики, конверсия %), график по дням (простой canvas без внешних библиотек — сознательно, чтобы не тянуть CDN-зависимость в wp-admin, см. assets/js/pc-admin-dashboard.js), таблица по подрядчикам, фильтры по типу/специализации/региону и по конкретной записи, диапазон дат.

4.4. Цели в Яндекс.Метрику (assets/js/pc-analytics.js)

ID счётчика — отдельное поле в настройках (не предполагается, что счётчик уже где-то программно доступен плагину). Две цели, обе no-op, если ym не определён или ID не задан:

  • partner_profile_view — при загрузке страницы подрядчика.
  • partner_click — по клику на ссылку с классом pc-outbound-link (только реальная кнопка «Перейти на сайт», не социальные ссылки — см. комментарий в class-pc-patterns.php::bound_button()).

5. Паттерны (class-pc-patterns.php)

Реализация макетов из docs/mockups/partner-catalog-pattern-*.html — не копия паттернов llm-agent-github-repos: там репозиторий со звёздами/языком/лицензией, здесь компания с контактами/специализацией/ценой — разный состав данных, разная композиция.

Паттерн Что это
partner-catalog/card Одна карточка подрядчика — содержимое Post Template внутри Query Loop. Единственный источник разметки карточки.
partner-catalog/archive-grid Сетка карточек для архива каталога/типа/специализации/региона, inherit:true + пагинация. Боковой AJAX-фильтр (Filter Everything) — настраивается отдельно в самой админке Filter Everything, тем же способом, что описан в README llm-agent-github-repos (раздел 8в) — в код паттерна не встроен.
partner-catalog/single-layout Шапка (лого, тип/специализация/регион, партнёрства, CTA на сайт) → рейтинг → статистика (год основания/команда/цена) → описание + особенности + клиенты → боковая колонка контактов → похожие подрядчики.

Гейтинг по интеграциям — блок рейтинга (partner-catalog/rating) и «Похожие подрядчики» (partner-catalog/related-posts) вставляются только если соответствующий тумблер в настройках включён (PC_Settings::integration_enabled()), проверяется на PHP-стороне в момент регистрации паттерна — тот же принцип, что в GitHub-плагине. Тумблеры больше не про «установлен ли сторонний плагин» (см. раздел 3а) — просто «включена ли фича».

Патterns — только вставляемые заготовки. Как и в GitHub-плагине: привязка паттернов к реальным шаблонам сайта (архив, single) — отдельный ручной шаг в Site Editor, не выполняется автоматически при регистрации.


6. Настройки (class-pc-admin.php, class-pc-settings.php)

Пункт меню «Подрядчики» (pc-main) + подпункты:

  • Настройки — UTM-шаблон (авто-режим), ID счётчика Яндекс.Метрики, SEO-плагин, интеграции (рейтинг / похожие подрядчики — свой код, раздел 3а; Filter Everything — флаг-документация, сторонний плагин).
  • Аналитика — дашборд, раздел 4.3.

Плюс штатные экраны CPT/таксономий: «Все подрядчики», «Тип сервиса», «Специализация», «Регион».


7. Осознанные упрощения / отличия от плана

  • Контакты — фиксированные поля (телефон/email/Telegram/VK), не универсальный репитер. В остальном стеке (включая llm-agent-github-repos) репитеров тоже нет ни одного — решено не вводить этот паттерн ради простоты ручного ввода. Если понадобится больше каналов связи — расширить список полей, не превращать в репитер без явной необходимости.
  • Функция «Сравнить» (2 колонки) — не реализована в этой версии. План (docs/partner-catalog-plan.md, раздел 8) явно относит её к «после MVP»: выбор чекбоксом на карточке → localStorage → отдельная страница сравнения по ID из query-параметров. Макет паттерна уже готов (docs/mockups/partner-catalog-pattern-compare.html), код — нет.
  • Матрица «платформа × конкретная услуга» (как на startpack.ru — для каждого вендора отдельный список услуг) — сознательно не реализована, отмечена в плане как возможное расширение, не нужное для ручного заполнения на старте.

8. Известные ограничения

  • Счётчики view/click не защищены от повторных заходов/ботов — если это станет проблемой (например, разброс конверсии из-за самих поисковых роботов), потребуется отдельная доработка (исключение известных User-Agent, троттлинг по IP/сессии).
  • UTM-override (_pc_utm_override) ожидает формат query-строки (key=value&key2=value2) — при опечатке в формате parse_str() либо ничего не распарсит, либо распарсит не то, что имелось в виду; валидации формата в meta-боксе нет.
  • Дашборд аналитики не пагинирует таблицу «по подрядчикам» — при большом количестве записей и активности стоит добавить лимит/постраничность (сейчас жёстко ограничено 30 строками через LIMIT, без навигации по остальным).