Cómo pasar mensajes de error/advertencia desde un meta box a "admin_notices"
Tengo un meta box simple que actualiza los campos personalizados del post (usando update_post_meta()
).
¿Cómo puedo enviar un mensaje de error o advertencia a la siguiente página cuando el usuario publica/actualiza el post y no completa uno de los campos del meta box (o los completa con datos inválidos)?

puedes usar el hook admin_notices
primero define la función del aviso:
function my_admin_notice(){
//imprimir el mensaje
echo '<div id="message">
<p>metabox con errores en el mensaje de guardado aquí!!!</p>
</div>';
//asegúrate de eliminar el aviso después de mostrarlo para que solo aparezca cuando sea necesario
remove_action('admin_notices', 'my_admin_notice');
}
Luego en tu función de guardado del metabox, basado en si es necesario, agrega:
...
...
if($errors){
add_action('admin_notices', 'my_admin_notice');
}
...
...
Actualización
Como lo prometí, aquí hay un ejemplo de cómo agrego un mensaje de error desde mi metabox
<?php
/*
Plugin Name: one-trick-pony-notice
Plugin URI: http://en.bainternet.info
Description: Solo para demostrar un punto usando aviso de admin desde metabox
Version: 1.0
Author: Bainternet
Author URI: http://en.bainternet.info
*/
/* aviso de administración */
function my_admin_notice(){
//imprimir el mensaje
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>';
//asegúrate de eliminar el aviso después de mostrarlo para que solo aparezca cuando sea necesario
unset($notice[$pid]);
update_option('otp_notice',$notice);
break;
}
}
}
//hooks
add_action('add_meta_boxes', 'OT_mt_add');
add_action('save_post', 'OT_mt_save');
add_action('admin_notices', 'my_admin_notice',0);
//agregar metabox
function OT_mt_add() {
add_meta_box('OT_mt_sectionid', __( 'One Trick Meta Box notice', 'textdomain' ),'OT_mt_display','post');
}
//mostrar metabox
function OT_mt_display() {
// Usar nonce para verificación
wp_nonce_field( plugin_basename(__FILE__), 'myplugin_noncename' );
// Los campos reales para entrada de datos
echo '<label for="myplugin_new_field">';
_e("dejar en blanco para obtener un aviso al publicar o actualizar", 'textdomain' );
echo '</label> ';
echo '<input type="text" id="ot_field" name="ot_field" value="" size="25" />';
}
//guardar metabox aquí es donde verifico los campos y si están vacíos muestro un mensaje
function OT_mt_save( $post_id ) {
// verificar que esto venga de nuestra pantalla y con la autorización adecuada,
// porque save_post puede ser activado en otros momentos
if (!isset($_POST['myplugin_noncename'])) return $post_id;
if ( !wp_verify_nonce( $_POST['myplugin_noncename'], plugin_basename(__FILE__) ) )
return $post_id;
// verificar si esta es una rutina de guardado automático
// Si lo es, nuestro formulario no ha sido enviado, así que no queremos hacer nada
if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE )
return $post_id;
if(!isset($_POST['ot_field']) || empty($_POST['ot_field'])){
//campo dejado vacío así que agregamos un aviso
$notice = get_option('otp_notice');
$notice[$post_id] = "Has dejado el campo vacío";
update_option('otp_notice',$notice);
}
}
Ahora, al buscar este código encontré mi antigua forma de hacerlo usando el hook de filtro post_updated_messages
de manera similar, así que también lo agregaré:
<?php
/*
Plugin Name: one-trick-pony-notice2
Plugin URI: http://en.bainternet.info
Description: igual que el anterior pero esta vez usando el hook post_updated_messages
Version: 1.0
Author: Bainternet
Author URI: http://en.bainternet.info
*/
//hooks
add_filter('post_updated_messages','my_messages',0);
add_action('add_meta_boxes', 'OT_mt_add');
add_action('save_post', 'OT_mt_save');
//agregar metabox
function OT_mt_add() {
add_meta_box('OT_mt_sectionid', __( 'One Trick Meta Box notice', 'textdomain' ),'OT_mt_display','post');
}
//mostrar metabox
function OT_mt_display() {
// Usar nonce para verificación
wp_nonce_field( plugin_basename(__FILE__), 'myplugin_noncename' );
// Los campos reales para entrada de datos
echo '<label for="myplugin_new_field">';
_e("dejar en blanco para obtener un aviso al publicar o actualizar", 'textdomain' );
echo '</label> ';
echo '<input type="text" id="ot_field" name="ot_field" value="" size="25" />';
}
//guardar metabox aquí es donde verifico los campos y si están vacíos muestro un mensaje
function OT_mt_save( $post_id ) {
// verificar que esto venga de nuestra pantalla y con la autorización adecuada,
// porque save_post puede ser activado en otros momentos
if (!isset($_POST['myplugin_noncename'])) return $post_id;
if ( !wp_verify_nonce( $_POST['myplugin_noncename'], plugin_basename(__FILE__) ) )
return $post_id;
// verificar si esta es una rutina de guardado automático
// Si lo es, nuestro formulario no ha sido enviado, así que no queremos hacer nada
if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE )
return $post_id;
if(!isset($_POST['ot_field']) || empty($_POST['ot_field'])){
//campo dejado vacío así que agregamos un aviso
$notice = get_option('otp_notice');
$notice[$post_id] = "Has dejado el campo vacío";
update_option('otp_notice',$notice);
}
}
//filtro de mensajes
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;
}

no funciona realmente porque después de guardar la entrada, te redirige así que esa acción nunca se ejecuta...

¿Redirigido a dónde? Y el código de arriba es lo que uso así que sé que funciona.

porque no funciona :) Después de hacer clic en el botón de publicar, el post se guarda y luego te redirige de vuelta a la página de edición.

Me hiciste preocupar por un momento, el aviso se muestra en la pantalla de edición y funciona perfectamente.

¿puedes publicar el código que estás usando, el metabox y todo eso? Porque probé tu código anterior y no veo ningún aviso proveniente de mi meta box...

gracias, pero esto hace lo mismo que señaló Rarst: el mensaje de error se guarda en la base de datos, y luego se recupera y se elimina en la siguiente página.

@One Trick Pony: sí, se guarda en la base de datos, pero es una opción agradable (solo una fila en la base de datos) y se elimina automáticamente cuando usas el código anterior, así que no hay que preocuparse por que la base de datos se llene con transiciones.

-1 por usar una base de datos. No puedes garantizar que el usuario correcto verá el error. Además, no vale la pena la sobrecarga innecesaria. Por no tener una forma clara de manejar errores en metaboxes, este es un buen parche, pero aún no es eficiente. Agregué un ejemplo de la forma en que hago esto en una nueva respuesta para ayudar a otros.

Esta respuesta [espejo] de Otto en WP Tavern, realmente resuelve el problema de los transients haciendo lo que WordPress mismo hace para superar el problema de redirección. Funcionó perfectamente para mí.
El problema es que los transients están disponibles para todos. Si tienes más de un usuario haciendo cosas al mismo tiempo, el mensaje de error puede llegar a la persona equivocada. Es una condición de carrera.
WordPress en realidad maneja esto pasando un parámetro de mensaje en la URL. El número del mensaje indica qué mensaje mostrar.
Puedes hacer lo mismo enganchando el filtro
redirect_post_location
y luego usandoadd_query_arg
para añadir tu propio parámetro a la solicitud. Así:add_filter('redirect_post_location','my_message'); function my_message($loc) { return add_query_arg( 'my_message', 123, $loc ); }
Esto añade
my_message=123
a la consulta. Luego, después de la redirección, puedes detectar el ajuste my_message en$_GET
y mostrar el mensaje adecuado correspondiente.

Puedes hacer esto manualmente, pero WordPress lo hace nativamente así para errores de configuración:
add_settings_error()
para crear el mensaje.- Luego
set_transient('settings_errors', get_settings_errors(), 30);
settings_errors()
en el hookadmin_notices
para mostrar (necesitarás enganchar para pantallas que no son de configuración).

hace lo que quiero, pero ¿no llenaría esto la base de datos con montones de transitorios?

@One Trick Pony en el proceso nativo el transitorio se elimina explícitamente (ver código fuente de get_settings_errors()
). Puede que necesites hacerlo tú mismo si adaptas la lógica para una página que no sea de ajustes.

aún así no me gusta la idea de almacenar mensajes de error temporales en la base de datos. Usaré ajax para advertir al usuario cuando cambie la entrada

Sé que esta pregunta es antigua pero encuentro que las respuestas aquí no resuelven el problema.
Extendiendo la respuesta de Ana Ban, usando el método de Otto, encontré que esta es la mejor forma de manejar errores. Esto no requiere almacenar los errores en la base de datos.
Incluí una versión simplificada de un objeto Metabox que uso. Esto me permite agregar fácilmente nuevos mensajes de error y asegurar que el usuario correcto vea el mensaje de error (usando la base de datos, esto no está garantizado).
<?php
/**
* Clase MetaboxExample
*/
class MetaboxExample {
/**
* Define la lista blanca de pantallas permitidas (post_types)
*/
private $_allowedScreens = array( 'SCREENS_TO_ALLOW_METABOX' );
/**
* Parámetro GET para el código de error del cuadro de error
*/
const GET_METABOX_ERROR_PARAM = 'meta-error';
/**
* Define los hooks de administración
*/
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')); // NOTA: admin_notices no posiciona esto correctamente en páginas de tipos de post personalizados, no he probado esto en POST o PAGE pero no veo que sea un problema
}
/**
* Agrega el metabox a los tipos de post especificados
*/
public function addMetabox() {
foreach ( $this->_allowedScreens as $screen ) {
add_meta_box(
'PLUGIN_METABOX',
__( 'TÍTULO', 'text_domain' ),
array($this, 'metaBox'),
$screen,
'side',
'high'
);
}
}
/**
* Muestra el contenido del metabox
* @param $post
*/
public function metaBox($post) {
// Agrega un campo nonce para que podamos verificarlo más tarde.
wp_nonce_field( 'metaboxnonce', 'metaboxnonce' );
// Carga los metadatos para este metabox
$someValue = get_post_meta( $post->ID, 'META_KEY_IDENTIFIER', true );
?>
<p>
<label for="some-value" style="width: 120px; display: inline-block;">
<?php _e( 'Algún campo:', 'text_domain' ); ?>
</label>
<input type="text" id="some-value" name="some_value" value="<?php esc_attr_e( $someValue ); ?>" size="25" />
</p>
<?php
}
/**
* Método para guardar el metabox
* @param $post_id
*/
public function saveMetabox($post_id) {
global $wpdb;
// Verifica si nuestro nonce está configurado.
if ( ! isset( $_POST['metaboxnonce'] ) ) {
return $post_id;
}
// Verifica que el nonce sea válido.
if ( ! wp_verify_nonce( $_POST['metaboxnonce'], 'metaboxnonce' ) ) {
return $post_id;
}
// Si esto es un autoguardado, nuestro formulario no se ha enviado, así que no queremos hacer nada.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $post_id;
}
// Verifica los permisos del usuario.
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;
}
}
// Asegúrate de que esté configurado.
if ( !isset( $_POST['some_value'] ) ) {
return $post_id;
}
// Sanitiza la entrada del usuario.
$someValue = sanitize_text_field( $_POST['some_value'] );
// Verifica que haya un valor
if (empty($someValue)) {
// Agrega nuestro código de error
add_filter('redirect_post_location', function($loc) {
return add_query_arg( self::GET_METABOX_ERROR_PARAM, 1, $loc );
});
return $post_id; // asegúrate de retornar para no permitir más procesamiento
}
// Actualiza el campo meta en la base de datos.
update_post_meta( $post_id, 'META_KEY_IDENTIFIER', $someValue );
}
/**
* Notificaciones de administración del metabox
*/
public function adminNotices() {
if (isset($_GET[self::GET_METABOX_ERROR_PARAM])) {
$screen = get_current_screen();
// Asegúrate de que estemos en el tipo de post correcto
if (in_array($screen->post_type, $this->_allowedScreens)) {
$errorCode = (int) $_GET[self::GET_METABOX_ERROR_PARAM];
switch($errorCode) {
case 1:
$this->_showAdminNotice( __('Ocurrió algún error', 'text_domain') );
break;
// Más códigos de error van aquí para mostrar errores
}
}
}
}
/**
* Muestra la notificación de administración para el metabox
* @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
}
}

El único problema que tengo con esta respuesta es que no funciona con PHP 5.2. No digo que todos debamos soportar PHP 5.2, pero hasta que WordPress no establezca PHP 5.2 como requisito mínimo, necesitamos darle soporte si estamos distribuyendo el plugin :(

Si eliminas la función anónima y la conviertes en un método público, debería funcionar bien. Entiendo tu problema, pero personalmente no desarrollaré para una versión EOL de PHP (http://php.net/eol.php) 5.2 EOL fue el 6 de enero de 2011. WordPress debería esforzarse más por no soportar versiones EOL, pero esa es otra historia, además de las malas empresas de hosting que aún proporcionan versiones EOL...
