...

понедельник, 12 августа 2013 г.

[Из песочницы] Создание метабоксов в WordPress



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

Без метабокса не обойтись, когда новые свойства

* задействованы в большинстве постов;

* имеют жёсткие ограничения (напр., числа конкретного формата);

* трудно или неудобно вводить в виде строк (напр., значения из списка);

* взаимосвязаны друг с другом и являются одним целым.


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



Заготовка




Для экспериментаторских целей создадим простейший плагин, Состоять он будет из одного скрипта. Назовём его 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', и состоит из двух функций — вывода тела формы и сохранения данных.


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


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends: 'You Say What You Like, Because They Like What You Say' - http://www.medialens.org/index.php/alerts/alert-archive/alerts-2013/731-you-say-what-you-like-because-they-like-what-you-say.html


Комментариев нет:

Отправить комментарий