Специфичные свойства поста, вносимые в структуру сайта вашим плагином, настраиваются с помощью метабоксов. Это — панели, содержащие все необходимые элементы настройки. Располагаются они на экранах редактирования.
Без метабокса не обойтись, когда новые свойства
* задействованы в большинстве постов;
* имеют жёсткие ограничения (напр., числа конкретного формата);
* трудно или неудобно вводить в виде строк (напр., значения из списка);
* взаимосвязаны друг с другом и являются одним целым.
Если же свойства могут отображаться в виде строки, затрагивают небольшое количество постов и не имеют жёстких ограничений по формату — для них можно воспользоваться метабоксом «Произвольные поля» на странице редактирования поста.
Заготовка
Для экспериментаторских целей создадим простейший плагин, Состоять он будет из одного скрипта. Назовём его 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. Результат расположился между боксами «Миниатюра записи» и «Метки»:
Всю работу здесь выполняет функция 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".
Это может быть полезно при установке стилей или при использовании в скриптах.
Все остальные аргументы опциональны.
Следующие три из них определяют расположение: $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!» и обновления страницы, должен выглядеть так:
Как видим, простейший метабокс создать несложно. Он опирается на два хука — 'add_meta_box' и 'save_post', и состоит из двух функций — вывода тела формы и сохранения данных.
Но это лишь отправная точка. В реальных случаях полей больше, формы сложнее и данные связаны как между собой, так и с функционалом плагина и компонентами темы. Поэтому ни сложность решаемых задач, ни начинка метабоксов, не ограничены.
Автор: asmin7
Здравствуйте! Использование функции esc_attr при вставке значения ИЗ базы данных в метабокс, по-моему, не имеет смысла. А вот перед вставкой – надо бы.
esc_attr($data)