No publicar posts de un custom post type si un campo de metadatos no es válido
Tengo un Custom Post Type (CPT) llamado event
. Tengo un meta box para este tipo con varios campos. Me gustaría validar algunos campos antes de publicar un evento. Por ejemplo, si la fecha de un evento no está especificada, me gustaría mostrar un mensaje de error informativo, guardar el evento para futuras ediciones, pero evitar que ese evento se publique. ¿Es el estado 'pending' para un post CPT sin toda la información necesaria la forma correcta de manejarlo?
¿Cuál es la mejor práctica para hacer validación de campos en CPTs y prevenir que un post se publique, pero guardarlo para futuras ediciones?
Muchas gracias, Dasha
Puedes evitar que el post se guarde completamente con pequeños trucos de jQuery y validar los campos antes de guardar, ya sea del lado del cliente o del servidor con ajax:
Primero añadimos nuestro JavaScript para capturar el evento de enviar/publicar y usarlo para enviar nuestra propia función ajax antes del envío real:
add_action('wp_print_scripts','my_publish_admin_hook');
function my_publish_admin_hook(){
if (is_admin()){
?>
<script language="javascript" type="text/javascript">
jQuery(document).ready(function() {
jQuery('#post').submit(function() {
var form_data = jQuery('#post').serializeArray();
form_data = jQuery.param(form_data);
var data = {
action: 'my_pre_submit_validation',
security: '<?php echo wp_create_nonce( 'pre_publish_validation' ); ?>',
form_data: form_data
};
jQuery.post(ajaxurl, data, function(response) {
if (response.indexOf('True') > -1 || response.indexOf('true') > -1 || response === true || response) {
jQuery('#ajax-loading').hide();
jQuery('#publish').removeClass('button-primary-disabled');
return true;
}else{
alert("Por favor corrige los siguientes errores: " + response);
jQuery('#ajax-loading').hide();
jQuery('#publish').removeClass('button-primary-disabled');
return false;
}
});
return false;
});
});
</script>
<?php
}
}
Luego creamos la función para hacer la validación real:
add_action('wp_ajax_my_pre_submit_validation', 'pre_submit_validation');
function pre_submit_validation(){
//Comprobación simple de seguridad
check_ajax_referer( 'pre_publish_validation', 'security' );
//haz tu validación aquí
//todos los campos del formulario están en el array $_POST['form_data']
//y devuelve true para enviar: echo 'true'; die();
//o tu mensaje de error: echo 'bal bla bla'; die();
}
Siempre puedes modificarlo un poco para hacer la validación solo para tu tipo de post añadiendo una comprobación condicional a la función my_publish_admin_hook
para tu tipo de post y para validar del lado del cliente, pero yo prefiero hacerlo del lado del servidor.

Tal vez estoy entendiendo mal algo. Parece que solo estás usando PHP para renderizar JavaScript que hace la validación. Eso no es validación del lado del servidor. Realmente no entiendo cómo encaja el pre_submit_validation
en esto.

El primer bloque my_publish_admin_hook
sí intercepta el envío del post del lado del cliente, pero luego realiza una llamada AJAX al servidor (pre-envío-oficial en pre_submit_validation
) que realiza la validación del lado del servidor.

Esto sigue siendo validación del lado del cliente, incluso si está usando AJAX para realizar la validación. El cliente debe ejecutar el JavaScript en primer lugar para que cualquier validación tenga lugar.
Sin embargo... aún encontré útil esta respuesta para la validación previa al envío. ¡Gracias!

El método consta de dos pasos: primero, una función para guardar los datos de tu campo de metabox personalizado (enganchado a save_post), y segundo, una función para leer ese nuevo post_meta (que acabas de guardar), validarlo y modificar el resultado del guardado si es necesario (también enganchado a save_post, pero después del primero). La función de validación, si falla la validación, en realidad cambia el post_status de vuelta a "pending", evitando efectivamente que la publicación se publique.
Dado que la función save_post se llama con frecuencia, cada función tiene comprobaciones para ejecutarse solo cuando el usuario pretende publicar y solo para tu tipo de publicación personalizada (mycustomtype).
También suelo añadir algunos mensajes de notificación personalizados para ayudar al usuario a entender por qué su publicación no se publicó, pero esos se complicaron un poco para incluirlos aquí...
No he probado este código exacto, pero es una versión simplificada de lo que he hecho en configuraciones de tipos de publicación personalizados a gran escala.
add_action('save_post', 'save_my_fields', 10, 2);
add_action('save_post', 'completion_validator', 20, 2);
function save_my_fields($pid, $post) {
// no ejecutar en autoguardado o cuando se crean nuevas publicaciones
if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || $post->post_status == 'auto-draft' ) return $pid;
// abortar si no es mi tipo personalizado
if ( $post->post_type != 'mycustomtype' ) return $pid;
// guardar post_meta con el contenido del campo personalizado
update_post_meta($pid, 'mymetafield', $_POST['mymetafield']);
}
function completion_validator($pid, $post) {
// no ejecutar en autoguardado o cuando se crean nuevas publicaciones
if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || $post->post_status == 'auto-draft' ) return $pid;
// abortar si no es mi tipo personalizado
if ( $post->post_type != 'mycustomtype' ) return $pid;
// marcador de completitud inicial (añadir más según sea necesario)
$meta_missing = false;
// obtener meta para validar
$mymeta = get_post_meta( $pid, 'mymetafield', true );
// solo comprobar que no esté vacío - podrías hacer otras pruebas...
if ( empty( $mymeta ) ) {
$meta_missing = true;
}
// al intentar publicar - verificar la completitud e intervenir si es necesario
if ( ( isset( $_POST['publish'] ) || isset( $_POST['save'] ) ) && $_POST['post_status'] == 'publish' ) {
// no permitir la publicación mientras alguno de estos esté incompleto
if ( $meta_missing ) {
global $wpdb;
$wpdb->update( $wpdb->posts, array( 'post_status' => 'pending' ), array( 'ID' => $pid ) );
// filtrar la URL de redirección para cambiar el mensaje de publicación
add_filter( 'redirect_post_location', create_function( '$location','return add_query_arg("message", "4", $location);' ) );
}
}
}
Para múltiples campos de metabox, simplemente añade más marcadores de completitud, recupera más post_meta y realiza más pruebas...

Debes verificar/validar el valor de tu campo meta en ajax, es decir, cuando el usuario presione el botón "Publicar/Actualizar". Aquí estoy validando que un producto de WooCommerce con el campo meta "product_number" no esté vacío.
add_action('admin_head-post.php','ep_publish_admin_hook');
add_action('admin_head-post-new.php','ep_publish_admin_hook');
function ep_publish_admin_hook(){
global $post;
if ( is_admin() && $post->post_type == 'product' ){
?>
<script language="javascript" type="text/javascript">
(function($){
jQuery(document).ready(function() {
jQuery('#publish').click(function() {
if(jQuery(this).data("valid")) {
return true;
}
//ocultar icono de carga, devolver el botón Publicar a su estado normal
jQuery('#publishing-action .spinner').addClass('is-active');
jQuery('#publish').addClass('button-primary-disabled');
jQuery('#save-post').addClass('button-disabled');
var data = {
action: 'ep_pre_product_submit',
security: '<?php echo wp_create_nonce( "pre_publish_validation" ); ?>',
'product_number': jQuery('#acf-field-product_number').val()
};
jQuery.post(ajaxurl, data, function(response) {
jQuery('#publishing-action .spinner').removeClass('is-active');
if ( response.success ){
jQuery("#post").data("valid", true).submit();
} else {
alert("Error: " + response.data.message );
jQuery("#post").data( "valid", false );
}
//ocultar icono de carga, devolver el botón Publicar a su estado normal
jQuery('#publish').removeClass('button-primary-disabled');
jQuery('#save-post').removeClass('button-disabled');
});
return false;
});
});
})(jQuery);
</script>
<?php
}
}
Después, añade la función manejadora de ajax:
add_action('wp_ajax_ep_pre_product_submit', 'ep_pre_product_submit_func');
function ep_pre_product_submit_func() {
//verificación básica de seguridad
check_ajax_referer( 'pre_publish_validation', 'security' );
if ( empty( $_POST['product_number'] ) || empty( $_POST['file_attachment'] ) ) {
$data = array(
'message' => __('Por favor ingresa el número de parte y el documento de especificación.'),
);
wp_send_json_error( $data );
}
wp_send_json_success();
}

Solo quería agregar que para leer las variables del post, usando la solución de Bainternet, tendrás que analizar la cadena en $_POST['form_data']
usando la función PHP parse_str
(solo para ahorrarte tiempo de investigación).
$vars = parse_str( $_POST['form_data'] );
Luego puedes acceder a cada variable simplemente usando $varname
. Por ejemplo, si tienes un campo meta llamado "my_meta" lo accederías así:
$vars = parse_str( $_POST['form_data'] );
if ( $my_meta == "something" ) { // hacer algo }
