Carbon Fields — бесплатный аналог ACF

Вступление

Advanced Custom Fields (ACF) очень раскручен и им все пользуются. Но у ACF есть ряд минусов:

  • Для создания полей ACF использует громосткий, неудобный интерфейс, который, в некоторых версиях, часто глючит, некорректно сохраняет данные. А у Carbon Fields (CF) поля создаются прямо в коде. Для разработчика это удобно, а человеку без знаний PHP интерфейс ACF бесполезен, потому что данные из новых полей еще нужно вывести в файлах темы. В итоге не понятно, для кого ACF сделан
  • Carbon Fields полностью бесплатный (open source), а по возможностям не уступает платной версии ACF
  • Если вам нужно перенести какие-то наработки по интерфейсу, то у ACF это делается через импорт-экспорт файла с параметрами. У CF достаточно скопировать и вставить код интерфейса и подправить в нужных местах. По опыту — это делается гораздо быстрее и удобней.

Еще CF можно подключить как библиотеку прямо в тему, а не как плагин. Через Composer. Чаще всего это очень удобно. Особенно, когда приходится подключать несколько библиотек, типа PHPWord или DomPDF. В этом случае у нас идет всего одно подключение через functions.php к автолоадеру, который подключает все остальное. В итоге код получается чище и понятней.

По этим и другим причинам я не люблю ACF и предпочитаю Carbon Fields. Поэтому я решил рассказать о нем:

  • Как установить и начать пользоваться
  • Как создать настройки темы и кастомные поля для любых типов записей.

Возможно, эта статья кому-то упростит жизнь 🙂

Официальный сайт Carbon Fields — carbonfields.net

Чтобы понимать статью, полезно знать основы ООП в PHP, пространства имён и хуков WP. Тему хуков я разобрал в этой статье.

Как установить

Есть 2 основных способа установки: по-проще и по-сложнее 🙂

Рассмотрим оба.

Способ по-проще: установить как плагин

Сначала идём по ссылке — https://docs.carbonfields.net/plugin-quickstart.html#without-composer

Дальше нажимаем сюда, чтобы скачать плагин:

Скачанный архив устанавливаем как обычный плагин WP.

Дальше открываем functions.php вашей темы и пишем код:

<?php
/**
 * Укажем пространства имен. Их лучше прописать в самом верху файла
 **/
use Carbon_Fields\Container;
use Carbon_Fields\Field;

/**
 * Через хук вызовем функцию для создания настроек темы
 **/
add_action( 'carbon_fields_register_fields', 'feodoraxis_attach_theme_options' );
function feodoraxis_attach_theme_options() {
    Container::make( 'theme_options', __( 'Theme Options' ) )
        ->add_fields( array(
            Field::make( 'text', 'feodoraxis_text', 'Text Field' ),
        ) );
}

Вообще, такие вещи лучше делать в отдельном файле. Например, в теме создать файл /inc/template-carbon-fields.php, подключить его в functions.php и работать с CF там.

Отлично! Если вы всё сделали правильно, то увидите в админке такую картину:

Способ по-сложнее: через Composer

Тут вам нужно уметь хоть немного работать с терминалом, а также установить composer на свой компьютер или сервер. Если работаете на сервере — то потребуется доступ по SSH через Terminal.

Если вы еще не разобрались с composer, то лучше это сделать. При помощи composer вы сможете легко устанавливать и удалять разные PHP-библиотеки для вашего проекта. При этом не придется каждый раз добавлять/убирать код подключения доп. файлов. Кроме того, ваш проект будет «чище» с точки зрения расположения файлов и директорий, что упростит его разработку и поддержку.

Если не получается установить composer на компьютер — попробуйте его на хостинге beget через ssh прямо в панели управления. Это очень удобно и я сам этим постоянно пользуюсь 🙂

Я предполагаю, что вы работаете на локальной машине, поэтому не учитываю подключение по SSH. Если вы подключились — то дальше действия такие-же.

Для начала перейдем в директорию темы сайта:

cd wp-content/themes/twentytwentytwo/

Важно: вы уже должны находиться в директории сайта

Дальше выполним команду:

composer require htmlburger/carbon-fields

Теперь в functions.php добавляем код:

<?php
/**
 * Укажем пространства имен. Их лучше прописать в самом верху файла
 **/
use Carbon_Fields\Container;
use Carbon_Fields\Field;

/**
 * Сначала подключим loader композера (composer)
 *
 * Подключив loader один раз, мы автоматически подключаем остальные решения,
 * которые в будущем захотим установить через composer.
 **/
add_action( 'after_setup_theme', 'feodoraxis_load' );
function feodoraxis_load() {
    require_once( __DIR__ . '/vendor/autoload.php' );
    \Carbon_Fields\Carbon_Fields::boot();
}

/**
 * Через хук вызовем функцию для создания настроек темы
 **/
add_action( 'carbon_fields_register_fields', 'feodoraxis_attach_theme_options' );
function feodoraxis_attach_theme_options() {
    Container::make( 'theme_options', __( 'Theme Options' ) )
        ->add_fields( array(
            Field::make( 'text', 'crb_text', 'Text Field' ),
        ) );
}

Если вы всё сделали правильно, то увидите в админке такую картину:

Основы, теория

Здесь я расскажу о принципах работы CF и дам советы из моей практики, которые могут упростить вам работу 🙂

Пространства имён

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

Например: если вы создаёте только кастомные поля, то вам нужны класс Container и Field. Их можно подключить так:

<?php
use Carbon_Fields\Container;
use Carbon_Fields\Field;

Если создаёте блок Gutenberg — то нужно подключить классы Field и Block:

<?php
use Carbon_Fields\Field;
use Carbon_Fields\Block;

Где указывать кастомные поля

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

В начале лучше-бы рассказать о контейнерах (Containers), но я решил сначала ответить на более насущные вопросы.
Тем не менее, не поленитесь и почитайте о Контейнерах в CF ниже 🙂

На один хук можно «повесить» хоть сотню функций, поэтому для каждой задачи лучше подключать свою.

Например: для настроек темы одна функция; для записей — другая; для товаров — третья; для блока Gutenberg — четвёртая. Выглядеть это может примерно так:

<?php
use Carbon_Fields\Container;
use Carbon_Fields\Field;

/**
 * Настройки темы. 
 * 
 * Последний параметр у add_action() - приоритет выполнения функции.
 * Это значит, что если у хука carbon_fields_register_fields
 * будет еще 9 функций с приоритетом ниже 10 - то сначала будут
 * выполнены они, и только потом - наша
 **/
add_action( 'carbon_fields_register_fields', 'feodoraxis_attach_theme_options', 10 );
function feodoraxis_attach_theme_options() {
    Container::make( 'theme_options', __( 'Theme Options' ) )
        ->add_fields( array(
            Field::make( 'text', 'feodoraxis_text', 'Text Field' ),
        ) );
}

/**
 * Кастомные поля для записей
 **/
add_action( 'carbon_fields_register_fields', 'feodoraxis_posts_fields', 20 );
function feodoraxis_posts_fields() {
    Container::make( 'post_meta', 'Мои кастомные поля' )
        ->where( 'post_type', '=', 'post' ) // укажем условие, чтобы поля выводились только у записей
        ->add_fields([
            Field::make( 'text', 'feodoraxis-subtitle', 'Подзаголовок' )
                ->set_width(70),
            Field::make( 'media_gallery', 'feodoraxis-gallery', 'Галерея изображений' )
                ->set_width(30),
        ])
}

/**
 * Кастомные поля для товаров WooCommerce
 * 
 * Конечно, у WC характеристики лучше делать через Атрибуты
 **/
add_action( 'carbon_fields_register_fields', 'feodoraxis_products_fields', 30 );
function feodoraxis_products_fields() {
    Container::make( 'post_meta', 'Дополнительные данные' )
        ->where( 'post_type', '=', 'product' ) // укажем условие, чтобы поля выводились только у товаров WooCommerce
        ->add_fields([
            Field::make( 'select', 'feodoraxis-paper-type', 'Тип бумаги' )
                ->set_options([
                    'gloss' => 'Глянец',
                    'mate' => 'Матовый'
                ])
        ])
}

Вообще, я предпочитаю раскидывать код по файлам и подключать их автолоадером. Дело в том, что когда у вас появляется много настроек, то работать с «колбасой» кода становится неудобно. А когда все раскидано по файлам с логичным названием — то и работать проще.

Помните, с помощью CF вы можете создать кастомные поля почти для любого типа записей: любые записи (posts), комментарии, категории, метки (таксономии) и прочее. Это открывает большие возможности при работе.

Условия (conditions)

У CF можно настроить отображение полей ввода при определенных условиях.

Например: показывать поле ввода только тогда, когда отмечен какой-то чекбокс:

Field::make( 'checkbox' 'feodoraxis-condition', 'Показать поле ввода?' ),
Field::make( 'text', 'feodoraxis-text', 'Поле ввода' )
    ->set_conditional_logic( array(
        'relation' => 'AND', // Опционально, стандартно "AND"
        array(
            'field' => 'feodoraxis-condition', //Укажем имя поля, значение которого определяет условие
            'value' => true, // какое значение должно принять поле. true - если чекбокс отмечен
            'compare' => '=', // Способ сравнения. Опционально, стандартно "=". Доступные операторы: =, <, >, <=, >=, IN, NOT IN
        )
    ) ),

Теперь текстовое поле будет доступно только тогда, когда чекбокс отмечен.

Контейнеры (Containers)

Создание настроек темы, кастомных полей записей, таксономий, комментариев — это всё создаётся через контейнеры (containers).

Контейнер — это группа кастомных полей и настроек отображения. Контейнеры отображаются в разных частях бекенда, в зависимости от их типа настроек отображения.

Это перевод определения с официального сайта. Мне оно не нравится. Поэтому скажу по-своему:

В контейнерах мы создаем все кастомные поля и блоки Gutenberg. Это основа всех кастомных полей в CF. Сам контейнер может отображаться в разных разделах админки: при добавлении/редактировании записи, таксономии, комментария, или, например, создать страницу настроек темы (theme_options).

Доступны следующие типы контейнеров:

  • Comment Meta — Комментарии
  • Gutenberg Blocks — Создание блоков Gutenberg
  • Nav Menu Item — Настройки для пунктов меню
  • Network — Поля для страниц в Мультсайтах
  • Post Meta — Записи, страницы, товары WooCommerce, любые записи типа post
  • Term Meta — Рубрики, метки, любые таксономии
  • Theme Options — Настройки темы
  • User Meta — Пользователи
  • Widgets — Виджеты. Т.е. можно создавать свои виджеты, не придумывая новый плагин.

При этом отображение контейнера, как мы видели выше, можно настроить, чтобы он выводился только в нужных типах записей. Например, не во всех записях типа post, а только у страниц:

Container::make( 'post_meta', 'Дополнительные данные' )
    ->where( 'post_type', '=', 'page' )

Настройки темы

Конечно, настройки темы правильно создавать при помощи нативного кастомизатора WP. Но работать с CF гораздо проще, при этом возможности из коробки — шире.

У CF можно создать не только блок с кастомными полями, но и разбить их на табы. Часто мой код выглядит так:

<?php
use Carbon_Fields\Container;
use Carbon_Fields\Field;

add_action( 'carbon_fields_register_fields', 'feodoraxis_attach_theme_options', 10 );
function feodoraxis_attach_theme_options() {
    Container::make( 'theme_options', __( 'Theme Options' ) )
        /**
         * Вкладка с общими настройками
         **/
        ->add_tab( 'Общие настройки', [
            Field::make( 'image', 'feodoraxis-logo', 'Логотип сайта' ),
            Field::make( 'text', 'feodoraxis-sn-vk', 'Ссылка на VK' )
                ->set_width(33),
            Field::make( 'text', 'feodoraxis-sn-telegram', 'Ссылка на Telegram' )
                ->set_width(33),
            Field::make( 'text', 'feodoraxis-sn-youtube', 'Ссылка на YouTube' )
                ->set_width(33),
            Field::make( 'text', 'feodoraxis-email', 'Email' )
                ->set_width(50),
            Field::make( 'text', 'feodoraxis-phone', 'Номер телефона' )
                ->set_width(50),
        ])

        /**
         * Если FAQ выводится на нескольких страницах
         * и текст везде одинаковый - лучше указать его в одном месте
         **/
        ->add_tab( 'Часто задаваемые вопросы', array(
            Field::make( 'complex', 'feodoraxis-faq-list', 'FAQ' )
                ->setup_labels([
                    'plural_name' => 'вопрос', //Как-то странно множественное число тут работает. Вообще не берется во внимание
                    'singular_name' => 'вопрос',
                ])
                ->set_collapsed( true ) // Чтобы при открытии в админке все вопросы были свёрнуты
                ->add_fields([
                    Field::make( 'text', 'feodoraxis-faq-item-question', 'Вопрос' ),
                    Field::make( 'textarea', 'feodoraxis-faq-item-answer', 'Ответ' ),
                ])
        ) );
}

Результат:

Таб с общими настройками
Таб с ЧАВО

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

$faq = carbon_get_theme_option( 'feodoraxis-faq-list' ); //Так получим все вопросы и ответы

$vk = carbon_get_theme_option( 'feodoraxis-sn-vk' ); //Получим ссылку на VK
$telegram = carbon_get_theme_option( 'feodoraxis-sn-telegram' ); //Получим ссылку на Telegram
$youtube = carbon_get_theme_option( 'feodoraxis-sn-youtube' ); //Получим ссылку на YouTube

Создание кастомных полей

Если вы читали статью полностью — этого вопроса уже быть не должно. Но если нет, то вот наглядный пример, как создать кастомные поля для товаров WooCommerce. Создадим поле feodoraxis-paper-type для товаров WooCommerce:

<?php
use Carbon_Fields\Container;
use Carbon_Fields\Field;

add_action( 'carbon_fields_register_fields', 'feodoraxis_products_fields' );
function feodoraxis_products_fields() {
    Container::make( 'post_meta', 'Дополнительные данные' )
        ->where( 'post_type', '=', 'product' ) // укажем условие, чтобы поля выводились только у товаров WooCommerce
        ->add_fields([
            Field::make( 'select', 'feodoraxis-paper-type', 'Тип бумаги' )
                ->set_options([
                    'gloss' => 'Глянец',
                    'mate' => 'Матовый'
                ])
        ])
}

Для других типов записей, в условии отображения контейнера (Container) просто указываем нужный слаг вместо «product».

Как получить кастомные поля

Выше мы создали поле feodoraxis-paper-type для товаров WooCommerce. Чтобы вывести значение для конкретного товара, нужна функция carbon_get_post_meta().

/**
 * Вместо $post->ID, можно использовать get_the_ID() или $product->get_id() (только для WooCommerce);
 * Или можно ручками указать ID записи, данные которой нужны
 **/

$feodoraxis_paper_type = carbon_get_post_meta( $post->ID, 'feodoraxis-paper-type' );
print_r( $feodoraxis_paper_type );

Если ранее мы указали, например, тип бумаги «матовый», то такой скрипт выведет нам следующий результат:

mate

Да, просто текстовое значение выбранного варианта. А в случае с изображением, если не укажем тип получаемых данных, получим массив, состоящий из:

  • Его ID в системе (что предпочтительно)
  • Прямую ссылку
  • Оригинальную запись о нем из БД.

Создание блоков Gutenberg

Есть мнение, что создавать блоки Gutenberg средствами PHP неправильно. Мол, нужно использовать JS.

Отчасти я согласен — для каждой задачи нужно использовать свой инструмент.

Однако, у создания при помощи JS я вижу ряд минусов:

  1. Нельзя править «на горячую». Это значит, что если мы увидим проблему в процессе работы, то чтобы исправить недочёт, после правок в коде, блок нужно заново добавить и наполнить. А если клиент использовал этот блок по всему сайту? Нам, получается, надо менять блоки по всему проекту?
  2. Сложно. Нужно потратить немало времени, чтобы создать такой блок. Для проектов с ограниченным бюджетом это проблема.

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

Но это всё лирика. Давайте рассмотрим создание блоков Gutenberg на практике!

Создание блоков Gutenberg в CF, на мой взгляд, даже проще, чем создание кастомных полей. Можно всё сделать в одном файле.

<?php
use Carbon_Fields\Field;
use Carbon_Fields\Block;

add_action( 'carbon_fields_register_fields', 'feodoraxis_about_block' );
function feodoraxis_about_block() {

    /**
     * CF не поддерживает кириллицу в названии блоков, поэтому пишем их на английском
     **/
    Block::make( 'About' )
        ->add_fields([
            Field::make( 'text', 'feodoraxis-about-title', 'Заголовок' ),
            Field::make( 'textarea', 'feodoraxis-about-text', 'Текст' )
                ->set_rows(2),
            Field::make( 'text', 'feodoraxis-about-link-text', "Текст ссылки"),
            Field::make( 'text', 'feodoraxis-about-link-uri', "Адрес ссылки"),
        ])
        ->set_render_callback( function ( $fields, $attributes, $inner_blocks ) {
            ?>
            <section class="about">
                <div class="wrapper">
                    <h2><?php echo $fields["feodoraxis-about-title"]; ?></h2>
                    <p><?php echo nl2br($fields['feodoraxis-about-text']); ?></p>
                    <p><a href="<?php echo $fields['feodoraxis-about-link-uri']; ?>"><?php echo $fields['about-link-text']; ?></a></p>
                </div>
            </section>
            <?php
        } );

Как видите, всё предельно просто. В методе add_fields(), как и у контейнеров, мы создаем нужные поля, которые будем заполнять в админке, а в методе set_render_callback() закидываем вёрстку и выводим в ней данные полей нашего блока.

В админке этот блок будет выглядеть так:

А в публичной части примерно так:

Конечно, многое при этом зависит от стилей 🙂

Заключение

Я постарался рассказать о Carbon Fields максимально подробно со всеми важными деталями. Конечно, в статье могут быть косяки, ошибки или недочёты. О них я прошу писать в комментариях в группе ВК. Да и вообще пишите, что думаете там. Так вы поможете моему продвижению 🙂