Создание метабоксов в 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’, и состоит из двух функций — вывода тела формы и сохранения данных.

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

ссылка на оригинал статьи http://habrahabr.ru/post/189794/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *