Как валидировать произвольные поля в пользовательском типе записи?
Я написал плагин, который создает пользовательский тип записи с произвольными полями. Как проверить данные, чтобы пользователи не могли вводить некорректную информацию?
Я предполагал, что функция хука save_post будет обрабатывать валидацию полей, но не могу найти простого способа отображения ошибок пользователю.
Есть ли встроенная функция WordPress для этого? Какой общий подход используется для валидации произвольных полей?

Вы на правильном пути. Я проверяю поля в колбэке save_post
, а затем использую админ-уведомления для отображения ошибок пользователю, если поле не прошло валидацию. Они появляются в выделенной области в верхней части страницы, так же, как любые ошибки/сообщения, генерируемые самим WordPress.
Вот простой пример создания админ-уведомления:
function my_admin_notice()
{
?>
<div class="updated">
<p>Aenean eros ante, porta commodo lacinia.</p>
</div>
<?php
}
add_action( 'admin_notices', 'my_admin_notice' );
Однако это не очень практично. В такой ситуации вам нужна функция, которой можно просто передать сообщение. Например:
if( $pizza != 'warm' )
$notices->enqueue( 'Пицца не горячая', 'error' );
Таким образом, вы можете написать функцию enqueue()
самостоятельно (вместе с функцией для вывода уведомлений) или использовать библиотеку, такую как IDAdminNotices.
Вот пример из плагина, который я написал. Здесь используются функции добавления и вывода уведомлений, встроенные в сам класс, а не подключение внешней библиотеки.
public function saveCustomFields( $postID )
{
// ...
if( filter_var( $_POST[ self::PREFIX . 'zIndex'], FILTER_VALIDATE_INT ) === FALSE )
{
update_post_meta( $post->ID, self::PREFIX . 'zIndex', 0 );
$this->enqueueMessage( 'Порядок наложения должен быть целым числом.', 'error' );
}
else
update_post_meta( $post->ID, self::PREFIX . 'zIndex', $_POST[ self::PREFIX . 'zIndex'] );
// ...
}
add_action( 'save_post', array( $this, 'saveCustomFields' );

Мне не совсем понятно... требует ли ваш пример кода стороннего кода или он будет работать в WordPress как есть?

Это, кажется, работает только при первой загрузке страницы. Если нажать кнопку "Обновить/Опубликовать", уведомления не отображаются.

Пример кода: http://pastebin.com/vTxv9cw1

Думаю, проблема в том, что ты используешь глобальную переменную вместо сохранения уведомлений в базе данных. Данные в переменных не сохраняются между запросами. То есть, уведомление добавляется в $bh_errorMessages при запросе сохранения, затем WordPress перенаправляет обратно на экран редактирования записи (это новый запрос), и все переменные сбрасываются. Смотри http://en.wikipedia.org/wiki/Post/Redirect/Get. Внимательно изучи IDAdminNotices и убедись, что делаешь всё так же, как там.

Я не совсем понимаю, что именно вы используете для сохранения переменных между запросами. Это включение переменной $instance
? Или один из вызовов add_action
? Или оба варианта? Или что-то ещё?

Хорошо, кажется, я разобрался. Вся магия в функциях WordPress add_option
и get_option
, которые сохраняют и извлекают переменную, а хук shutdown
полезен для вызова функции add_option
только один раз за сессию, после того как всё остальное уже выполнено.

Я написал небольшой плагин, который не только проверяет поля ввода для пользовательских типов записей, но и удаляет стандартное уведомление в админке без использования JavaScript.
Вот часть кода:
// Фильтры валидации
$title = $album->post_title;
if ( ! $title ) {
$errors['title'] = "Заголовок обязателен";
}
// Если есть ошибки, настроим сообщения
if (! empty($errors)) {
// Удаляем этот хук, чтобы избежать бесконечного цикла
remove_action('save_post', 'album_save_post');
// Сохраняем ошибки в настройках
update_option('album_errors', $errors);
// Меняем статус записи с "Опубликовано" на "Черновик"
$album->post_status = 'draft';
// Обновляем запись
wp_update_post( $album );
// Возвращаем хук обратно
add_action('save_post', 'album_save_post');
// admin_notice создается через $_GET['message'] с номером, который WordPress использует
// для отображения сообщения. Добавляем фильтр для замены стандартного сообщения с редиректом
add_filter( 'redirect_post_location', 'album_post_redirect_filter' );
}
Полный урок можно посмотреть здесь

Когда срабатывает save_post
, запись уже сохранена в базе данных.
Если вы используете ACF, в нем есть встроенная валидация.
Однако, если вам нужно проверять данные вне ACF, такие как post_title, все становится немного сложнее.
Изучая исходный код WordPress, в частности функцию update_post()
в файле wp-includes/post.php
, нет встроенного способа перехватить запрос до его сохранения в базе данных.
Тем не менее, мы можем использовать хук pre_post_update
и функции header()
и get_post_edit_link()
для предотвращения сохранения записи.
<?php
/**
* Выполняет пользовательскую валидацию для произвольного типа записи "Site"
*/
function custom_post_site_save($post_id, $post_data) {
# Если это просто ревизия, ничего не делаем.
if (wp_is_post_revision($post_id))
return;
if ($post_data['post_type'] == 'site') {
# В этом примере мы вызовем ошибку для заголовков записей короче 5 символов
if (strlen($post_data['post_title']) < 5) {
# Добавляем уведомление
update_option('my_notifications', json_encode(array('error', 'Заголовок записи не может быть короче 5 символов.')));
# И перенаправляем
header('Location: '.get_edit_post_link($post_id, 'redirect'));
exit;
}
}
}
add_action( 'pre_post_update', 'custom_post_site_save', 10, 2);
/**
* Показывает пользовательские уведомления в админ-панели WordPress
*/
function my_notification() {
$notifications = get_option('my_notifications');
if (!empty($notifications)) {
$notifications = json_decode($notifications);
#notifications[0] = (string) Тип уведомления: error, updated или update-nag
#notifications[1] = (string) Сообщение
#notifications[2] = (boolean) Можно закрыть?
switch ($notifications[0]) {
case 'error': # красный
case 'updated': # зеленый
case 'update-nag': # ?
$class = $notifications[0];
break;
default:
# По умолчанию error на всякий случай
$class = 'error';
break;
}
$is_dismissable = '';
if (isset($notifications[2]) && $notifications[2] == true)
$is_dismissable = 'is_dismissable';
echo '<div class="'.$class.' notice '.$is_dismissable.'">';
echo '<p>'.$notifications[1].'</p>';
echo '</div>';
# Сбрасываем уведомление
update_option('my_notifications', false);
}
}
add_action( 'admin_notices', 'my_notification' );

Я хочу дополнить отличный ответ @Lucas Bustamante тем, что значение произвольных полей можно получить через глобальную переменную $_POST
.
Таким образом, ответ @Lucas Bustamante также применим, если валидация относится к произвольному полю. Например:
<?php
/**
* Выполняет пользовательскую валидацию для произвольного типа записи "Site"
*/
function custom_post_site_save($post_id, $post_data) {
# Если это просто ревизия, ничего не делаем
if (wp_is_post_revision($post_id))
return;
if ($post_data['post_type'] == 'site') {
# В этом примере мы вызовем ошибку для почтовых индексов короче 5 символов
if (strlen($_POST['zip_code']) < 5) {
# Добавляем уведомление
update_option('my_notifications', json_encode(array('error', 'Почтовый индекс не может быть короче 5 символов.')));
# И перенаправляем
header('Location: '.get_edit_post_link($post_id, 'redirect'));
exit;
}
}
}
add_action( 'pre_post_update', 'custom_post_site_save', 10, 2);
/**
* Показывает пользовательские уведомления в админке WordPress
*/
function my_notification() {
$notifications = get_option('my_notifications');
if (!empty($notifications)) {
$notifications = json_decode($notifications);
#notifications[0] = (string) Тип уведомления: error, updated или update-nag
#notifications[1] = (string) Сообщение
#notifications[2] = (boolean) Можно закрыть?
switch ($notifications[0]) {
case 'error': # красный
case 'updated': # зеленый
case 'update-nag': # ?
$class = $notifications[0];
break;
default:
# По умолчанию error на всякий случай
$class = 'error';
break;
}
$is_dismissable = '';
if (isset($notifications[2]) && $notifications[2] == true)
$is_dismissable = 'is_dismissable';
echo '<div class="'.$class.' notice '.$is_dismissable.'">';
echo '<p>'.$notifications[1].'</p>';
echo '</div>';
# Сбрасываем уведомление
update_option('my_notifications', false);
}
}
add_action( 'admin_notices', 'my_notification' );

Вы можете использовать фильтр "wp_insert_post_data" для изменения или проверки данных перед их вставкой в базу данных
add_filter( 'wp_insert_post_data', 'validate_posttypes' );
function wpb_custom_excerpt($data) {
// Если заголовок поста начинается с 'wp', добавляем префикс 'wp'
str_starts_with($data['post_title'],'wp')?$data['post_title']='wp'.$data['post_title']:null;
return $data;
}
