6

I am trying to replace the categories metabox with what looks and works like the tags metabox because there's too much hierarchy and scrolling to check the appropriate categories and sub-categories isn't an option. So in my case tags'-like metabox is better.

This is how I am doing it:

/*
 * Non-hierarchal metabox for categories
 * (like the tags metabox)
 *
 * SOURCES:
 * http://wordpress.stackexchange.com/a/50098
 * http://wordpress.stackexchange.com/a/49048
 * http://wordpress.stackexchange.com/a/48816
 * 
 */

// De-register categories metabox
add_action( 'admin_menu', 'flatsy_remove_meta_box' );
function flatsy_remove_meta_box() {
    remove_meta_box( 'categorydiv', 'post', 'normal' );
}

// Add new taxonomy meta box
add_action( 'add_meta_boxes', 'flatsy_add_custom_cat_meta_box' );
function flatsy_add_custom_cat_meta_box() {
    add_meta_box( 'flatsy_categorydiv', 'Categories', 'flatsy_custom_cat_metabox', 'post', 'side', 'core' );
}

// This function determines what displays in your metabox
function flatsy_custom_cat_metabox( $post ) {
    $defaults = array('taxonomy' => 'category');
    if ( !isset($box['args']) || !is_array($box['args']) )
        $args = array();
    else
        $args = $box['args'];
    extract( wp_parse_args($args, $defaults), EXTR_SKIP );
    $tax_name = esc_attr($taxonomy);
    $taxonomy = get_taxonomy($taxonomy);
    $disabled = !current_user_can($taxonomy->cap->assign_terms) ? 'disabled="disabled"' : '';
?>
<div class="tagsdiv" id="<?php echo $tax_name; ?>">
    <div class="jaxtag">
    <div class="nojs-tags hide-if-js">
    <p><?php echo $taxonomy->labels->add_or_remove_items; ?></p>
    <textarea name="<?php echo "tax_input[$tax_name]"; ?>" rows="3" cols="20" class="the-tags" id="tax-input-<?php echo $tax_name; ?>" <?php echo $disabled; ?>><?php echo get_terms_to_edit( $post->ID, $tax_name ); // textarea_escaped by esc_attr() ?></textarea></div>
    <?php if ( current_user_can($taxonomy->cap->assign_terms) ) : ?>
    <div class="ajaxtag hide-if-no-js">
        <label class="screen-reader-text" for="new-tag-<?php echo $tax_name; ?>"><?php echo $box['title']; ?></label>
        <div class="taghint"><?php echo $taxonomy->labels->add_new_item; ?></div>
        <p><input type="text" id="new-tag-<?php echo $tax_name; ?>" name="newtag[<?php echo $tax_name; ?>]" class="newtag form-input-tip" size="16" autocomplete="off" value="" />
        <input type="button" class="button tagadd" value="<?php esc_attr_e('Add'); ?>" tabindex="3" /></p>
    </div>
    <p class="howto"><?php echo esc_attr( $taxonomy->labels->separate_items_with_commas ); ?></p>
    <?php endif; ?>
    </div>
    <div class="tagchecklist"></div>
</div>
<?php if ( current_user_can($taxonomy->cap->assign_terms) ) : ?>
<p class="hide-if-no-js"><a href="#titlediv" class="tagcloud-link" id="link-<?php echo $tax_name; ?>"><?php echo $taxonomy->labels->choose_from_most_used; ?></a></p>
<?php endif; ?>
<?php
}

That piece of code works as it should... see...

The categories metabox now looks like the tags metabox

...except that it isn't saving the category metadata when the post is saved. A little bit of searching revealed that I must be doing something like this:

<?php
add_action( 'save_post', 'cd_meta_box_save' );
function cd_meta_box_save( $post_id )
{
    // Bail if we're doing an auto save
    if( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;

    // if our nonce isn't there, or we can't verify it, bail
    if( !isset( $_POST['meta_box_nonce'] ) || !wp_verify_nonce( $_POST['meta_box_nonce'], 'my_meta_box_nonce' ) ) return;

    // if our current user can't edit this post, bail
    if( !current_user_can( 'edit_post' ) ) return;

    // now we can actually save the data
    $allowed = array( 
        'a' => array( // on allow a tags
            'href' => array() // and those anchors can only have href attribute
        )
    );

    // Make sure your data is set before trying to save it
    if( isset( $_POST['my_meta_box_text'] ) )
        update_post_meta( $post_id, 'my_meta_box_text', wp_kses( $_POST['my_meta_box_text'], $allowed ) );

    if( isset( $_POST['my_meta_box_select'] ) )
        update_post_meta( $post_id, 'my_meta_box_select', esc_attr( $_POST['my_meta_box_select'] ) );

    // This is purely my personal preference for saving check-boxes
    $chk = isset( $_POST['my_meta_box_check'] ) && $_POST['my_meta_box_select'] ? 'on' : 'off';
    update_post_meta( $post_id, 'my_meta_box_check', $chk );
}
?>

But as I am dealing with the default functionality (i.e. how/what wordpress already does with tags metabox), I want to know what checks are in place for save_post for 'category' and 'tag' meta boxes and how WordPress does it by default.

// Save post metadata when a post is saved.
add_action( 'save_post', 'flatsy_save_cat_meta' );
function flatsy_save_cat_meta( $post_id, $post, $update ) {

    return 'WHAT DO I DO HERE? HOW DOES WORDPRESS DO IT FOR THE TAGS METABOX?';

}

AND if that's not how it's done, what should the code look like when I am converting the category metabox to look like a tag metabox and vice-versa (two cases)?


CLARIFICATION: I don't want to change Categories from hierarchical to non-hierarchical. I just want a tags-like metabox for categories. If I wanted a non-hierarchical taxonomy I'd simply have registered a custom taxonomy.

5
+150

It's informative to check out the /wp-admin/post.php file, that contains the edit_post() function that calls wp_update_post(), which is a wp_insert_post() wrapper.

Here's a skeleton for saving the assigned category terms:

/**
 * Saving assigned category terms (skeleton)
 */
add_action( 'admin_action_editpost', function()
{
    add_filter( 'wp_insert_post_data',  function( $data, $parr )
    {
        add_action( 'save_post_post', function( $post_ID, $post ) use ( $parr )
        {
            if( 
                    isset( $parr['_wpnonce'] )
                &&  wp_verify_nonce( $parr['_wpnonce'], 'update-post_' . absint( $post_ID ) )
                &&  current_user_can( 'manage_categories' )
                && function_exists( 'wpse_save_assigned_cats' )
                && ! did_action( 'wpse_save_assigned_cats' )
            ) {
                wpse_save_assigned_cats( $post_ID, $parr );
                do_action( 'wpse_save_assigned_cats' );
            }
        }, 10, 2 );
        return $data;
    }, 10, 2 );
} );

where our helper function wpse_save_assigned_cats() is based on the edit_post() function:

/**
 * Helper function based on the cat/tax handling of the edit_post() functions
 */
function wpse_save_assigned_cats( $post_ID, $parr )
{
    if( ! empty( $parr['tax_input']['category'] ) && $post_ID > 0 )
    {       
        // Change the comma seperated string of category names,
        // in $parr['tax_input']['category'], to an array of cats id
        $input_cats = explode( ',',  trim( $parr['tax_input']['category'], " \n\t\r\0\x0B," ) );
        $clean_cats = array();
        foreach ( $input_cats as $cat_name )
        {
            // Don't allow empty categories
            if ( empty( $cat_name ) )
                continue;

            // Check if there already exists such a category
            $_cat = get_terms( 'category', array(
                'name'          => $cat_name,
                'fields'        => 'ids',
                'hide_empty'    => false,
            ) );                

            // The category name already exists
            if ( ! empty( $_cat ) )
            {
                // Collect the (first) category id
                $clean_cats[] = intval( $_cat[0] );
            } 
            else 
            {
                // Create the category, since it doesn't exists
                $cat_id = wp_create_category( $cat_name );

                // Collect the category id
                if( $cat_id > 0 )
                    $clean_cats[] = $cat_id;
            }
        }
        // Current post's category IDs
        $cats = (array) wp_get_post_categories( $post_ID, array( 'fields' => 'ids' ) );

        // Unique array of category IDs
        $post_categories = array_unique( array_merge( $cats, $clean_cats ) );           

        // Assign the categories to the current post    
        wp_set_post_categories( $post_ID, $post_categories );
    }   
}

Previous Answer:

Here's my Friday answer, so it might need some testing ;-)

I just re-registered the category taxonomy as non-hierarchical with:

        'hierarchical' => false,

Then the category box showed up like this:

category

and saving terms worked as expected.

Here's my testing code snippet, so you can try it further:

add_action( 'init', function()
{
    global $wp_rewrite;
    register_taxonomy( 'category', 'post', array(
        'hierarchical' => false,
        'query_var' => 'category_name',
        'rewrite' =>  array(
            'hierarchical' => true,
            'slug'         => get_option('category_base') ? get_option('category_base') : 'category',
            'with_front'   => ! get_option('category_base') || $wp_rewrite->using_index_permalinks(),
            'ep_mask'      => EP_CATEGORIES,
        ),
        'public' => true,
        'show_ui' => true,
        'show_admin_column' => true,
        '_builtin' => true,
        'labels' => array(
            'name'                          => __( 'Categories' ),
            'singular_name'                 => __( 'Category' ),
            'search_items'                  => __( 'Search Categories' ),
            'popular_items'                 => null,
            'all_items'                     => __( 'All Categories' ),
            'edit_item'                     => __( 'Edit Category' ),
            'update_item'                   => __( 'Update Category' ),
            'add_new_item'                  => __( 'Add New Category' ),
            'new_item_name'                 => __( 'New Category Name' ),
            'separate_items_with_commas'    => null,
            'add_or_remove_items'           => null,
            'choose_from_most_used'         => null,
        ),
        'capabilities' => array(
            'manage_terms' => 'manage_categories',
            'edit_terms'   => 'manage_categories',
            'delete_terms' => 'manage_categories',
            'assign_terms' => 'edit_posts',
        ),
    ) );
} );
  • Oh, no! (I commented this under an answer prior to yours--the answer was deleted.) If I wanted a non-hierarchical taxonomy I'd simply have registered a custom taxonomy. I want category to stay hierarchical but have a tags-like metabox. Should probably have been more clear in my question I guess. – its_me Sep 12 '15 at 3:15
  • ok, I guess I misread your question, I thought your category terms had no children ;-) I updated the answer with a skeleton. @its_me – birgire Sep 12 '15 at 15:28
  • Thank you! I will now see if I can get it to work (with some edits if necessary); and get back to you soon to confirm if I am doing it the right way. – its_me Sep 12 '15 at 15:31
  • I was wondering... did you actually mean /wp-admin/includes/post.php when you referred to /wp-admin/post.php (in your answer)? Because the latter didn't seem much relevant. – its_me Sep 12 '15 at 16:07
  • I just mentioned /wp-admin/post.php because that's where the journey starts (this is in the form's action), then we follow the editpost case and there's the edit_post() call. But the edit_post() function is defined in /wp-admin/includes/post.php where the $_POST data (aka $post_data) is handled before calling wp_update_post(). There we can see how it handles the post_category and tax_input data. The terms are then saved/added within the wp_insert_post() function @its_me – birgire Sep 12 '15 at 16:19
2

How do the 'tag' and 'category' (default) taxonomies do 'save_post' action?

They don't!

Taxonomies aren't handled with a save_post action, it all happens directly inside wp_insert_post. You can see it in source here.

You can use wp_set_object_terms in your callback to save terms from your custom metabox.

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.