WordPress Attachment Category/Terms

I was working on a photography site project for a dear friend of mine, Virda, and it requires me to set categories/terms for the attachment images. By default, an attachment post will inherit its parent terms, but, what if we only need to pull attachments which have certain terms? Querying their parents and then filtering the result will surely need more time and resources don’t you think? Besides, we won’t have the freedom to set the attachments’ terms without modifying their parent posts.

Update:
I’ve updated the code (fixes/cleanups, enhancements) and create a complete WordPress plugin for it with i18n support. You can download it here. I’ll submit this plugin to the repository and will update this post when it’s live.

Update 2:
I’ve created a plugin for this so you won’t need to edit your theme/plugin files anymore :) Download it from WordPress plugins repo.

So I searched for a solution on the interweb and found the closest one to my liking at WP StackExchange which is now available as a plugin. Unfortunately, this solution needs javascript (jQuery) which in my opinion should be avoided for this kind of things. So I began learning the code and modify it to suit my needs.

As usual, you can add these codes to your theme’s function.php file, or you can create a new php file and place it inside the wp-content/mu-plugins directory (create it if it doesn’t exist yet). The second method is preferable so you’ll still have this functionality no matter what theme you’re using.

OK, let’s get started.

Register attachment taxonomies

function kc_attachment_taxonomies() {
	$taxonomies = array( 'category', 'post_tag' );
	foreach ( $taxonomies as $tax )
		register_taxonomy_for_object_type( $tax, 'attachment' );
}
add_action( 'init', 'kc_attachment_taxonomies' );

The code above will register category and post_tag taxonomies for the attachment. You can add more taxonomies to the array if you like, but you’ll have to register them beforehand.

Note:
You can also hook kc_attachment_taxonomies function into admin_init to reduce function calls when WordPress is loaded on the frontend, but you won’t be able to get any result when calling get_attachment_taxonomies() on the frontend.

Attachment fields

function kc_attachment_fields_to_edit( $fields, $post ) {
	$taxonomies = get_attachment_taxonomies( $post );
	if ( empty($taxonomies) )
		return $fields;

	foreach ( $taxonomies as $tax ) {
		$tax_object = (array) get_taxonomy( $tax );
		if ( !$tax_object['public'] )
			continue;
		if ( empty($tax_object['label']) )
			$tax_object['label'] = $tax;
		if ( empty($t['args']) )
			$tax_object['args'] = array();

		$att_terms = array();
		$post_terms = get_object_term_cache( $post->ID, $tax );
		if ( empty($post_terms) )
			$post_terms = wp_get_object_terms( $post->ID, $tax, $tax_object['args'] );
		if ( !empty($post_terms) )
			foreach ( $post_terms as $post_term )
				$att_terms[$post_term->term_id] = $post_term->name;

		$tax_terms = get_terms( $tax, array('hide_empty' => false) );

		$html = "<ul class='attachment-terms-list'>\n";

		if ( !empty($tax_terms) )
			foreach ( $tax_terms as $term )
				$html .= "\t<li><label><input type='checkbox' name='attachments[{$post->ID}][{$tax}][]' value='{$term->name}' ".checked(array_key_exists($term->term_id, $att_terms), true, false)." /> {$term->name}</label></li>\n";

		$html .= "\t<li><input type='text' name='attachments[{$post->ID}][{$tax}][]' /></label></li>\n";
		$html .= "</ul>\n";


		$fields[$tax]['input'] = 'html';
		$fields[$tax]['html'] = $html;
		$fields[$tax]['helps'] = __( sprintf('Check/uncheck existing %s, or add new one(s), separated by commas.', $fields[$tax]['label']) );
	}

	return $fields;
}
add_filter( 'attachment_fields_to_edit', 'kc_attachment_fields_to_edit', 10, 2 );

By default, when we registered a taxonomy for the attachment post_type, WordPress will add an input field in the attachment form. We can then fill it with the term names (separated by commas) and WordPress will set them for the attachment. This is not cool, because we’ll have to remember exactly the term names we want to set for the attachment, otherwise, a new term will be created each time we made a typo :)

The function above will create a list of existing term names from each taxonomy and also an empty input field in case you need to add a new term.

Attachment form
Attachment form with term list

Attachment data

function kc_attachment_fields_data() {
	if ( empty($_POST['attachments']) )
		return;

	foreach ( $_POST['attachments'] as $id => $data ) {
		$taxonomies = get_attachment_taxonomies( $id );
		if ( empty($taxonomies) )
			continue;

		foreach ( $taxonomies as $tax )
			if ( isset($data[$tax]) )
				$_POST['attachments'][$id][$tax] = trim( join(',', $data[$tax]) );
	}
}
add_action( 'init', 'kc_attachment_fields_data' );

The last step is to intercept the posted data when we save the attachment. When saving the attachment data, WordPress expects the terms data as a string which will be converted to an array. But, because we’ve already modified the field, the terms data is sent as an array. I tried to find a filter hook to modify the data, but unfortunately it’s not available (yet), so we need to use the init hook to modify it. Don’t worry though, the function will exit as soon as it found out that the $_POST data doesn’t contain what it needs, so it won’t waste your server’s CPU cycle :)

kc_attachment_fields_data‘s job is pretty simple; take each attachment’s term data for each taxonomy and convert it to a string by separating each term with a comma, and then give it back to $_POST so WordPress can use it when updating the attachments.

Done!

Usage

To pull attachments from certain categories/terms, we can use what we usually use to pull ordinary posts/pages, either query_posts, get_posts, or even better, WP_Query class. To keep it simple, we’ll use get_posts, for the example:

$images = get_posts( array('post_type' => 'attachment', 'category__in' => array(1,5,6,7))  );
if ( !empty($images) ) {
	foreach ( $images as $image ) {
		echo wp_get_attachment_image($image->ID).'<br />';
		echo $image->post_title .'<br />';
	}
}

That should give you some attachment images that are member of categories 1, 5, 6 and 7 (if you have any, off course).

What’s next?

I’m not quite done with this. I think it’d be sweet if I could set which taxonomy should be registered for certain file types (which should be pretty simple to do I guess). I may also come up with other new ideas along the way, but for now I think it’s more than enough :)

14 thoughts on “WordPress Attachment Category/Terms”

  1. Piece of art!!!

    Im using more taxonomies plugin to create a taxonomy named: color.

    Then in my custom plugin im using:

    function custom_attachment_taxonomies() {
      $taxonomies = array( 'category', 'color' );
      foreach ( $taxonomies as $tax )
        register_taxonomy_for_object_type( $tax, 'attachment' );
    }
    add_action( 'init', 'custom_attachment_taxonomies' );
    

    But only category taxonomy is printed.

    How can I add and manage new custom hierarchical taxonomies?

    Thanks in advance.

    1. Thank you for trying out the plugin Josorama.

      I just tested the More Taxonomies plugin, and unfortunately, it doesn’t return a boolean value for the public variable. I think your best bet for now is to manually register the taxonomies you want to use for the attachment post type.

  2. Great, but only works for me if it is standart wordpress category. If it is my custom taxonomy – can’t get results. And in WP_Query doesn’t work at all. WordPress 3.3

    Thanks in advance.

    1. Hi Tomasz,

      Do you use the code or the plugin? Either way, you need to register your custom taxonomies before priority 13 in the init hook. Also, what do you mean by not working in WP_Query? Could you paste here your query code?

      I actually will retire this plugin and replace it with KC Essentials. If you want to help me test it, please donwload it from here. This plugin depends on KC Settings, so you will need to install and activate it first. When KC Essentials is activated, go to Settings » KC Essentials, activate the components, and check the taxonomies you want to use for the attachments.

  3. Hi! Thanks for fast response.

    I’m using the plugin.

    I have only modified the code that at Media Manager instead of checkboxes it shows me a select input (because it require less space).

    When I’m at Media Manager I can assign images to the taxonomy and to the standart category.

    Your code works only for standart categories.

    $images = get_posts( array('post_type' => 'attachment', 'category__in' => array(1,5,6,7))  );
    

    Standart Loop for WP_Query to check if I get titles of images.

    while ( $query->have_posts() ) : $query->the_post();
    	the_title(); // title of image
    endwhile;
    

    Below code works only for ‘not attachment’, so, it shows nothing.

    $query = new WP_Query( array( 'post_type' => 'attachment' ) );
    

    This also not working for attachments with WP_Query

    $args = array(
    	'tax_query' => array(
    		array(
    			'taxonomy' => 'realizacje',
    			'field' => 'slug',
    			'terms' => 'interior'
    		)
    	)
    );
    

    So I’m gonna try KC Essentials.

    1. Oh, I forgot to mention this. When using WP_Query to get attachments, you need to add the inherit post_status in the arguments.

      Let me know about your experiment with KC Essentials & KC Settings.

    2. Big thanks!

      With this code works perfectly!

      $args = array(
      	'tax_query' => array(
      		array(
      			'taxonomy' => 'custom_taxonomy',
      			'field' => 'slug',
      			'terms' => 'custom_taxonomy_term'
      		)
      	),
      	'post_type' => 'attachment', 
      	'post_status' => 'inherit'
      );
      
    3. Unfortunately when I installed KC Essentials and I want to save images with selected terms I get this

      Warning: preg_split() expects parameter 2 to be string, array given in /wp-admin/includes/media.php on line 462
      
      Warning: array_map() [function.array-map]: Argument #2 should be an array in /wp-admin/includes/media.php on line 462
      
      1. Thanks for the report Tomasz. I will investigate it later. Since the old plugin is still working, please keep using it for now.
        Oh, don’t forget to try the other components in KC Essentials if you have free time :)

  4. This plugin is Fantastic!

    But I’ll just give you an idea … does not have to pick the category of the post automatically without having to enter each image and edit them one by one?

    I’ll take some research here and find something or get to do what I said above, make available to you. =D

    But anyway, thank you … It has helped a lot!

    1. Hmm, not sure what you meant by that, but let me know when you make some progress with your research :)

      1. I’ll explain better now …
        put the same categories in the post all attachments … so when I upload an attachment he automatically select categories

  5. I’m trying create a WP cloud tag but WordPress 3.5 only returns tags assigned to a post, not to attachments

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>