Передача сообщений об ошибках/предупреждениях из метабокса в "admin_notices"
У меня есть простой метабокс, который обновляет пользовательские поля записи (с помощью update_post_meta()
).
Как я могу отправить сообщение об ошибке или предупреждение на следующую страницу, если пользователь публикует/обновляет запись и не заполняет одно из полей метабокса (или заполняет их недопустимыми данными)?

Вы можете использовать хук admin_notices
Сначала определите функцию уведомления:
function my_admin_notice(){
//выводим сообщение
echo '<div id="message">
<p>сообщение об ошибках при сохранении метабокса!!!</p>
</div>';
//убедитесь, что удаляете уведомление после его отображения, чтобы оно показывалось только когда нужно
remove_action('admin_notices', 'my_admin_notice');
}
Затем в функции сохранения метабокса, при необходимости, добавьте:
...
...
if($errors){
add_action('admin_notices', 'my_admin_notice');
}
...
...
Обновление
Как и обещал, вот пример того, как я добавляю сообщение об ошибке из моего метабокса
<?php
/*
Plugin Name: one-trick-pony-notice
Plugin URI: http://en.bainternet.info
Description: Просто чтобы показать пример использования admin notice из метабокса
Version: 1.0
Author: Bainternet
Author URI: http://en.bainternet.info
*/
/* admin notice */
function my_admin_notice(){
//выводим сообщение
global $post;
$notice = get_option('otp_notice');
if (empty($notice)) return '';
foreach($notice as $pid => $m){
if ($post->ID == $pid ){
echo '<div id="message" class="error"><p>'.$m.'</p></div>';
//убедимся, что удаляем уведомление после отображения, чтобы оно показывалось только когда нужно
unset($notice[$pid]);
update_option('otp_notice',$notice);
break;
}
}
}
//хуки
add_action('add_meta_boxes', 'OT_mt_add');
add_action('save_post', 'OT_mt_save');
add_action('admin_notices', 'my_admin_notice',0);
//добавляем метабокс
function OT_mt_add() {
add_meta_box('OT_mt_sectionid', __( 'Метабокс с уведомлением One Trick', 'textdomain' ),'OT_mt_display','post');
}
//отображаем метабокс
function OT_mt_display() {
// Используем nonce для проверки
wp_nonce_field( plugin_basename(__FILE__), 'myplugin_noncename' );
// Поля для ввода данных
echo '<label for="myplugin_new_field">';
_e("оставьте пустым, чтобы получить уведомление при публикации или обновлении", 'textdomain' );
echo '</label> ';
echo '<input type="text" id="ot_field" name="ot_field" value="" size="25" />';
}
//сохраняем метабокс, здесь мы проверяем поля и если они пустые, выводим сообщение
function OT_mt_save( $post_id ) {
// проверяем, что запрос пришел с нашего экрана и с правильной авторизацией,
// потому что save_post может срабатывать в других случаях
if (!isset($_POST['myplugin_noncename'])) return $post_id;
if ( !wp_verify_nonce( $_POST['myplugin_noncename'], plugin_basename(__FILE__) ) )
return $post_id;
// проверяем, не автосохранение ли это
// Если это автосохранение, наша форма не отправлялась, поэтому ничего не делаем
if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE )
return $post_id;
if(!isset($_POST['ot_field']) || empty($_POST['ot_field'])){
//поле оставлено пустым, добавляем уведомление
$notice = get_option('otp_notice');
$notice[$post_id] = "Вы оставили поле пустым";
update_option('otp_notice',$notice);
}
}
Теперь, когда я искал этот код, я нашел свой старый способ сделать это с помощью хука фильтра post_updated_messages
примерно таким же образом, так что я тоже добавлю его:
<?php
/*
Plugin Name: one-trick-pony-notice2
Plugin URI: http://en.bainternet.info
Description: Так же, как и выше, но на этот раз используя хук post_updated_messages
Version: 1.0
Author: Bainternet
Author URI: http://en.bainternet.info
*/
//хуки
add_filter('post_updated_messages','my_messages',0);
add_action('add_meta_boxes', 'OT_mt_add');
add_action('save_post', 'OT_mt_save');
//добавляем метабокс
function OT_mt_add() {
add_meta_box('OT_mt_sectionid', __( 'Метабокс с уведомлением One Trick', 'textdomain' ),'OT_mt_display','post');
}
//отображаем метабокс
function OT_mt_display() {
// Используем nonce для проверки
wp_nonce_field( plugin_basename(__FILE__), 'myplugin_noncename' );
// Поля для ввода данных
echo '<label for="myplugin_new_field">';
_e("оставьте пустым, чтобы получить уведомление при публикации или обновлении", 'textdomain' );
echo '</label> ';
echo '<input type="text" id="ot_field" name="ot_field" value="" size="25" />';
}
//сохраняем метабокс, здесь проверяем поля и если они пустые, выводим сообщение
function OT_mt_save( $post_id ) {
// проверяем, что запрос пришел с нашего экрана и с правильной авторизацией,
// потому что save_post может срабатывать в других случаях
if (!isset($_POST['myplugin_noncename'])) return $post_id;
if ( !wp_verify_nonce( $_POST['myplugin_noncename'], plugin_basename(__FILE__) ) )
return $post_id;
// проверяем, не автосохранение ли это
// Если это автосохранение, наша форма не отправлялась, поэтому ничего не делаем
if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE )
return $post_id;
if(!isset($_POST['ot_field']) || empty($_POST['ot_field'])){
//поле оставлено пустым, добавляем уведомление
$notice = get_option('otp_notice');
$notice[$post_id] = "Вы оставили поле пустым";
update_option('otp_notice',$notice);
}
}
//фильтр сообщений
function my_messages($m){
global $post;
$notice = get_option('otp_notice');
if (empty($notice)) return $m;
foreach($notice as $pid => $mm){
if ($post->ID == $pid ){
foreach ($m['post'] as $i => $message){
$m['post'][$i] = $message.'<p>'.$mm.'</p>';
}
unset($notice[$pid]);
update_option('otp_notice',$notice);
break;
}
}
return $m;
}

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

Куда происходит переадресация? И код выше — это то, что я использую, так что я знаю, что он работает.

ваша функция сохранения метабокса подключена к хуку save_post
?

потому что это не работает :) После нажатия кнопки "Опубликовать" пост сохраняется, а затем вас перенаправляют обратно на страницу редактирования.

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

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

спасибо, но это делает то же самое, что и указал Rarst: сообщение об ошибке сохраняется в базе данных, а затем извлекается и удаляется на следующей странице.

@One Trick Pony: да, оно сохраняется в базе данных, но это всего лишь одна запись, и код выше автоматически удаляет её, так что не нужно беспокоиться о заполнении базы данных сообщениями о переходах.

-1 за использование базы данных. Вы не можете гарантировать, что правильный пользователь увидит ошибку. Кроме того, это лишняя нагрузка. Хотя это неплохой обходной путь для отсутствия чёткого способа обработки ошибок метабоксов, он всё же неэффективен. Я добавил пример своего подхода в новом ответе, чтобы помочь другим.

Этот ответ [зеркало] от Otto на WP Tavern действительно решает проблему с транзиентами, делая то же самое, что и WordPress для преодоления проблемы с редиректом. У меня сработало на 100%.
Проблема в том, что транзиенты доступны всем. Если у вас несколько пользователей выполняют действия одновременно, сообщение об ошибке может попасть не к тому человеку. Это состояние гонки.
WordPress решает это, передавая параметр сообщения в URL. Номер сообщения указывает, какое именно сообщение нужно отобразить.
Вы можете сделать то же самое, подключив фильтр
redirect_post_location
и используяadd_query_arg
для добавления своего параметра к запросу. Например:add_filter('redirect_post_location','my_message'); function my_message($loc) { return add_query_arg( 'my_message', 123, $loc ); }
Это добавляет
my_message=123
к запросу. Затем после редиректа вы можете обнаружить параметр my_message в$_GET
и отобразить соответствующее сообщение.

Вы можете сделать это вручную, но в WordPress это реализовано нативно следующим образом для ошибок настроек:
add_settings_error()
для создания сообщения.- Затем
set_transient('settings_errors', get_settings_errors(), 30);
settings_errors()
в хукеadmin_notices
для отображения (потребуется подключить хук для экранов, не связанных с настройками).

это делает то, что мне нужно, но не заполнит ли это базу данных кучей временных данных?

@One Trick Pony в нативном процессе временные данные явно удаляются (см. исходный код get_settings_errors()
). Возможно, вам нужно будет делать это вручную, если адаптируете логику для не-страницы настроек.

все равно мне не нравится идея хранить временные сообщения об ошибках в базе данных. Я буду использовать ajax для предупреждения пользователя при изменении ввода

Я знаю, что вопрос старый, но ответы здесь не решают проблему.
Развивая ответ от Ana Ban и используя метод Otto, я нашел этот способ самым лучшим для обработки ошибок. Он не требует хранения ошибок в базе данных.
Я привожу упрощенную версию объекта Metabox, который я использую. Это позволяет легко добавлять новые сообщения об ошибках и гарантировать, что правильный пользователь увидит сообщение об ошибке (при использовании базы данных такой гарантии нет).
<?php
/**
* Класс MetaboxExample
*/
class MetaboxExample {
/**
* Определяет белый список разрешенных экранов (post_types)
*/
private $_allowedScreens = array( 'SCREENS_TO_ALLOW_METABOX' );
/**
* GET параметр для кода ошибки в блоке ошибок
*/
const GET_METABOX_ERROR_PARAM = 'meta-error';
/**
* Определяет хуки для админки
*/
public function __construct() {
add_action('add_meta_boxes', array($this, 'addMetabox'), 50);
add_action('save_post', array($this, 'saveMetabox'), 50);
add_action('edit_form_top', array($this, 'adminNotices')); // ПРИМЕЧАНИЕ: admin_notices не позиционирует это правильно на страницах произвольных типов записей, не тестировал это на POST или PAGE, но не вижу в этом проблемы
}
/**
* Добавляет метабокс к указанным типам записей
*/
public function addMetabox() {
foreach ( $this->_allowedScreens as $screen ) {
add_meta_box(
'PLUGIN_METABOX',
__( 'ЗАГОЛОВОК', 'text_domain' ),
array($this, 'metaBox'),
$screen,
'side',
'high'
);
}
}
/**
* Выводит содержимое метабокса
* @param $post
*/
public function metaBox($post) {
// Добавляем nonce поле для последующей проверки
wp_nonce_field( 'metaboxnonce', 'metaboxnonce' );
// Загружаем метаданные для этого метабокса
$someValue = get_post_meta( $post->ID, 'META_KEY_IDENTIFIER', true );
?>
<p>
<label for="some-value" style="width: 120px; display: inline-block;">
<?php _e( 'Какое-то поле:', 'text_domain' ); ?>
</label>
<input type="text" id="some-value" name="some_value" value="<?php esc_attr_e( $someValue ); ?>" size="25" />
</p>
<?php
}
/**
* Метод сохранения метабокса
* @param $post_id
*/
public function saveMetabox($post_id) {
global $wpdb;
// Проверяем, установлено ли наше nonce поле
if ( ! isset( $_POST['metaboxnonce'] ) ) {
return $post_id;
}
// Проверяем, что nonce действителен
if ( ! wp_verify_nonce( $_POST['metaboxnonce'], 'metaboxnonce' ) ) {
return $post_id;
}
// Если это автосохранение, наша форма не была отправлена, поэтому ничего не делаем
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $post_id;
}
// Проверяем права пользователя
if ( isset( $_POST['post_type'] ) && 'page' == $_POST['post_type'] ) {
if ( ! current_user_can( 'edit_page', $post_id ) ) {
return $post_id;
}
} else {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
}
// Убеждаемся, что значение установлено
if ( !isset( $_POST['some_value'] ) ) {
return $post_id;
}
// Санитизируем пользовательский ввод
$someValue = sanitize_text_field( $_POST['some_value'] );
// Проверяем, что значение не пустое
if (empty($someValue)) {
// Добавляем наш код ошибки
add_filter('redirect_post_location', function($loc) {
return add_query_arg( self::GET_METABOX_ERROR_PARAM, 1, $loc );
});
return $post_id; // обязательно возвращаем, чтобы не допустить дальнейшей обработки
}
// Обновляем метаполе в базе данных
update_post_meta( $post_id, 'META_KEY_IDENTIFIER', $someValue );
}
/**
* Уведомления админки для метабокса
*/
public function adminNotices() {
if (isset($_GET[self::GET_METABOX_ERROR_PARAM])) {
$screen = get_current_screen();
// Убеждаемся, что мы на правильном типе записи
if (in_array($screen->post_type, $this->_allowedScreens)) {
$errorCode = (int) $_GET[self::GET_METABOX_ERROR_PARAM];
switch($errorCode) {
case 1:
$this->_showAdminNotice( __('Произошла какая-то ошибка', 'text_domain') );
break;
// Здесь можно добавить больше кодов ошибок для их вывода
}
}
}
}
/**
* Показывает уведомление админки для метабокса
* @param $message
* @param string $type
*/
private function _showAdminNotice($message, $type='error') {
?>
<div class="<?php esc_attr_e($type); ?> below-h2">
<p><?php echo $message; ?></p>
</div>
<?php
}
}

Единственная проблема с этим ответом в том, что он не работает с PHP 5.2. Я не говорю, что мы все должны поддерживать PHP 5.2, но пока WordPress не установит PHP 5.2 в качестве минимального требования, мы должны поддерживать его, если распространяем плагин :(

Если вы уберете анонимную функцию и сделаете её публичным методом, всё должно работать нормально. Я понимаю вашу проблему, но лично я не буду разрабатывать для устаревшей версии PHP (http://php.net/eol.php). Поддержка PHP 5.2 прекращена 6 января 2011 года. WordPress должен приложить больше усилий, чтобы не поддерживать устаревшие версии, но это уже другая история, как и множество плохих хостинг-компаний, которые до сих пор предоставляют устаревшие версии...
