I am working on a website where there is a custom taxonomy for places, which can be either cities or states. The users are supposed to tag each post with a city and a state. To deal with disambiguating cities with the same name in multiple states, those city tags have the state name appended (eg “Miami, Florida” has the name “Miami Florida” and “Miami, Ohio” has the name “Miami Ohio”).
Users have been unable to master this system, despite the autocomplete, and keep inputting “Miami, Florida”, which creates a new “Miami” tag along with the existing Florida tag.
I’d like to intercept the user input when the post is saved and a new tag is about to be created, to see if the city-state combination already exists (ie, if one tag is Miami and the other is Florida, does “Miami Florida” already exist). Then I’d like to block the creation of the new tag and instead apply the existing tag to the post.
What hook would I use, and how would I alter the tags on the post?
The hook you’re looking for is created_{$taxonomy}
but there’s a another issue. I think the biggest flaw in the logic is putting both states and cities in the same taxonomy. You’ll never be able to differentiate between the two terms ( which is the city and which is the state ). This opens the user to do some of the following all of which are incorrect
- Enter city with no state
- Enter state with no city
- Misspell state or city
I think the solution here is to break the states and cities into two different taxonomies. tax_states
and tag_cities
. This will ensure that every post has a state and every city tag is prefixed with the state. I’m going to start the below code with the assumption that the two taxonomies are registered.
First, let’s add some jQuery to ensure that a state is set – you may want to consider enqueueing it but since it’s a small script maybe not:
function post_type_custom_script() {
global $post_type;
if( 'post' === $post_type ) {
?>
<script type="text/javascript">
/* Checks if cat is selected when publish button is clicked */
jQuery( '#submitdiv' ).on( 'click', '#publish', function( e ) {
var $checked = jQuery( '#category-all li input:checked' );
//Checks if cat is selected
if( $checked.length <= 0 ) {
alert( "Please select a State" );
return false;
} else {
return true;
}
} );
</script>
<?php
}
}
add_action( 'admin_footer', 'post_type_custom_script' );
The biggest issue with the above script is that it assumes that your taxonomy is hierarchical ( checkboxes ) – but since in theory only one state should be selected neither taxonomy is an optimal choice. Instead you could create a selectlist of terms and use save_post
to save the term or ( what I would suggest ) change the hierarchical checkboxes to Radio Buttons ( PHP Class, Radio Buttons for Taxonomies Plugin ).
Now we should have a Taxonomy for States which needs to be selected in order to publish and a Tag box for cities. The next step is to append the state to the city tag whenever it’s been published. It’s a hefty list of conditionals so I’ve added comments where necessary. If somehow a state isn’t selected we turn the post to a draft as a last result:
/**
* This function runs whenever a post is saved
*
* @param Int $post_id
*
* @return void
*/
function state_appended_tags( $post_id ) {
global $post;
if( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ( ! current_user_can( 'edit_post', $post_id ) || ! is_object( $post ) ) ) {
return $post_id;
}
if( 'post' === $post->post_type ) {
$state = false;
$set_draft = false;
if( isset( $_POST['tax_input'] ) && ! empty( $_POST['tax_input'] ) ) {
// Grab State
// If using the Radio Buttons Plugin linked above, use this conditional
// if( isset( $_POST['radio_tax_input']['tax_states'] ) && ! empty( $_POST['radio_tax_input']['tax_states'] ) && 0 !== $_POST['radio_tax_input']['tax_states'][0] ) {
// $state_obj = get_term_by( 'id', $_POST['radio_tax_input']['tax_states'][0], 'tax_states' ); // Grab Term Object
// $state_slug = $state_obj->slug;
if( isset( $_POST['tax_input']['tax_states'] ) && ! empty( $_POST['tax_input']['tax_states'] ) && 0 !== $_POST['tax_input']['tax_states'][0] ) {
$state_obj = get_term_by( 'id', $_POST['tax_input']['tax_states'][0], 'tax_states' ); // Grab Term Object
$state_slug = $state_obj->slug;
} else {
$set_draft = true;
}
// Prefix Cities
if( ! empty( $state_slug ) && isset( $_POST['tax_input']['tag_cities'] ) && ! empty( $_POST['tax_input']['tag_cities'] ) ) {
$new_tags = array();
foreach( $_POST['tax_input']['tag_cities'] as $city ) {
$city_obj = false;
$city_slug = '';
// Post tags come in two forms - slugs and integers
if( is_numeric( $city ) ) {
$city_obj = get_term_by( 'id', $city, 'tag_cities' ); // Grab Term Object
$city_slug = $city_obj->slug;
} else {
$city_obj = get_term_by( 'slug', $city, 'tag_cities' ); // Grab Term Object
$city_slug = $city_obj->slug;
}
// If user has already entered the correct state-city formatted tag, skip it
// Otherwise we'll enter the conditional
if( 0 !== strcmp( "{$state_slug}-{$city_slug}", $city_slug ) ) {
$new_tags[] = "{$state_slug}-{$city_slug}";
wp_delete_term( $city_obj->term_id, 'tag_cities' ); // Delete Term
}
}
// Set New State Appended Tags
if( ! empty( $new_tags ) ) {
wp_set_object_terms( $post_id, $new_tags, 'tag_cities', false );
}
}
} else {
$set_draft = true;
}
// Finally, should something have failed, don't publish the post and investigate
if( $set_draft ) {
remove_action( 'save_post', 'state_appended_tags' );
wp_update_post( array(
'ID' => $post_id,
'post_status' => 'draft',
) );
add_action( 'save_post', 'state_appended_tags' );
}
}
}
add_action( 'save_post', 'state_appended_tags' );
You’ll need to switch out tag_cities
and tax_states
with your actual taxonomies but this should work for you.