Creating a Lightweight Media Tags Plugin with a Custom Taxonomy

Sadly, I haven’t found the flaw in your code as far as update counts. Copy and paste mine for comparison (stick it in a plugin you can easily deactivate). This works like a charm, with all counts updating correctly:

class ZGAttachmentTags {
    const SLUG = 'attachment-tags';

    function __construct() {
        add_action( 'init', array( $this, 'register_custom_taxonomy' ) );
    }


    function register_custom_taxonomy() {
        register_taxonomy(
            self::SLUG,
            array( 'attachment' ),
            array(
                'label' => __( 'Media Tags' ),
                'labels' => array(
                  'name'=> __( 'Media Tags' ),
                  'singular_name'=> __( 'Media Tags' )
                ),
                'hierarchical' => false,
                'update_count_callback' => '_update_generic_term_count'
            )
        );
    }
}

$zg_attachment_tags_plugin = new ZGAttachmentTags();

Clearly I’m using a pared-down version of register_taxonomy, laving a lot of the defaults alone. Perhaps it’s one of your other parameters that’s causing the issue. Or I’m misunderstanding the question.

Regarding the “View” link – by default, taxonomy archive queries only include posts whose status are public (usually ‘publish’ and ‘private’ – if your user is logged in). Attachments don’t get published – they received the ‘inherit’ status. The solution then is to modify either the query or the request to include posts with the status of ‘inherit’.

The simplest way to do this is to use the parse_query filter, which is run after the query vars have all been parsed into the main query object, and just before the get_posts() method is run that ultimately generates the SQL request. You do that like this:

add_filter( 'parse_query', 'parse_query__attachment_status' );

function parse_query__attachment_status( $query_obj ){
    // Make sure we don't contaminate any other queries...
    if ( $query_obj->query['attachment-tags'] ){
        $statuses = get_post_stati( array( 'public' => true ) ) ;
        $statuses['inherit'] = 'inherit'; // push 'inherit' status onto the list
        $query_obj->set( 'post_status', array_keys( $statuses ) );
    }

    return $query_obj;
}

Note that for simplicity, I included this example as if it were a standalone piece of code in your functions.php for example. But you could (and should) roll it into your class and therefore use the class constant for the taxonomy slug (self::SLUG). Also don’t forget to modify your add_filter() call to use the array form.

The only problem with this approach (it’s not really a problem I guess) is that WordPress automatically does some funkiness when it detects that you’re querying an attachment. It will add a JOIN to wp_posts to query the parent post, and adds a clause to the WHERE clause that checks the parent’s post_status, as well as the child’s status.

This means that you might get some duplication if, in fact, some of the media items have been included in posts. One way to avoid this is to hard code the status as ‘inherit’ only (rather than using the show-off get_post_stati() approach that I used, primarily because I admire the use of the latin plural in a function name.).

The other thing you could do, if you’re brave at heart, is eschew the parse_query filter in favour of the posts_clauses filter, and rewrite the WHERE and JOIN sql yourself. This would take a bit of creative search-and-replace, and to me seemed like too much trouble for this answer, though it is undoubtedly the most correct way to go, and the most future-proof.

Leave a Comment