Cómo validar campos personalizados en un tipo de post personalizado
Escribí un plugin que crea un tipo de post personalizado con campos personalizados. Para evitar que los usuarios ingresen información incorrecta, ¿cómo puedo validar los datos?
Supuse que la función del hook save_post procesaría la validación de campos, pero no encuentro una manera directa de mostrar los errores al usuario.
¿Existe alguna función o característica incorporada en WordPress para esto? ¿Cuál es la técnica general para implementar validación de campos personalizados?

Vas por buen camino. Pruebo los campos en la devolución de llamada save_post
, y luego uso avisos de administración para mostrar errores al usuario cuando un campo falla en la validación. Aparecen en un cuadro resaltado en la parte superior de la página, igual que cualquier error o mensaje que WordPress genere por sí mismo.
Aquí tienes un ejemplo simple de cómo crear un aviso de administración:
function my_admin_notice()
{
?>
<div class="updated">
<p>Aenean eros ante, porta commodo lacinia.</p>
</div>
<?php
}
add_action( 'admin_notices', 'my_admin_notice' );
Eso no es muy práctico, sin embargo. En una situación como esta, realmente quieres una función a la que puedas pasar un mensaje. Algo como:
if( $pizza != 'warm' )
$notices->enqueue( 'La pizza no está caliente', 'error' );
Así que puedes escribir esa función enqueue()
tú mismo (junto con una función para imprimir los avisos), o puedes incluir una biblioteca como IDAdminNotices.
Aquí tienes un ejemplo de un plugin que escribí. Este utiliza funciones de encolado e impresión de avisos que están integradas en la clase misma, en lugar de incluir una biblioteca externa.
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( 'El orden de apilamiento debe ser un número entero.', 'error' );
}
else
update_post_meta( $post->ID, self::PREFIX . 'zIndex', $_POST[ self::PREFIX . 'zIndex'] );
// ...
}
add_action( 'save_post', array( $this, 'saveCustomFields' );

No me queda muy claro... ¿tu código de ejemplo requiere código de terceros o funcionará en WordPress tal cual está?

Esto solo parece funcionar cuando la página se carga por primera vez. Si se hace clic en el botón "Actualizar/Publicar", no se muestran notificaciones.

Ambos enfoques me funcionan. ¿Puedes publicar un enlace al código completo?

Código de ejemplo: http://pastebin.com/vTxv9cw1

Creo que el problema es que estás usando una variable global en lugar de guardar los avisos en la base de datos. Los datos en variables no persisten entre solicitudes. Entonces, el aviso se agrega a $bh_errorMessages en la solicitud de guardado, luego WordPress redirige de vuelta a la pantalla de Editar Entrada (que es una nueva solicitud), y todas las variables se reinician. Revisa http://en.wikipedia.org/wiki/Post/Redirect/Get. Examina más de cerca IDAdminNotices y asegúrate de estar haciendo todo lo que hace.

No tengo muy claro qué estás usando para permitir que las variables persistan entre solicitudes. ¿Es la inclusión de la variable $instance
? ¿O es una de las declaraciones add_action
? ¿O ambas? ¿Algo más?

Vale, creo que lo entendí. La magia está en las funciones de WordPress add_option
y get_option
para almacenar y recuperar la variable, y el hook shutdown
es útil para llamar a la función add_option
solo una vez por sesión después de que todo esté hecho.

Escribí un pequeño plugin que no solo valida los campos de entrada en tipos de contenido personalizados, sino que también elimina el aviso predeterminado del administrador, sin el uso de Javascript.
Aquí hay parte del código
/ Filtros de validación
$title = $album->post_title;
if ( ! $title ) {
$errors['title'] = "El título es obligatorio";
}
// si tenemos errores, configuremos algunos mensajes
if (! empty($errors)) {
// debemos eliminar esta acción o se ejecutará en bucle para siempre
remove_action('save_post', 'album_save_post');
// guardar los errores como opción
update_option('album_errors', $errors);
// Cambiar el estado de la entrada de publicado a borrador
$album->post_status = 'draft';
// actualizar la entrada
wp_update_post( $album );
// debemos volver a agregar esta acción
add_action('save_post', 'album_save_post');
// admin_notice es creado por un $_GET['message'] con un número que WordPress usa para
// mostrar el mensaje del administrador, así que agregaremos un filtro para reemplazar el mensaje predeterminado con una redirección
add_filter( 'redirect_post_location', 'album_post_redirect_filter' );
}
Puedes ver el tutorial completo aquí

Cuando save_post
se ejecuta, el post ya ha sido guardado en la base de datos.
Si estás usando ACF, tiene validación incorporada.
Sin embargo, si necesitas validar elementos fuera de ACF, como el post_title, las cosas se complican un poco.
Al revisar el código núcleo de WordPress, más específicamente la función update_post()
en wp-includes/post.php
, no hay una forma incorporada de interceptar una solicitud antes de que se guarde en la base de datos.
No obstante, 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 personalizada en el tipo de post personalizado "Site"
*/
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'] == 'site') {
# En este ejemplo, mostraremos un error para títulos de post con menos de 5 caracteres
if (strlen($post_data['post_title']) < 5) {
# Añadir una notificación
update_option('my_notifications', json_encode(array('error', 'El título del post no puede tener menos de 5 caracteres.')));
# Y redirigir
header('Location: '.get_edit_post_link($post_id, 'redirect'));
exit;
}
}
}
add_action( 'pre_post_update', 'custom_post_site_save', 10, 2);
/**
* Muestra notificaciones personalizadas en el panel de administración de WordPress
*/
function my_notification() {
$notifications = get_option('my_notifications');
if (!empty($notifications)) {
$notifications = json_decode($notifications);
#notifications[0] = (string) Tipo de notificación: error, updated o update-nag
#notifications[1] = (string) Mensaje
#notifications[2] = (boolean) ¿Es descartable?
switch ($notifications[0]) {
case 'error': # rojo
case 'updated': # verde
case 'update-nag': # ?
$class = $notifications[0];
break;
default:
# Por defecto, usa error por si acaso
$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>';
# Reiniciar la notificación
update_option('my_notifications', false);
}
}
add_action( 'admin_notices', 'my_notification' );

Solo quiero añadir a la excelente respuesta de @Lucas Bustamante que el valor de los campos personalizados puede accederse a través del global $_POST
.
Por lo tanto, la respuesta de @Lucas Bustamante también es válida si la validación se refiere a un campo personalizado. Por ejemplo:
<?php
/**
* Realiza validación personalizada en el tipo de post personalizado "Site"
*/
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'] == 'site') {
# En este ejemplo, generaremos un error para códigos postales con menos de 5 caracteres
if (strlen($_POST['zip_code']) < 5) {
# Añadir una notificación
update_option('my_notifications', json_encode(array('error', 'El código postal no puede tener menos de 5 caracteres.')));
# Y redireccionar
header('Location: '.get_edit_post_link($post_id, 'redirect'));
exit;
}
}
}
add_action( 'pre_post_update', 'custom_post_site_save', 10, 2);
/**
* Muestra notificaciones personalizadas en el panel de administración de WordPress
*/
function my_notification() {
$notifications = get_option('my_notifications');
if (!empty($notifications)) {
$notifications = json_decode($notifications);
#notifications[0] = (string) Tipo de notificación: error, updated o update-nag
#notifications[1] = (string) Mensaje
#notifications[2] = (boolean) ¿Es descartable?
switch ($notifications[0]) {
case 'error': # rojo
case 'updated': # verde
case 'update-nag': # ?
$class = $notifications[0];
break;
default:
# Por defecto usa error por si acaso
$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>';
# Reiniciamos la notificación
update_option('my_notifications', false);
}
}
add_action( 'admin_notices', 'my_notification' );

Puedes usar el filtro "wp_insert_post_data" para modificar o verificar datos antes de que sean insertados en la base de datos
add_filter( 'wp_insert_post_data', 'validate_posttypes' );
function wpb_custom_excerpt($data) {
str_starts_with($data['post_title'],'wp')?$data['post_title']='wp'.$data['post_title']:null;
return $data;
}
