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

27

Tengo una función que define un campo personalizado en un tipo de publicación. Digamos que el campo es "subtítulo".

Cuando se guarda la publicación, quiero hacer una validación en la entrada y mostrar un mensaje de error en la pantalla de edición de la publicación si es necesario. Algo como:

// Handle post updating
function wpse_update_post_custom_values($post_id, $post) {

    // Do some checking...
    if($_POST['subhead'] != 'value i expect') {

        // Add an error here
        $errors->add('oops', 'There was an 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 entender cómo manejar los errores. No parece haber un objeto de error pasado a la función, y si creo mi propio obj WP_Error y lo devuelvo, no se respeta por ningún mecanismo que escupe errores en la página de edición posterior.

Actualmente tengo un mensaje de error en la página dentro de mi metacuadro personalizado, pero esto es menos que ideal: prefiero tener un error grande, rojo y arriba como el que normalmente se muestra WP.

¿Algunas ideas?

ACTUALIZAR:

Según la respuesta de @Denis, probé algunas cosas diferentes. El almacenamiento de errores como global no funcionó, porque Wordpress realiza una redirección durante el proceso save_post, que mata al global antes de que pueda visualizarlo.

Terminé almacenándolos en un metacampo. El problema con esto es que debe eliminarlos, o no desaparecerán cuando navegue a otra página, por lo que tuve que agregar otra función adjunta al admin_footer que simplemente elimina los errores.

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

// Handle post updating
function wpse_5102_update_post_custom_values($post_id, $post) {

    // To keep the errors in
    $errors = false;

    // Do some validation...
    if($_POST['subhead'] != 'value i expect') {

        // Add an error here
        $errors .= 'whoops...there was an error.';

    }

    update_option('my_admin_errors', $errors);

    return;

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


// Display any errors
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' );


// Clear any errors
function wpse_5102__clear_errors() {

    update_option('my_admin_errors', false);

}
add_action( 'admin_footer', 'wpse_5102_clear_errors' );
MathSmath
fuente
Buena pregunta. Creo que podría deshacerse del admin_footerenganche si elimina los errores al final de su función de controlador de notificaciones. Simplifica las cosas solo un poco.
Geert
¿Cómo se ocupa de repoblar los campos del formulario (con los posibles datos no válidos)?
Geert
Tengo una pregunta básica. ¿En qué archivo php de Wordpress está esto?
@Karen Esto estaría en un archivo de complemento personalizado, o en tu functions.php.
MathSmath
Puede que me falte algo obvio, pero ¿sería un poco más eficiente ejecutar update_option('my_admin_errors', false);inmediatamente después de la declaración if al final de wpse_5102_admin_notice_handler()?
Andrew Odri

Respuestas:

6

Almacene los errores en su clase o como global, posiblemente en un transitorio o meta, y muéstrelos en avisos de administrador en solicitudes POST. WP no cuenta con ningún controlador de mensajes flash.

Denis de Bernardy
fuente
¡Gracias por señalarme en esta dirección! Terminé usando un meta para almacenar errores, porque tuve problemas para intentar hacerlo como global o como una propiedad. Estoy actualizando mi respuesta en este momento para explicar cómo lo estoy haciendo ... avíseme si este es el tipo de cosa que estaba sugiriendo, o si hay una mejor manera de que no lo entiendo.
MathSmath
Ese tipo de cosas, sí. Sin embargo, quizás lo almacene en una variable de sesión, sin pensarlo. Esto, para permitir que varios autores editen publicaciones al mismo tiempo. :-) Además, creo que no es posible almacenar false en una opción. Almacene una cadena vacía en su lugar.
Denis de Bernardy
6

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

Las sesiones no se inician con WordPress. Por lo tanto, debe iniciar una sesión en su complemento, functions.php o incluso wp-config.php:

if (!session_id())
  session_start();

Al guardar la publicación, agregue errores y avisos a la sesión:

function my_save_post($post_id, $post) {
   if($something_went_wrong) {
     //Append error notice if something went wrong
     $_SESSION['my_admin_notices'] .= '<div class="error"><p>This or that went wrong</p></div>';
     return false; //might stop processing here
   }
   if($somthing_to_notice) {  //i.e. successful saving
     //Append notice if something went wrong
     $_SESSION['my_admin_notices'] .= '<div class="updated"><p>Post updated</p></div>';
   }

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

Imprima avisos y errores y luego limpie 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' );
davidn
fuente
. fix para la versión de sesión: en la primera vez de utilizar la variable de sesión no utilizar = = Sólo si a su vez en la depuración, se puede comprobar por qué ...
3
También he estado haciendo esto, pero si lanzas un complemento a una audiencia tan amplia como esa, la gente terminará odiándote por ello. Wordpress no crea instancias de sesiones porque está diseñado para no tener estado y no las necesita, y algunas configuraciones de servidor extrañas lo romperán. Use la API de transitorios - codex.wordpress.org/Transients_API en lugar de sesiones y mantendrá la compatibilidad. Solo pensé que valía la pena señalar una razón por la cual no hacer esto aquí.
pospi
@pospi parece tener problemas similares al uso original de las funciones get_option y update_option. Entonces, ¿supongo que la solución sería agregar la identificación del usuario actual a la clave?
Gazillion
Sí, eso funcionaría totalmente! Siempre que agregue algo para identificar de forma única al usuario, evitará que los mensajes se mezclen entre los usuarios registrados (:
pospi
5

Basado en la sugerencia de pospi de usar transitorios , se me ocurrió lo siguiente. El único problema es que no hay gancho para poner el mensaje debajo de donde van otros mensajes, así que tuve que hacer un hack de jQuery para llegar allí.h2

Primero, guarde el mensaje de error durante su save_postcontrolador (o similar) Le doy una corta vida útil de 60 segundos, por lo que está allí el tiempo suficiente para que ocurra la redirección.

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

Luego, solo recupere ese mensaje de error en la siguiente página cargada y muéstrelo. 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 se admin_noticesdispara antes de que se genere el contenido de la página principal, el aviso no es donde van los otros mensajes de edición de publicación, por lo que tuve que usar este jQuery para moverlo allí:

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

Dado que la ID de la publicación es parte del nombre transitorio, esto debería funcionar en la mayoría de los entornos multiusuario, excepto cuando varios usuarios están editando simultáneamente la misma publicación.

Joshua Coady
fuente
¿Podría dar más detalles sobre "Dado que la ID de la publicación es parte del nombre transitorio"? Creé una clase para manejar mensajes de error usando esta técnica pero requiero que mi constructor pase un user_ID. ¿La API transitoria utiliza el ID de usuario cuando utiliza la clave? (Pregunto porque el códice no parece mencionar esto)
Gazillion
No, pero puede agregarlo manualmente. En el código que publiqué arriba, el nombre del transitorio es acme_plugin_error_msg_POSTID. Se podía añadir ID de usuario para que al igual que acme_plugin_error_msg_POSTID_USERID.
Joshua Coady
2

Cuando se save_postejecuta, ya ha guardado la publicación en la base de datos.

Mirando el código central de WordPress, más específicamente en la función de wp-includes/post.php's' update_post(), no hay una forma integrada de interceptar una solicitud antes de que se guarde en la base de datos.

Sin embargo, podemos conectar pre_post_updatey usar header()y get_post_edit_link()para evitar que se guarde la publicación.

<?php

/**
*   Performs validation before saving/inserting custom post type
*/
function custom_post_site_save($post_id, $post_data) {
    // If this is just a revision, don't do anything.
    if (wp_is_post_revision($post_id))
        return;

    if ($post_data['post_type'] == 'my_custom_post_type') {
        // Deny post titles with less than 5 characters
        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 desea notificar al usuario qué salió mal, consulte esta información general: https://gist.github.com/Luc45/09f2f9d0c0e574c0285051b288a0f935

Lucas Bustamante
fuente
Gracias por esto, maneja la validación perfectamente, ya sea publicando por primera vez o actualizando la publicación. Me acabas de ahorrar mucho tiempo y esfuerzo.
Zade
1

¿Por qué no valida su campo con la ayuda de algunos Javascript? Creo que este sería el mejor enfoque para esto.

Horttcore
fuente
¡Gracias por la sugerencia! Lo que dejé fuera de la pregunta (por simplicidad) es que estoy tratando de manejar los errores de carga de archivos, por lo que debe ser del lado del servidor. Gracias por la sugerencia sin embargo!
MathSmath
La validación de JavaScript no evita algunos ataques, la validación del lado del servidor es la única segura. Además, WordPress ofrece algunas buenas herramientas para validar los datos del usuario. Pero tiene razón si solo verifica algunos valores antes de enviar datos al servidor, puede ahorrar algo de tiempo en el servidor bajo ^^
nderambure
1

Intentando usar el script anterior, me encontré con un extraño problema. Se muestran dos mensajes en la pantalla de edición, después de la actualización posterior. Uno muestra el estado del contenido de guardar anterior y otro del actual. Por ejemplo, si guardo la publicación correctamente y luego cometo un error, el primero es "error" y el segundo es "ok", aunque se generan al mismo tiempo. Si cambio la secuencia de comandos y agrego solo un mensaje (p. Ej., "Error"), inicie una actualización con "error" y después de esa otra con "ok", el mensaje "error" permanece (se muestra por segunda vez). Debo guardar con "ok" una vez más para deshacerme de él. Realmente no sé qué está mal, lo he probado en tres servidores locales diferentes y hay el mismo problema en cada uno de ellos.

jlub
fuente
Hice algunas pruebas más de la segunda versión más simple de la secuencia de comandos que he mencionado anteriormente y parece que si el mensaje de "error" realmente se agrega a la matriz de la sesión, se muestra en la pantalla de edición. Si no hay ningún mensaje (todo está "bien") y el mensaje anterior fue un error, aparece en la pantalla. Lo que es extraño, se genera en el momento de guardar (no en caché): lo he comprobado usando date () en el cuerpo del mensaje de error. Estoy totalmente confundido ahora.
jlub
Ok, en caso de que alguien más se esté quitando el cabello de la cabeza, resultó que el sistema de revisiones de Wordpress era el problema (¿algún tipo de error probablemente?). Lo he desactivado y ahora todo está bien.
0

He escrito un complemento que agrega un manejo rápido de errores para las pantallas de edición de publicaciones y evita que se publiquen publicaciones hasta que se completen los campos obligatorios:

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

Le permite hacer obligatorios todos los campos de publicación, pero puede usar la API que proporciona para hacer que los campos personalizados también sean necesarios con un mensaje de error personalizable y una función de validación. El valor predeterminado es verificar si el campo está vacío o no.

Sanchothefat
fuente
No dude en agregar cualquier problema en github si se encuentra con ellos. También necesito documentar un poco mejor la API, ya que hay algunos filtros adicionales que puedes usar.
sanchothefat