Agregar validación y manejo de errores al guardar campos personalizados

9 dic 2010, 23:57:18
Vistas: 37.2K
Votos: 31

Tengo una función que define un campo personalizado en un tipo de entrada. Digamos que el campo es "subhead".

Cuando se guarda la entrada, quiero realizar alguna validación en el input y mostrar un mensaje de error en la pantalla de edición si es necesario. Algo como:

// Manejar actualización de entrada
function wpse_update_post_custom_values($post_id, $post) {

    // Hacer algunas verificaciones...
    if($_POST['subhead'] != 'valor que espero') {

        // Agregar un error aquí
        $errors->add('oops', 'Hubo un error.');

    }

    return $errors;

} 
add_action('save_post','wpse_update_post_custom_values',1,2);

Estoy tratando de conectar esto a la acción save_post, pero no puedo averiguar cómo manejar los errores. No parece haber un objeto de error pasado a la función, y si creo mi propio objeto WP_Error y lo devuelvo, no es respetado por el mecanismo que muestra los errores en la página de edición.

Actualmente tengo un mensaje de error en la página dentro de mi meta box personalizado, pero esto no es lo ideal - preferiría tener un error grande y rojo en la parte superior como normalmente muestra WordPress.

¿Alguna idea?

ACTUALIZACIÓN:

Basado en la respuesta de @Denis, probé varias cosas diferentes. Almacenar errores como una variable global no funcionó, porque WordPress hace una redirección durante el proceso save_post, lo que elimina la global antes de que puedas mostrarla.

Terminé almacenándolos en un campo meta. El problema con esto es que necesitas limpiarlos, o no desaparecerán cuando navegues a otra página, así que tuve que agregar otra función conectada al admin_footer que simplemente limpia los errores.

No hubiera esperado que el manejo de errores para algo tan común (actualizar entradas) fuera tan complicado. ¿Me estoy perdiendo algo obvio o este es el mejor enfoque?

// Manejar actualización de entrada
function wpse_5102_update_post_custom_values($post_id, $post) {

    // Para mantener los errores
    $errors = false;

    // Hacer alguna validación...
    if($_POST['subhead'] != 'valor que espero') {

        // Agregar un error aquí
        $errors .= 'ups... hubo un error.';

    }

    update_option('my_admin_errors', $errors);

    return;

} 
add_action('save_post','wpse_5102_update_post_custom_values',1,2);


// Mostrar cualquier error
function wpse_5102_admin_notice_handler() {

    $errors = get_option('my_admin_errors');

    if($errors) {

        echo '<div class="error"><p>' . $errors . '</p></div>';

    }   

}
add_action( 'admin_notices', 'wpse_5102_admin_notice_handler' );


// Limpiar cualquier error
function wpse_5102__clear_errors() {

    update_option('my_admin_errors', false);

}
add_action( 'admin_footer', 'wpse_5102_clear_errors' );
6
Comentarios

Buena pregunta. Creo que podrías eliminar el hook admin_footer si limpias los errores al final de tu función de manejo de notificaciones. Simplifica un poco las cosas.

Geert Geert
3 may 2011 09:54:21

¿Cómo estás manejando el repoblar los campos del formulario (con los posibles datos inválidos)?

Geert Geert
3 may 2011 09:58:27

Tengo una pregunta básica. ¿En qué archivo php de Wordpress está esto?

User User
27 may 2011 02:25:20

@Karen Esto iría en un archivo de plugin personalizado, o en tu functions.php.

MathSmath MathSmath
27 may 2011 07:38:37

Podría estar pasando por alto algo obvio, pero ¿sería un poco más eficiente ejecutar update_option('my_admin_errors', false); inmediatamente después de la sentencia if al final de wpse_5102_admin_notice_handler()?

Andrew Odri Andrew Odri
1 dic 2012 00:55:24

@AndrewOdri Sí, supongo que eso también funcionaría. Siempre y cuando no necesites la información del error más tarde (para resaltar cosas en el formulario, o lo que sea). ¡Buen ojo!

MathSmath MathSmath
5 dic 2012 18:19:40
Mostrar los 1 comentarios restantes
Todas las respuestas a la pregunta 7
2

Almacena errores en tu clase o como global, posiblemente en un transient o meta, y muéstralos en admin notices en las peticiones POST. WP no incluye ningún manejador de mensajes flash.

10 dic 2010 00:02:19
Comentarios

¡Gracias por señalarme esta dirección! Terminé usando un meta para almacenar errores, porque tuve problemas intentando hacerlo como una variable global o una propiedad. Estoy actualizando mi respuesta ahora mismo para explicar cómo lo estoy haciendo... por favor, déjame saber si esto es el tipo de cosa que estabas sugiriendo, o si hay una mejor manera que no estoy entendiendo.

MathSmath MathSmath
10 dic 2010 02:15:03

Ese tipo de cosa, sí. Aunque, pensándolo mejor, tal vez almacenarlo en una variable de sesión. Esto, para permitir que múltiples autores editen publicaciones al mismo tiempo. :-) Además, creo que no es posible almacenar false en una opción. Almacena una cadena vacía en su lugar.

Denis de Bernardy Denis de Bernardy
10 dic 2010 11:35:09
4

Sugiero usar sesiones ya que esto no creará efectos extraños cuando dos usuarios editen al mismo tiempo. Esto es lo que hago:

WordPress no inicia sesiones por defecto. Así que necesitas iniciar una sesión en tu plugin, functions.php o incluso wp-config.php:

if (!session_id())
  session_start();

Al guardar el post, agrega errores y notificaciones a la sesión:

function my_save_post($post_id, $post) {
   if($something_went_wrong) {
     //Agregar notificación de error si algo salió mal
     $_SESSION['my_admin_notices'] .= '<div class="error"><p>Algo salió mal</p></div>';
     return false; //podría detener el procesamiento aquí
   }
   if($somthing_to_notice) {  //ej. guardado exitoso
     //Agregar notificación si algo salió mal
     $_SESSION['my_admin_notices'] .= '<div class="updated"><p>Post actualizado</p></div>';
   }

   return true;
} 
add_action('save_post','my_save_post');

Mostrar notificaciones y errores y luego limpiar los mensajes en la sesión:

function my_admin_notices(){
  if(!empty($_SESSION['my_admin_notices'])) print  $_SESSION['my_admin_notices'];
  unset ($_SESSION['my_admin_notices']);
}
add_action( 'admin_notices', 'my_admin_notices' );
14 jun 2011 13:36:04
Comentarios

solución para la versión de sesión: la primera vez que uses la variable de sesión no uses .= solo = si activas la depuración, puedes ver por qué...

User User
13 jul 2012 16:12:01

Yo también he hecho esto, pero si publicas un plugin para una audiencia amplia de esa manera, la gente terminará odiándote por ello. WordPress no inicia sesiones porque está diseñado para ser sin estado y no necesitarlas, y algunas configuraciones extrañas de servidor pueden romperlo. Usa la API de transitorios - http://codex.wordpress.org/Transients_API en lugar de sesiones y mantendrás la compatibilidad. Solo pensé que valía la pena señalar una razón para no hacer esto aquí.

pospi pospi
12 sept 2012 04:37:55

@pospi esto parece tener problemas similares al uso original de las funciones get_option y update_option. Así que supongo que la solución sería añadir el ID del usuario actual a la clave?

Gazillion Gazillion
24 oct 2013 19:55:28

¡Sí, eso funcionaría perfectamente! Siempre y cuando añadas algo para identificar de manera única al usuario, evitarás que los mensajes se mezclen entre los usuarios conectados (:

pospi pospi
28 oct 2013 02:23:34
2

Basado en la sugerencia de pospi para usar transients, se me ocurrió lo siguiente. El único problema es que no hay un hook para colocar el mensaje debajo del h2 donde van otros mensajes, así que tuve que usar un truco con jQuery para colocarlo ahí.

Primero, guarda el mensaje de error durante tu manejador save_post (o similar). Le doy una vida corta de 60 segundos, así que estará allí solo el tiempo suficiente para que ocurra la redirección.

if($has_error)
{
  set_transient( "acme_plugin_error_msg_$post_id", $error_msg, 60 );
}

Luego, simplemente recupera ese mensaje de error en la siguiente carga de página y muéstralo. También lo elimino para que no se muestre dos veces.

add_action('admin_notices', 'acme_plugin_show_messages');

function acme_plugin_show_messages()
{
  global $post;
  if ( false !== ( $msg = get_transient( "acme_plugin_error_msg_{$post->ID}" ) ) && $msg) {
    delete_transient( "acme_plugin_error_msg_{$post->ID}" );
    echo "<div id=\"acme-plugin-message\" class=\"error below-h2\"><p>$msg</p></div>";
  }
}

Dado que admin_notices se ejecuta antes de que se genere el contenido principal de la página, el aviso no aparece donde van otros mensajes de edición de entradas, así que tuve que usar este jQuery para moverlo ahí:

jQuery('h2').after(jQuery('#acme-plugin-message'));

Como el ID de la entrada es parte del nombre del transient, esto debería funcionar en la mayoría de entornos multi-usuario excepto cuando varios usuarios están editando la misma entrada concurrentemente.

16 sept 2012 00:55:49
Comentarios

¿Podrías elaborar sobre "Dado que el ID del post es parte del nombre del transitorio"? Creé una clase para manejar mensajes de error usando esta técnica pero requiero que mi constructor pase un user_ID. ¿La API de transitorios usa el user_id al hacer hash de la clave? (pregunto porque el codex no parece mencionar esto)

Gazillion Gazillion
24 oct 2013 20:37:01

No, pero podrías agregarlo manualmente. En el código que publiqué arriba, el nombre del transitorio es acme_plugin_error_msg_POSTID. Podrías simplemente agregar el ID de usuario así acme_plugin_error_msg_POSTID_USERID.

Josh Coady Josh Coady
30 ene 2015 06:34:21
1

Cuando se ejecuta save_post, el post ya ha sido guardado en la base de datos.

Al revisar el código núcleo de WordPress, específicamente en la función update_post() del archivo wp-includes/post.php, no existe una forma incorporada de interceptar una solicitud antes de que se guarde en la base de datos.

Sin embargo, podemos engancharnos a pre_post_update y usar header() junto con get_post_edit_link() para evitar que el post se guarde.

<?php

/**
*   Realiza validación antes de guardar/insertar un custom post type
*/
function custom_post_site_save($post_id, $post_data) {
    // Si esto es solo una revisión, no hacer nada
    if (wp_is_post_revision($post_id))
        return;

    if ($post_data['post_type'] == 'my_custom_post_type') {
        // Rechazar títulos con menos de 5 caracteres
        if (strlen($post_data['post_title'] < 5)) {
            header('Location: '.get_edit_post_link($post_id, 'redirect'));
            exit;
        }
    }
}
add_action( 'pre_post_update', 'custom_post_site_save', 10, 2);

Si deseas notificar al usuario sobre el error, revisa este gist: https://gist.github.com/Luc45/09f2f9d0c0e574c0285051b288a0f935

30 jul 2018 00:54:58
Comentarios

Gracias por esto, maneja la validación perfectamente, ya sea al publicar por primera vez o al actualizar la publicación. Acabas de ahorrarme mucho tiempo y esfuerzo.

Zade Zade
15 dic 2018 10:31:52
2

¿Por qué no validas tu campo con la ayuda de un poco de Javascript? Creo que este sería el mejor enfoque para esto.

10 dic 2010 10:10:10
Comentarios

¡Gracias por la sugerencia! Lo que dejé fuera de la pregunta (por simplificar) es que estoy intentando manejar errores de carga de archivos, así que necesita ser del lado del servidor. ¡Gracias de todos modos por la sugerencia!

MathSmath MathSmath
10 dic 2010 18:33:45

La validación con javascript no previene de algunos ataques, la validación del lado del servidor es la única segura. Además, WordPress ofrece buenas herramientas para validar datos de usuario. Pero tienes razón, si solo se trata de verificar algunos valores antes de enviar datos al servidor, puedes ahorrar algo de tiempo en servidores con baja capacidad ^^

nderambure nderambure
15 mar 2011 01:50:40
2

Al intentar usar el script anterior, me encontré con un problema extraño. Se muestran dos mensajes en la pantalla de edición después de actualizar la publicación. Uno muestra el estado del contenido del guardado anterior y otro del actual. Por ejemplo, si guardo la publicación correctamente y luego cometo un error, el primero muestra "error" y el segundo "ok", aunque se generan al mismo tiempo. Si modifico el script y solo agrego un mensaje (por ejemplo, "error"), inicio una actualización con "error" y luego otra con "ok", el mensaje de "error" permanece (se muestra por segunda vez). Debo guardar con "ok" una vez más para eliminarlo. Realmente no sé qué está mal, lo he probado en tres servidores locales diferentes y ocurre el mismo problema en cada uno. Si alguien tiene alguna idea o sugerencia, ¡por favor ayude!

8 sept 2011 19:57:15
Comentarios

Hice algunas pruebas más de la versión más simple y segunda del script que mencioné arriba y parece que si el mensaje de "error" realmente se agrega al arreglo de sesión, se muestra en la pantalla de edición. Si no hay mensaje (todo está "ok") y el mensaje anterior era un error, aparece en la pantalla. Lo extraño es que se genera al momento de guardar (no está cachead) - lo verifiqué usando date() en el cuerpo del mensaje de error. Ahora estoy totalmente confundido.

jlub jlub
8 sept 2011 20:55:32

Ok, en caso de que alguien más se esté arrancando los cabellos - resultó que el sistema de revisiones de Wordpress era el problema (¿algún tipo de bug probablemente?). Lo desactivé y ahora todo está bien.

User User
12 sept 2011 12:41:27
1

He escrito un plugin que añade un manejo de errores rápido para las pantallas de edición de entradas y evita que las entradas se publiquen hasta que se completen los campos obligatorios:

https://github.com/interconnectit/required-fields

Permite hacer obligatorios cualquier campo de entrada, pero también puedes usar la API que proporciona para hacer que cualquier campo personalizado sea obligatorio con un mensaje de error personalizable y una función de validación. Por defecto, comprueba si el campo está vacío o no.

19 nov 2012 12:00:22
Comentarios

No dudes en reportar cualquier problema en github si te encuentras con alguno. También necesito documentar un poco mejor la API ya que hay algunos filtros adicionales que puedes utilizar.

sanchothefat sanchothefat
7 dic 2012 16:13:53