J'ai un type de message personnalisé Event
qui contient des champs personnalisés de début/fin (comme des métaboxes dans l'écran de modification).
Je voudrais m'assurer qu'un événement ne peut pas être publié (ou programmé) sans que les dates soient remplies, car cela poserait des problèmes avec les modèles affichant les données de l'événement (outre le fait qu'il s'agit d'une exigence nécessaire!). Cependant, j'aimerais pouvoir avoir des événements Brouillon qui ne contiennent pas de date valide pendant qu'ils sont en préparation.
Je pensais que je pourrais utiliser save_post
pour effectuer la vérification, mais comment puis-je empêcher le changement de statut?
EDIT1: C'est le hook que j'utilise maintenant pour sauvegarder le post_meta.
// Save the Metabox Data
function ep_eventposts_save_meta( $post_id, $post ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return;
if ( !isset( $_POST['ep_eventposts_nonce'] ) )
return;
if ( !wp_verify_nonce( $_POST['ep_eventposts_nonce'], plugin_basename( __FILE__ ) ) )
return;
// Is the user allowed to edit the post or page?
if ( !current_user_can( 'edit_post', $post->ID ) )
return;
// OK, we're authenticated: we need to find and save the data
// We'll put it into an array to make it easier to loop though
//debug
//print_r($_POST);
$metabox_ids = array( '_start', '_end' );
foreach ($metabox_ids as $key ) {
$events_meta[$key . '_date'] = $_POST[$key . '_date'];
$events_meta[$key . '_time'] = $_POST[$key . '_time'];
$events_meta[$key . '_timestamp'] = $events_meta[$key . '_date'] . ' ' . $events_meta[$key . '_time'];
}
$events_meta['_location'] = $_POST['_location'];
if (array_key_exists('_end_timestamp', $_POST))
$events_meta['_all_day'] = $_POST['_all_day'];
// Add values of $events_meta as custom fields
foreach ( $events_meta as $key => $value ) { // Cycle through the $events_meta array!
if ( $post->post_type == 'revision' ) return; // Don't store custom data twice
$value = implode( ',', (array)$value ); // If $value is an array, make it a CSV (unlikely)
if ( get_post_meta( $post->ID, $key, FALSE ) ) { // If the custom field already has a value
update_post_meta( $post->ID, $key, $value );
} else { // If the custom field doesn't have a value
add_post_meta( $post->ID, $key, $value );
}
if ( !$value )
delete_post_meta( $post->ID, $key ); // Delete if blank
}
}
add_action( 'save_post', 'ep_eventposts_save_meta', 1, 2 );
EDIT2: et c’est ce que j’essaie d’utiliser pour vérifier les données de publication après leur enregistrement dans la base de données.
add_action( 'save_post', 'ep_eventposts_check_meta', 99, 2 );
function ep_eventposts_check_meta( $post_id, $post ) {
//check that metadata is complete when a post is published
//print_r($_POST);
if ( $_POST['post_status'] == 'publish' ) {
$custom = get_post_custom($post_id);
//make sure both dates are filled
if ( !array_key_exists('_start_timestamp', $custom ) || !array_key_exists('_end_timestamp', $custom )) {
$post->post_status = 'draft';
wp_update_post($post);
}
//make sure start < end
elseif ( $custom['_start_timestamp'] > $custom['_end_timestamp'] ) {
$post->post_status = 'draft';
wp_update_post($post);
}
else {
return;
}
}
}
Le problème principal avec ceci est un problème qui a été décrit dans une autre question : utiliser wp_update_post()
dans un hook save_post
déclenche une boucle infinie.
EDIT3: J'ai imaginé un moyen de le faire en accrochant wp_insert_post_data
au lieu de save_post
. Le seul problème est que maintenant le post_status
est rétabli, mais maintenant un message trompeur disant "Post published" apparaît (en ajoutant &message=6
à l'URL redirigée), mais le statut est défini sur Draft.
add_filter( 'wp_insert_post_data', 'ep_eventposts_check_meta', 99, 2 );
function ep_eventposts_check_meta( $data, $postarr ) {
//check that metadata is complete when a post is published, otherwise revert to draft
if ( $data['post_type'] != 'event' ) {
return $data;
}
if ( $postarr['post_status'] == 'publish' ) {
$custom = get_post_custom($postarr['ID']);
//make sure both dates are filled
if ( !array_key_exists('_start_timestamp', $custom ) || !array_key_exists('_end_timestamp', $custom )) {
$data['post_status'] = 'draft';
}
//make sure start < end
elseif ( $custom['_start_timestamp'] > $custom['_end_timestamp'] ) {
$data['post_status'] = 'draft';
}
//everything fine!
else {
return $data;
}
}
return $data;
}
OK, c'est finalement comme ça que j'ai fini par le faire: un appel Ajax à une fonction PHP qui vérifie, inspirée en quelque sorte par cette réponse et en utilisant un astuce astucieuse issue d'une question J'ai demandé sur StackOverflow . De manière importante, je m'assure que la vérification n'est effectuée que lorsque nous voulons publier, afin qu'un brouillon puisse toujours être enregistré sans vérification. Cela s'est avéré être la solution la plus simple pour réellement empêcher la publication du message. Cela pourrait aider quelqu'un d'autre, alors je l'ai écrit ici.
Tout d'abord, ajoutez le Javascript nécessaire:
//AJAX to validate event before publishing
//adapted from https://wordpress.stackexchange.com/questions/15546/dont-publish-custom-post-type-post-if-a-meta-data-field-isnt-valid
add_action('admin_enqueue_scripts-post.php', 'ep_load_jquery_js');
add_action('admin_enqueue_scripts-post-new.php', 'ep_load_jquery_js');
function ep_load_jquery_js(){
global $post;
if ( $post->post_type == 'event' ) {
wp_enqueue_script('jquery');
}
}
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 == 'event' ){
?>
<script language="javascript" type="text/javascript">
jQuery(document).ready(function() {
jQuery('#publish').click(function() {
if(jQuery(this).data("valid")) {
return true;
}
var form_data = jQuery('#post').serializeArray();
var data = {
action: 'ep_pre_submit_validation',
security: '<?php echo wp_create_nonce( 'pre_publish_validation' ); ?>',
form_data: jQuery.param(form_data),
};
jQuery.post(ajaxurl, data, function(response) {
if (response.indexOf('true') > -1 || response == true) {
jQuery("#post").data("valid", true).submit();
} else {
alert("Error: " + response);
jQuery("#post").data("valid", false);
}
//hide loading icon, return Publish button to normal
jQuery('#ajax-loading').hide();
jQuery('#publish').removeClass('button-primary-disabled');
jQuery('#save-post').removeClass('button-disabled');
});
return false;
});
});
</script>
<?php
}
}
Ensuite, la fonction qui gère la vérification:
add_action('wp_ajax_ep_pre_submit_validation', 'ep_pre_submit_validation');
function ep_pre_submit_validation() {
//simple Security check
check_ajax_referer( 'pre_publish_validation', 'security' );
//convert the string of data received to an array
//from https://wordpress.stackexchange.com/a/26536/10406
parse_str( $_POST['form_data'], $vars );
//check that are actually trying to publish a post
if ( $vars['post_status'] == 'publish' ||
(isset( $vars['original_publish'] ) &&
in_array( $vars['original_publish'], array('Publish', 'Schedule', 'Update') ) ) ) {
if ( empty( $vars['_start_date'] ) || empty( $vars['_end_date'] ) ) {
_e('Both Start and End date need to be filled');
die();
}
//make sure start < end
elseif ( $vars['_start_date'] > $vars['_end_date'] ) {
_e('Start date cannot be after End date');
die();
}
//check time is also inputted in case of a non-all-day event
elseif ( !isset($vars['_all_day'] ) ) {
if ( empty($vars['_start_time'] ) || empty( $vars['_end_time'] ) ) {
_e('Both Start time and End time need to be specified if the event is not an all-day event');
die();
}
elseif ( strtotime( $vars['_start_date']. ' ' .$vars['_start_time'] ) > strtotime( $vars['_end_date']. ' ' .$vars['_end_time'] ) ) {
_e('Start date/time cannot be after End date/time');
die();
}
}
}
//everything ok, allow submission
echo 'true';
die();
}
Cette fonction retourne true
si tout va bien et soumet le formulaire pour publier la publication par le canal normal. Sinon, la fonction renvoie un message d'erreur affiché sous la forme alert()
et le formulaire n'est pas soumis.
Comme l'a souligné m0r7if3r, il n'y a aucun moyen de empêcher une publication d'être publiée à l'aide du hook save_post
, car au moment où ce hook est activé, la publication est déjà enregistrée. Cependant, ce qui suit vous permettra de rétablir l’état sans utiliser wp_insert_post_data
et sans provoquer de boucle infinie.
Ce qui suit n'est pas testé, mais devrait fonctionner.
<?php
add_action('save_post', 'my_save_post');
function my_save_post($post_id) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return;
if ( !isset( $_POST['ep_eventposts_nonce'] ) )
return;
if ( !wp_verify_nonce( $_POST['ep_eventposts_nonce'], plugin_basename( __FILE__ ) ) )
return;
// Is the user allowed to edit the post or page?
if ( !current_user_can( 'edit_post', $post->ID ) )
return;
// Now perform checks to validate your data.
// Note custom fields (different from data in custom metaboxes!)
// will already have been saved.
$prevent_publish= false;//Set to true if data was invalid.
if ($prevent_publish) {
// unhook this function to prevent indefinite loop
remove_action('save_post', 'my_save_post');
// update the post to change post status
wp_update_post(array('ID' => $post_id, 'post_status' => 'draft'));
// re-hook this function again
add_action('save_post', 'my_save_post');
}
}
?>
Je n'ai pas vérifié, mais en regardant le code, le message de réaction affiche un message incorrect indiquant que le message a été publié. En effet, WordPress nous redirige vers une URL où la variable message
est maintenant incorrecte.
Pour le changer, nous pouvons utiliser le filtre redirect_post_location
:
add_filter('redirect_post_location','my_redirect_location',10,2);
function my_redirect_location($location,$post_id){
//If post was published...
if (isset($_POST['publish'])){
//obtain current post status
$status = get_post_status( $post_id );
//The post was 'published', but if it is still a draft, display draft message (10).
if($status=='draft')
$location = add_query_arg('message', 10, $location);
}
return $location;
}
Pour résumer le filtre de redirection ci-dessus: Si une publication est définie pour être publiée, mais qu'elle est toujours brouillon, nous modifions le message en conséquence (qui est message=10
). Encore une fois, ceci n’a pas été testé, mais devrait fonctionner. Le Codex du add_query_arg
suggère que, lorsqu'une variable est déjà définie, la fonction la remplace (mais comme je l'ai dit, je n'ai pas testé cela).
Je pense que la meilleure façon de s'y prendre est de ne pas empêcher le changement de statut de se produire, mais plutôt de revenir en arrière le cas échéant. Par exemple: vous décrochez save_post
, avec une priorité très élevée (afin que le raccroché se déclenche très tard, en particulier après avoir effectué votre insertion méta), puis vérifiez le post_status
du message qui vient d’être sauvegardé et mettez-le à jour en attente (ou brouillon ou autre) s’il ne répond pas à vos critères.
Une autre stratégie consisterait à utiliser wp_insert_post_data
pour définir directement le post_status. L’inconvénient de cette méthode, en ce qui me concerne, est que vous n’avez pas encore inséré le postmeta dans la base de données. Vous devrez donc le traiter, etc. pour effectuer vos vérifications, puis le traiter à nouveau pour l’insérer. dans la base de données ... qui pourrait devenir très onéreuse, que ce soit en performances ou en code.
La meilleure méthode peut être JAVASCRIPT:
<script type="text/javascript">
var field_id = "My_field_div__ID"; // <----------------- CHANGE THIS
var SubmitButton = document.getElementById("save-post") || false;
var PublishButton = document.getElementById("publish") || false;
if (SubmitButton) {SubmitButton.addEventListener("click", SubmCLICKED, false);}
if (PublishButton) {PublishButton.addEventListener("click", SubmCLICKED, false);}
function SubmCLICKED(e){
var passed= false;
if(!document.getElementById(field_id)) { alert("I cant find that field ID !!"); }
else {
var Enabled_Disabled= document.getElementById(field_id).value;
if (Enabled_Disabled == "" ) { alert("Field is Empty"); } else{passed=true;}
}
if (!passed) { e.preventDefault(); return false; }
}
</script>