4

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?

  • It's nearly impossible to make automatic system for dynamic input that was always accurate. You would need an army of engineers from Google to code a complex algorithm or something that would assist if there are few character errors or checked if some parts of text matched (without false positives). Why? Because there are around 20 000 cities in States and you can't use regular == because that would fail in most cases. You might think that your site is small or something but it will get more and more complicated with each new taxonomy. Better change the system or embrace the situation. – N00b Apr 30 '16 at 5:12
  • 1
    @TwerkingN00b, i assume OP is asking a way to check the input before it is created which is already in the db.I may be wrong – bravokeyl Apr 30 '16 at 5:22
  • 1
    You might want to check pre_insert_term filter? – Sumit Apr 30 '16 at 5:41
  • Yes, this preprocessing might help with small amout of taxonomies (would still require a lot of switch cases).. Question describes how some users add comma where they shouldn't but what if they misspell or use more than one space? There's too many things that could go wrong.. In order to totally eliminate the problem you would need to use <select> but that would require you to insert all the available cities to database. There might be a public list or something to make it painless.. But in the end of the day you can't 100% control the output if user has total freedom over the input. – N00b Apr 30 '16 at 6:13
  • I don't expect 100% control. But I have examined the input and there is this very common error which is over 90% of the difficulty. There are only 50 states, so I figure it is reasonable to check against them, and if one of the tags matches, then see if "city thatstate" exists already. As @bravokeyl suggests, I just need to know how to do this. – Joel Apr 30 '16 at 11:52
2

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.