Кейсы в Kadence на ACF Pro: свой тип записи, вывод на главную и просмотры
Как завести кейсы отдельным типом записи, собрать поля в ACF Pro, вывести карточки на главную и подключить просмотры. С подводными камнями.
Кейс — это не статья. У статьи есть текст и картинка, у кейса есть структура: клиент, задача, что сделали, результат, отрасль. Эту структуру нужно показывать одинаково на всех кейсах, выводить кусками в разных местах сайта и фильтровать. Обычная запись такого не умеет, и попытка сделать кейсы страницами разваливается на третьем кейсе.
Ниже — рабочая схема на Kadence и ACF Pro: от того, зачем вообще заводить отдельный тип записи, до готовых сниппетов. Порядок такой: сначала принципы, потом шаги, потом подводные камни, которые стоили мне отдельного дня.
Зачем отдельный тип записи
Соблазн понятный: сделать кейсы обычными записями в отдельной рубрике или просто страницами. И то, и другое ломается быстро.
Страницы не годятся. У страниц нет архива, нет ленты, нет рубрик. Каждый новый кейс придётся руками вставлять в общий список, а список — руками сортировать. На пятом кейсе редактор начнёт забывать это делать.
Рубрика записей не годится тоже. Технически лента появится, но кейсы смешаются с блогом: они полезут в поиск по блогу, в RSS, в блок «Похожие статьи», в счётчик записей рубрики. А главное — оформление карточки в архиве настраивается один раз на весь тип записи, и вы не сможете сделать карточку кейса не такой, как карточка статьи.
Свой тип записи решает всё это разом. Он даёт: отдельный пункт в админке, отдельный адрес архива, отдельные настройки карточек в Kadence, отдельные таксономии и полную изоляцию от блога. Плюс редактор больше не выбирает «куда положить» — он просто добавляет кейс.
Зачем ACF и чего он не делает
Структуру кейса нужно где-то хранить. Писать её прямо в редакторе — значит доверить единообразие человеку: один напишет «Сфера: услуги», другой «Отрасль — услуги», третий забудет. Через полгода вы не соберёте из этого ни фильтр, ни витрину.
ACF даёт отдельные поля под каждый факт: название компании, форма собственности, система налогообложения, число сотрудников. Редактор заполняет форму, а не сочиняет вёрстку. Поля можно сделать обязательными, ограничить длину, дать выпадающий список вместо свободного ввода.
Но вот что важно понять сразу, и об это спотыкаются все. ACF хранит и редактирует данные. Он их не выводит. Заполнив поля, вы не увидите на сайте ничего: тема про ваши поля не знает и знать не может. Вывод — всегда отдельная работа: либо файл шаблона в дочерней теме, либо сниппет. Это не недостаток плагина, это разделение ответственности.
Нужна ли именно Pro-версия: да, если вам нужны повторители (список шагов «что сделали»), галереи и вкладки в форме редактирования. На бесплатной версии повторителя нет, и список шагов придётся имитировать десятью отдельными полями — так делать не надо.
Три слоя, которые нельзя путать
Вся конструкция делится на три слоя, и 90% ошибок — это попытка сделать работу одного слоя средствами другого.
- Данные. Тип записи, таксономии, поля. Живут в ACF, настраиваются мышкой.
- Вывод. Что из полей и в каком порядке попадает на страницу. Живёт в сниппете или шаблоне дочерней темы. Только здесь уместен PHP.
- Оформление. Сетка карточек, отступы, шрифты, полоса заголовка, мета. Живёт в Настройщике Kadence, в разделе своего типа записи.
Практическое следствие: прежде чем писать CSS, найдите настройку. На реальном проекте три захода правок CSS ради одинаковой высоты карточек оказались лишними — высоту делала разная длина описания, а она задаётся одним полем. Если настройки правда нет, это нужно доказать: назвать раздел, где искали.
Шаг 1. Регистрируем тип записи
В ACF Pro 6 это делается мышкой: ACF → Типы записей → Добавить. Кодом регистрировать не нужно — и не нужно смешивать: если часть настроек мышкой, а часть в PHP, править потом сможет только разработчик.
- Множественное и единственное название: «Кейсы» и «Кейс».
- Ярлык латиницей — он попадёт в адрес:
case. - Архив: включить и задать адрес, например
keysy. Без архива не будет ленты кейсов. - Поддержка: заголовок, миниатюра, редактор, произвольные поля, отрывок, ревизии.
- Показывать в REST: включить, иначе не заработает блочный редактор.
- Иконка меню — любая узнаваемая, чтобы редактор не путал разделы.
Проверка: в админке появился отдельный пункт меню, а по адресу вида сайт.ру/keysy/ открывается лента.
Шаг 2. Таксономии — если нужна фильтрация
Таксономия нужна тогда, когда по значению будут фильтровать или собирать подборки: «кейсы для розницы», «кейсы про восстановление учёта». Если значение просто показывается в карточке и никогда не служит фильтром — это поле ACF, а не таксономия. Путаница здесь плодит лишние сущности.
Регистрировать можно там же в ACF (ACF → Таксономии) или кодом. Кодом это выглядит так:
add_action( 'init', function () {
register_taxonomy( 'case_industry', 'case', array(
'label' => 'Отрасли',
'hierarchical' => true,
'show_in_rest' => true,
) );
register_taxonomy( 'case_task', 'case', array(
'label' => 'Типы задач',
'hierarchical' => true,
'show_in_rest' => true,
) );
} ); Подводный камень. Зарегистрировать таксономию мало — её ещё нужно где-то выводить. На реальном проекте отрасли и типы задач были заведены, заполнены и не показывались нигде: ни на кейсе, ни в карточке. Полгода данные копились впустую. Проверяйте не наличие таксономии в админке, а её присутствие на отрисованной странице.
Шаг 3. Поля кейса
Группа полей привязывается к типу записи условием «Тип записи равно Кейс». Дальше — сами поля. Рабочий набор, проверенный на живом проекте:
- Короткое описание для карточки — текстовая область, обязательное, ограничение 160 символов. Это то, что видно в ленте и на главной.
- О клиенте — название компании, сфера, форма собственности (выпадающий список, не текст), система налогообложения, число сотрудников, документов в месяц, регион.
- Задача — текстовая область, обязательное.
- Что сделали — повторитель с двумя подполями: заголовок шага и пояснение.
- Результат — текстовая область.
- Галерея — если показываете скриншоты.
Вкладки в группе полей — не украшение. Форма кейса длинная, и без вкладок «Карточка», «О клиенте», «Суть», «Медиа» редактор тонет.
Обязательные поля ставьте сразу. Если «короткое описание» не обязательно, рано или поздно кейс уйдёт в ленту с пустой карточкой. Выпадающий список вместо текста — по той же причине: «ООО», «ооо» и «О.О.О.» в одном поле убивают любую группировку.
Поля можно объявить и кодом, через acf_add_local_field_group(). Это оправдано, когда одну и ту же схему вы переносите между проектами: код едет вместе со сниппетом. Но правило простое: либо всё мышкой, либо всё кодом. Половина там, половина здесь — и следующая правка становится невозможной без разработчика.
Шаг 4. Отключаем редактор — и что за это придётся заплатить
Если весь кейс собирается из полей, редактор блоков только мешает: в нём можно написать что угодно мимо структуры. Отключается одной строкой:
add_action( 'init', function () {
remove_post_type_support( 'case', 'editor' );
} ); Но здесь начинается самое интересное, и об этом мало кто предупреждает. Отключив редактор, вы остались без содержимого записи. А из содержимого WordPress делает отрывок. Значит:
- автоотрывок пустой — в карточке архива будет пусто;
- счётчик времени чтения покажет ноль;
- поиск по сайту кейс не найдёт по тексту;
- любой плагин, который читает
the_content, увидит пустоту.
Отрывок чиним фильтром: подставляем в него то самое короткое описание из поля.
add_filter( 'get_the_excerpt', function ( $excerpt, $post ) {
if ( ! $post || 'case' !== $post->post_type ) {
return $excerpt;
}
if ( ! function_exists( 'get_field' ) ) {
return $excerpt;
}
$lead = trim( (string) get_field( 'case_lead', $post->ID ) );
return '' !== $lead ? wp_trim_words( $lead, 24 ) : $excerpt;
}, 10, 2 ); Число слов лучше вынести в константу и использовать её везде, где режется описание, — иначе в архиве будет одна длина, а на главной другая.
Шаг 5. Выводим одиночный кейс
Вот здесь и живёт весь PHP. Два пути: файл single-case.php в дочерней теме или фильтр the_content в сниппете. Кода одинаково, но сниппет переживает обновления темы и виден в одном списке рядом с остальными правками — поэтому обычно берут его.
add_filter( 'the_content', function ( $content ) {
if ( ! is_singular( 'case' ) || ! in_the_loop() || ! is_main_query() ) {
return $content;
}
if ( ! function_exists( 'get_field' ) ) {
return $content;
}
$id = get_the_ID();
$g = function ( $key ) use ( $id ) {
return trim( (string) get_field( $key, $id ) );
};
$out = '';
// Факты о клиенте — плитками.
$facts = array_filter( array(
'Сфера' => $g( 'client_industry' ),
'Форма' => $g( 'client_form' ),
'Налогообложение' => $g( 'client_tax' ),
'Сотрудников' => $g( 'client_staff' ),
'Регион' => $g( 'client_region' ),
) );
if ( $facts ) {
$out .= '<div class="case__facts">';
foreach ( $facts as $label => $value ) {
$out .= '<div class="case__fact"><span>' . esc_html( $label ) . '</span>'
. esc_html( $value ) . '</div>';
}
$out .= '</div>';
}
// Задача.
if ( $g( 'task' ) ) {
$out .= '<h2>Задача</h2><p>' . esc_html( $g( 'task' ) ) . '</p>';
}
// Что сделали — повторитель.
if ( have_rows( 'steps', $id ) ) {
$out .= '<h2>Что сделали</h2><ol class="case__steps">';
while ( have_rows( 'steps', $id ) ) {
the_row();
$out .= '<li><strong>' . esc_html( get_sub_field( 'step_title' ) ) . '</strong>'
. wpautop( esc_html( get_sub_field( 'step_text' ) ) ) . '</li>';
}
$out .= '</ol>';
}
return $out . $content;
} ); Три вещи, которые легко упустить. Проверка in_the_loop() и is_main_query() обязательна — без неё содержимое кейса полезет в блок «Похожие», в поиск и в выдачу REST. Всё, что пришло от редактора, экранируется через esc_html(). И повторитель обходится через have_rows(), а не через get_field() с ручным перебором массива.
Шаг 6. Архив кейсов — это настройки, а не код
Как только тип записи зарегистрирован, в Настройщике Kadence появляется свой раздел: Внешний вид → Настроить → Макет Записей и Страниц → Кейсы Архив. Там задаётся почти всё оформление ленты.
- Стиль содержимого — «в рамке» даёт карточки с фоном. При «без рамки» карточек нет вообще, и их начинают рисовать в CSS.
- Колонок — число колонок ленты.
- Элементы отдельных записей — состав карточки: изображение, рубрики, заголовок, мета, отрывок, «Читать далее». Перетаскиванием.
- Мета → Разделитель — черта, слэш или дефис между элементами.
- Шрифт названия — кегль заголовка карточки, отдельно для трёх устройств.
- Выравнивание названия архива и минимальная высота — про полосу заголовка.
Самый частый лишний код. Карточки разной высоты и «Читать далее» на разных уровнях — почти всегда следствие разной длины описания, а не отсутствия выравнивания. Ограничьте описание — и карточки станут равными сами. Ни flex, ни height: 100% не понадобятся: равную высоту карточек в ряду Kadence держит сам, ячейка списка объявлена как flex-колонка, а статья внутри получает flex: 1 1 0%. Проверяется одним замером — выключите свой CSS и сравните высоты соседних карточек.
Шаг 7. Кейсы на главной — тем же циклом, что и архив
Тут два пути, и выбор определяет, сколько кода вы будете поддерживать.
Путь первый: блок «Сетка записей». Ставится мышкой, умеет выбирать тип записи. Минус в том, что всё оформление блока живёт в его атрибутах, отдельно от настроек архива. Меняете кегль в Настройщике — на главной он не меняется. Через месяц карточки в двух местах разъезжаются, и разницу приходится догонять руками.
Путь второй: запустить цикл темы шорткодом. Тогда карточка на главной — буквально та же самая, что в архиве, и слушается тех же настроек.
add_shortcode( 'site_cards', function ( $atts ) {
$a = shortcode_atts( array(
'count' => 3,
'columns' => 3,
'post_type' => 'case',
), $atts, 'site_cards' );
$post_type = post_type_exists( $a['post_type'] ) ? $a['post_type'] : 'post';
$columns = max( 1, min( 4, (int) $a['columns'] ) );
$q = new WP_Query( array(
'post_type' => $post_type,
'post_status' => 'publish',
'posts_per_page' => max( 1, (int) $a['count'] ),
'ignore_sticky_posts' => true,
'no_found_rows' => true,
) );
if ( ! $q->have_posts() ) {
return '';
}
ob_start();
printf(
'<ul class="site-cards content-wrap kadence-posts-list grid-cols post-archive grid-sm-col-2 grid-lg-col-%d item-image-style-above">',
$columns
);
while ( $q->have_posts() ) {
$q->the_post();
get_template_part( 'template-parts/content/entry' );
}
echo '</ul>';
wp_reset_postdata();
return ob_get_clean();
} ); На страницу ставится блоком «Шорткод»: [site_cards post_type="case" count="3"]. Классы обёртки — не декорация: именно они подключают сетку и адаптив темы, поэтому свой CSS для колонок не нужен.
Ловушка, которая тут ждёт. Kadence печатает CSS архива с привязкой к типу страницы — селекторами вида body.archive .content-bg и .loop-entry.type-post h2.entry-title. На обычной странице таких классов у body нет, поэтому карточка останется без фона, а заголовок придёт третьим уровнем: вне архива шаблон отдаёт h3, и это правильно — на главной карточки стоят под заголовком секции. Нужны два связующих правила:
.site-cards article.loop-entry.content-bg {
background: var(--global-palette8);
}
.site-cards .loop-entry h3.entry-title {
font-size: 24px;
line-height: 1.3;
} Вес селектора здесь важен: правило темы body.page .content-bg весит (0,2,1), и короткое правило в два класса ему проиграет. Перед тем как писать CSS, соберите все правила, совпадающие с элементом, и сравните вес — это один запрос в консоли, экономящий часы.
Шаг 8. Просмотры кейсов
Счётчик — плагин Post Views Counter. Порядок такой: сначала настройки, потом вывод.
- Подсчёт → Типы записей: отметить «Кейсы». Пока тип не отмечен, счётчик у него всегда ноль — это самая частая причина «просмотры не работают».
- Подсчёт → Режим работы: если на сайте есть кэш страниц, PHP не годится — закэшированная страница отдаётся до запуска PHP. Бесплатны JavaScript и REST API, платен только «Быстрый AJAX».
- Подсчёт → Исключить посетителей: роботы и вошедшие пользователи. Следствие: под администратором счётчик расти не будет, проверять надо в приватном окне.
- Отображение: подпись пустая, стиль «только иконка», расположение «Вручную» — иначе плагин впечатает счётчик своим местом и он задвоится с вашим.
Вывод — хуком внутрь меты Kadence. Именно внутрь: тогда счётчик становится прямым потомком контейнера и получает штатный разделитель из настройки, без единой строки CSS.
add_action( 'kadence_before_loop_entry_meta', function () {
if ( ! function_exists( 'pvc_get_post_views' ) || ! function_exists( 'pvc_post_views' ) ) {
return;
}
$id = get_the_ID();
// Проверяем ЧИСЛО, а не разметку — почему, объяснено ниже.
if ( ! pvc_get_post_views( $id ) ) {
return;
}
echo '<span class="post-views-loop">' . pvc_post_views( $id, false ) . '</span>';
} ); Для страницы одиночного кейса хук другой — kadence_before_entry_meta. Если у кейса своя мета-строка в вашем шаблоне, счётчик добавляйте туда, иначе он задвоится.
Подводные камни
Всё ниже — не теория, а то, на чём я терял время. Каждый пункт с симптомом, причиной и лечением.
Плагин возвращает разметку, а не число
Симптом: у кейсов без просмотров в карточке висит «0» с иконкой. Причина: pvc_post_views( $id, false ) и шорткод возвращают готовый HTML, который непуст даже при нуле. Привычная проверка if ( $views ) ноль не отсекает. Лечение: спрашивать число через pvc_get_post_views( $id ) и только потом брать разметку. У меня эта ошибка нашлась в трёх местах сразу — в карточках, на странице кейса и в шорткоде главной.
Количество слов не режет ручные отрывки
Симптом: ставите в настройках архива «20 слов», а описания остаются длинными. Причина: настройка применяется только к автоотрывку. Если поле «Отрывок» заполнено или подставлено фильтром из ACF — тема его не трогает. Проверяется за минуту: поставьте пять слов и обновите архив. Лечение: резать в своём фильтре через wp_trim_words() и держать редакторское правило по длине описания.
Пустая настройка — это не разумный дефолт
Самая дорогая привычка — оставлять поля пустыми, считая, что «тема подставит нормальное». Она подставит своё:
{тип}_content_styleне задан → «в рамке» → +32px слева, и левый край кейса не совпадает с остальным сайтом. У меня крошки на кейсе стояли на 131px против 99px на страницах.{тип}_archive_title_alignне задан → заголовок архива встаёт по центру, тогда как весь сайт слева. Замер показал h1 на 683px вместо 99.{тип}_archive_title_heightне задан → фиксированные 200px, снизу остаётся мёртвое место.
Лечение: заполнять явно, даже если значение совпадает с дефолтом. И проверять левый край между типами страниц, а не только внутри одной: откройте подряд страницу, архив и кейс и снимите координату первого текстового узла.
Знак рубля превращается в мусор в адресе
Симптом: адрес кейса вида /keysy/snyali-380-000-%e2%82%bd/. Причина: WordPress кодирует символы, которых нет в транслитерации. Лечение: фильтр на sanitize_title, вычищающий процентное кодирование. Важно: он действует на новые записи. У существующего кейса адрес менять нельзя без 301-редиректа.
ACF не принимает частичное обновление
Если правите кейсы программно: нельзя менять статус записи и поля одним запросом — вернётся 400. Сначала публикация, потом поля. И нельзя отправить одно поле: ACF валидирует всю группу и упадёт на обязательных с сообщением «Неверный параметр: acf». Слать нужно вместе со всеми обязательными полями группы.
Kadence не знает про поля ACF
Архив и блок «Сетка записей» показывают штатное изображение записи, а не картинку из вашего поля. Если обложка кейса лежит в поле ACF, в ленте её не будет. Либо кладите обложку в миниатюру записи, либо рисуйте карточку сами.
Один сниппет — одна задача
Соблазн сложить всё про кейсы в один файл на 800 строк велик, но когда что-то сломается, отключать придётся всё сразу. Держите отдельно: регистрацию данных, вывод одиночного кейса, отрывок, просмотры, шорткод для главной, стили. Названия — говорящие, чтобы владелец сайта мог отключить одну штуку и посмотреть, что изменилось.
Менеджер сниппетов сам выключает сломанный код
Если в PHP-сниппете ошибка, плагин отключит его сам. Откат кода сам по себе его не включает: скобки сходятся, фаталов нет — а вывод исчез со всего сайта. И переключатель в интерфейсе показывает состояние до сбоя, реальное видно только после перезагрузки страницы редактора. Признак сохранения — исчезнувшая звёздочка в списке, а не длина текста в редакторе.
Порядок работ для исполнителя
Если отдаёте задачу подрядчику, вот последовательность, по которой её можно принимать по частям.
- Зарегистрировать тип записи в ACF, включить архив. Приёмка: открывается лента по адресу архива.
- Завести таксономии, если нужна фильтрация. Приёмка: термины видны и на странице кейса, и в карточке.
- Собрать группу полей с вкладками, обязательными полями и выпадающими списками. Приёмка: заполнить тестовый кейс, ничего не додумывая.
- Отключить редактор и починить отрывок фильтром. Приёмка: в карточке архива есть описание.
- Написать вывод одиночного кейса. Приёмка: все заполненные поля видны, незаполненные не оставляют пустых блоков.
- Настроить архив в Настройщике — без CSS. Приёмка: названы разделы, где что задано.
- Вывести кейсы на главную. Приёмка: карточка на главной совпадает с карточкой архива по кеглю, фону и высоте.
- Подключить просмотры. Приёмка: число растёт в приватном окне и не показывается нулём.
Чек-лист приёмки
- Тип записи и поля заведены в одном месте — либо всё мышкой, либо всё кодом.
- Все обязательные поля отмечены обязательными, форма собственности — выпадающий список.
- У каждого CSS-правила есть обоснование, почему настройки не хватило.
{тип}_content_style,_archive_title_alignи_archive_title_heightзаданы явно.- Левый край крошек и заголовка совпадает у страницы, архива и кейса.
- Карточки в ряду равной высоты, «Читать далее» на одном уровне.
- Описание в карточке ограничено и не обрывается на середине слова.
- Просмотры считаются для типа «Кейс», выводятся через разделитель и не печатают ноль.
- Проверено в приватном окне: под администратором просмотры не растут.
- На 390px нет горизонтальной прокрутки, карточки в одну колонку.
- Адреса новых кейсов без процентного кодирования.
- Каждый сниппет решает одну задачу и имеет говорящее имя.
Коротко
Отдельный тип записи нужен, чтобы кейсы не смешивались с блогом и получили собственное оформление. ACF нужен, чтобы структура кейса не зависела от аккуратности редактора. Но ACF только хранит данные — вывод всегда пишется руками, и это единственное место, где нужен PHP.
Всё остальное — сетка, отступы, состав карточки, разделители, кегли — задаётся в Настройщике Kadence. Правило, экономящее дни: сначала ищем настройку, и только если её правда нет — пишем код и записываем рядом, где именно искали.
