Создание метабоксов в WordPress

9ab3bde0f62559575ac6cc339728e110
Специфичные свойства поста, вносимые в структуру сайта вашим плагином, настраиваются с помощью метабоксов. Это — панели, содержащие все необходимые элементы настройки. Располагаются они на экранах редактирования.

Без метабокса не обойтись, когда новые свойства
* задействованы в большинстве постов;
* имеют жёсткие ограничения (напр., числа конкретного формата);
* трудно или неудобно вводить в виде строк (напр., значения из списка);
* взаимосвязаны друг с другом и являются одним целым.

Если же свойства могут отображаться в виде строки, затрагивают небольшое количество постов и не имеют жёстких ограничений по формату — для них можно воспользоваться метабоксом «Произвольные поля» на странице редактирования поста.

Заготовка

Для экспериментаторских целей создадим простейший плагин, Состоять он будет из одного скрипта. Назовём его metatest.php и поместим прямо в wp-content.

Вот минимум, необходимый для появления метабокса на странице редактирования записи после активации плагина:

<?php 
/* 
* Plugin Name: metatest 
*/ 

add_action('add_meta_boxes', 'metatest_init'); 

function metatest_init() { 
add_meta_box('metatest', 'MetaTest-параметры поста', 
'metatest_showup', 'post', 'side', 'default'); 
} 

function metatest_showup() { 
echo '<p>Содержимое метабокса расположено тут</p>'; 
} 

?>

 

В момент, когда следует создавать собственные метабоксы, активируется хук add_meta_boxes. Мы привязали к нему функцию metatest_init, создающую бокс ‘MetaTest-параметры поста’ на боковой панели экрана редактирования записи. Содержимое его формирует функция metatest_showup. Результат расположился между боксами «Миниатюра записи» и «Метки»:

1230402efa932ba8dee39d773422dff1

Всю работу здесь выполняет функция add_meta_box. Определение её выглядит так:

function add_meta_box( $id, $title, $callback, $screen = null, $context = 'advanced', $priority = 'default', $callback_args = null) ...

 

Внешний вид и содержимое задаётся тремя первыми аргументами: $id — идентификатор метабокса, $title — заголовок, $callback — функция, выдающая содержимое метабокса.

При формировании экрана идентификатор $id даётся секции, содержащей метабокс. Идентификатор галочки, задающей отображение бокса и расположенной в настройках экрана, формируется как «{$id}-hide».

6a5fd37f801add6bcd8299c33b5c1df8

Это может быть полезно при установке стилей или при использовании в скриптах.

Все остальные аргументы опциональны.

Следующие три из них определяют расположение: $screen — экран редактирования поста конкретного типа, $context — положение на этом экране, $priority — приоритет в отношении боксов, расположенных на том же экране и в том же контексте.

Значение аргумента $screen — это, по сути, тип постов, страница редактирования которых имеется в виду. Из стандартных, которыми WordPress владеет изначально, это ‘post’ (запись), ‘page’ (страница) и ‘attachment’ (медиафайлы и прочие вложения). Если значение не задано (или равно null’ю), метабокс будет присутствовать на всех экранах вне зависимости от типа редактируемого поста.

Положение, задаваемое в $context, может быть одним из следующих: ‘normal’ — основные элементы редактирования — верхняя часть центрального столбца; ‘advanced’ — дополнительные элементы — нижняя часть центрального столбца; ‘side’ — боковая панель, экран должен иметь два столбца. После ручного изменения WordPress запоминает положение метабокса и игнорирует заданные в add_meta_box умолчания. Поэтому, для ознакомления с размещением, можно добавить ещё три таких же бокса в различные контексты:

foreach (array('normal', 'advanced', 'side') as $context) 
add_meta_box('metatest_' . $context, 
'MetaTest ' . $context, 'metatest_meta_box_showup', 
'post', $context, 'default');

 

Аргумент $priority задаёт приоритет размещения панели по отношению к остальным. Чем выше приоритет, тем раньше отрисуется панель. Возможные значения приоритета, по убыванию: ‘high’, ‘core’, ‘default’, ‘low’.

В $callback_args передаётся произвольный параметр для функции $callback. Она принимает два аргумента: редактируемый пост (экземпляр класса WP_Post) и информацию о метабоксе. Информация содержится в массиве, ключи которого почти одноимённы с аргументами add_meta_box: ‘id’, ‘title’, ‘callback’ и ‘args’. Значение $callback_args помещено в ‘args’:

function showup_fn($post, $box) { 
$args = $box['args']; 
... 
}

 

Передать несколько аргументов можно в виде массива

array('var0' => $var0, 'var1' => $var1, ...)

 

Внутри функции выражение

extract($box['args']);

 

поместит переменные $varN в текущую область видимости.

Метаданные

Одна из основных задач метабокса — редактирование связанных с постом данных. Аналогичную же задачу выполняет и панель «Произвольные поля», но поля, имена которых начинаются с символа подчёркивания, ею игнорируются. Это следует помнить, чтобы уберечь данные от бесконтрольных изменений.

Формирование полей ввода и вывод имеющихся значений (или значений по умолчанию) возлагаются на функцию рисования метабокса. Информация извлекается из базы данных и помещается в поле ввода:

function metatest_showup($post, $box) { 

// получение существующих метаданных 
$data = get_post_meta($post->ID, '_metatest_data', true); 

// поле ввода 
echo '<p>Метаданные: <input type="text" name="metadata_field" value="' 
. esc_attr($data) . '"/></p>'; 
}

 

На данный момент нужных метаданных в базе не содержится, и поле ввода будет пустым. Заполнение его пока что ничего не даст. Сохранение не реализовано и WordPress на поле не реагирует.

Функция сохранения

Отредактированная информация приходит от пользователя по нажатию им кнопки «Сохранить» или «Опубликовать/Обновить». Получив её, WordPress активирует хук ‘save_post’. К нему следует привязать функцию, которая проверяет и пишет метаданные в БД:

add_action('save_post', 'metatest_save'); 

function metatest_save($postID) { 
... 
}

 

Первый (и, в данном случае, единственный) аргумент функции — идентификатор сохраняемого поста. Сам сохраняемый пост можно получить либо вызовом get_post($postID), либо вторым аргументом:

function metatest_save($postID, $post) ...

 

В этом случае количество принимаемых аргументов следует задать явно в четвёртом параметре add_action:

add_action('save_post', 'metatest_save', 10, 2);

 

В качестве предпоследнего параметра — приоритета выполнения нашей функции — взято его значение по умолчанию (из wp-includes/plugin.php).

Действие ‘save_post’ выполняется с двумя аргументами: идентификатором поста и самим постом в виде экземпляра класса WP_Post. Поэтому в большем количестве аргументов смысла нет.

Функция metatest_save немного сложнее, чем остальные в нашем плагине. План её выглядит так:
* проверяем наличие информации от нашего метабокса;
* проверяем принимающий её пост;
* убеждаемся в подлинности её источника;
* контролируем корректность данных и устраняем потенциально опасные последовательности;
* наконец, сохраняем чистую информацию в БД.

Проверки

Вся присланная пользователем информация находится в глобальном массиве $_POST. Но поля наших метаданных в нём по разным причинам может не быть: при автосохранении, сохранении поста другого типа, при создании пустых постов во время формирования экрана редактирования, и т.д. Тест на его наличие довольно прост:

if (!isset($_POST['metadata_field'])) 
return; 

Если поля нет — выходим.

С целевым постом связана пара существенных моментов.

Момент первый — ревизии. Каждая новая редакция поста вытесняет его предыдущее содержимое в ревизию — дочерний, неизменяемый пост. При этом активируется ещё один ‘save_post’, уже с идентификатором ревизии. Таким образом, функция metatest_save будет вызвана дважды, причём единственное отличие в вызовах — значение $postID.

Владельцем метаданных является пост. Поэтому сохранение ревизии должно игнорироваться:

if (wp_is_post_revision($postID)) 
return;

 

Функция wp_is_post_revision возвращает истину, если идентификатор, переданный в аргументе, принадлежит ревизии.

Второй момент — автосохранение. Происходит регулярно при редактировании поста, по умолчанию — каждые две минуты. Информация при этом присылается неполная — только та, что касается текста поста. Обычно в этом случае хватает проверки на наличие данных в $_POST. Но так как в будущих версиях (с 3.6) обещана принадлежность метаданных ревизиям, и так как поведение WordPress сильно зависит от плагинов, проверкой на автосохранение пренебрегать не стоит.

Автосохранение, создаваемое для поста — вид ревизии. В ответ на его идентификатор функция wp_is_post_revision также вернёт истину. Но тут есть подводный камень: при автосохранении черновиков хук ‘save_post’ активируется с идентификатором самого поста, и эта функция не сработает.

То же можно сказать и о специализированной wp_is_post_autosave: она сработает только при автосохранении уже опубликованного поста:

if (wp_is_post_autosave($postID)) { 
// если выполняется этот код, то $postID 
// является идентификатором автосохранения. 
}

 

Опираться в данном случае следует на константу DOING_AUTOSAVE, устанавливаемую в true функцией wp_ajax_autosave, вызываемую сервером при получении автосохраняемых данных. (Функция wp_ajax_autosave расположена в wp-admin/includes/ajax-actions.php). Соответствующий этому код может выглядеть, например, так:

if (exists('DOING_AUTOSAVE') && DOING_AUTOSAVE) 
return;

 

Происходящее автосохранение также можно определить по значению $_POST[‘action’] == ‘autosave’.

Проверка подлинности источника основана на одноразовом коде, встраиваемом в форму к нашим полям ввода. Он помогает обезопасить сайт от атак вида CSRF. При таких атаках браузер авторизованного пользователя, выполняя код злоумышленника, производит действия с полномочиями этого пользователя. В данном случае — устанавливает собственные значения наших метаданных.

Установку надо поместить в код формирования тела метабокса:

wp_nonce_field("metatest_action", "metatest_nonce"); 

а проверять результат следует в функции сохранения до записи в БД: 

check_admin_referer("metatest_action", "metatest_nonce");

 

Контроль корректности

Теперь, когда мы работаем с целевым постом, имеем необходимые поля и уверены в достоверности их источника, можно приступать к их обработке.

Прежде всего следует проконтролировать корректность сохраняемых метаданных. Два условия должны выполняться:
1) данные приемлемы;
2) данные безопасны;

Критерий приемлемости полностью зависит от решаемой задачи — допустимые значения для перечислений, отсутствие в тексте нецензурной лексики и т.д.

О безопасности метаданных в отношении БД позаботится функция update_post_meta.

В отношении же сайта и пользователей безопасность данных определяется методами их применения. Не должно быть бесконтрольного HTML-кода, интерпретируемых произвольных JavaScript’ов, лазеек для злонамеренных URL, и т.д.

В нашем случае метаданные — строка. Поэтому имеет смысл избавить её от лишних пробелов, переносов и некорректных символов юникода. Для этих целей служит функция sanitize_text_field, возвращающая скорректированную версию строки-аргумента:

$string = sanitize_text_field($string);

 

Сохранение

Запись данных — цель функции — производится одним вызовом:

update_post_meta($postID, '_metatest_data', $string);

 

Обратите внимание на символ подчёркивания, с которого начинается имя поля. Он делает поле недоступным из панели «произвольные поля».

Итог

Теперь применим всю изложенную теорию на практике. Заголовок плагина выглядит по-прежнему, добавлена только регистрация функции сохранения:

<?php 
/* 
* Plugin Name: metatest 
*/ 

// привязываем функции сотворения метабокса и 
// сохранения данных к соответствующим хукам: 
add_action('add_meta_boxes', 'metatest_init'); 
add_action('save_post', 'metatest_save'); 

function metatest_init() { 
add_meta_box('metatest', 'MetaTest-параметр поста', 
'metatest_showup', 'post', 'side', 'default'); 
}

 

Функция рисования метабокса пополнилась формированием одноразового кода и выглядит теперь так:

function metatest_showup($post, $box) { 

// получение существующих метаданных 
$data = get_post_meta($post->ID, '_metatest_data', true); 

// скрытое поле с одноразовым кодом 
wp_nonce_field('metatest_action', 'metatest_nonce'); 

// поле с метаданными 
echo '<p>Метаданные: <input type="text" name="metadata_field" value="' 
. esc_attr($data) . '"/></p>'; 
}

 

А вот функция сохранения данных:

function metatest_save($postID) { 

// пришло ли поле наших данных? 
if (!isset($_POST['metadata_field'])) 
return; 

// не происходит ли автосохранение? 
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) 
return; 

// не ревизию ли сохраняем? 
if (wp_is_post_revision($postID)) 
return; 

// проверка достоверности запроса 
check_admin_referer('metatest_action', 'metatest_nonce'); 

// коррекция данных 
$data = sanitize_text_field($_POST['metadata_field']); 

// запись 
update_post_meta($postID, '_metatest_data', $data); 

} 
?>

 

Результат, после сохранения строки «Hello, world!» и обновления страницы, должен выглядеть так:

aa25a74916237c2e86267cf6b6ad11d2

Как видим, простейший метабокс создать несложно. Он опирается на два хука — ‘add_meta_box’ и ‘save_post’, и состоит из двух функций — вывода тела формы и сохранения данных.

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