Thumbnails for Taxonomy Elements (WP_Term_Image)

In this post I'm going to share the code that adds the ability to set thumbnails for taxonomy elements, both built-in (tags, categories) and custom. The code is tested and seven times improved - all for you, my dears smile

To add, or download and add, or change the image of taxonomy element, you need to click on the image itself - it is a button. When you load it, the standard WP media window opens and the picture is uploaded to the WordPress media library. But unlike pictures of entries, it's not attached to a term - it's not attached to anything and that's a minus. Well, for now.

By default, the code works for all public taxonomies (categories, tags, and existing arbitrary taxonomies). You can set specific taxonomies in the code for which it should work.

This is what we end up with:

There are a lot of old and shoddy manuals on the web on this topic. In some of them data are saved to options table, not to metadata. Recall that metadata for taxonomy items appeared since WP 4.4 and all term data should be saved there, because it is correct and convenient.

Code

GitHub
<?php

/**
 * Ability to upload images for terms (elements of taxonomies: categories, labels).
 *
 * See here for an example of how to use it: https://github.com/doiftrue/Term_Meta_Image
 *
 * @author Kama (wp-kama.ru)
 *
 * @version 3.5
 */

namespace Kama;

class WP_Term_Image {

	/**
	 * Default parameters that can be changed during class initialization.
	 *
	 * @var array
	 */
	private static $args = [

		// For which taxonomies to include code. The default is for all public ones.
		'taxonomies' => [],

		// URL of the empty image
		'noimage_src' => "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 1000 1000'%3E%3Cpath fill='%23bbb' d='M430 660 l0 -90 -90 0 -90 0 0 -70 0 -70 90 0 90 0 0 -85 0 -85 70 0 70 0 0 85 0 85 90 0 90 0 0 70 0 70 -90 0 -90 0 0 90 0 90 -70 0 -70 0 0 -90z'%3E%3C/path%3E%3C/svg%3E",
	];

	/**
	 * The name of the meta key for the term where the ID of the attachment is stored.
	 *
	 * @var string
	 */
	private static $meta_key = '_thumbnail_id';

	/**
	 * The name of the meta key for the attachment post (label which term the picture belongs to).
	 *
	 * @var string
	 */
	private static $attach_meta_key = 'image_of_term';

	/**
	 * @param array|string $args String can be passed when call it from WP hook directly.
	 *                           See: self::$args.
	 *
	 * @return WP_Term_Image
	 */
	public static function init( $args = [] ){
		static $inst;

		$inst || $inst = new self( (array) $args );

		return $inst;
	}

	private function __construct( array $args = [] ){

		self::$args = $args + self::$args;

		$taxes = self::$args['taxonomies'] ?: get_taxonomies( [ 'public' => true ], 'names' );

		foreach( $taxes as $taxname ){

			add_action( "{$taxname}_add_form_fields", [ $this, '_add_term__image_fields' ], 10 );
			add_action( "{$taxname}_edit_form_fields", [ $this, '_update_term__image_fields' ], 10, 2 );
			add_action( "created_{$taxname}", [ $this, '_create_term__handler' ], 10, 2 );
			add_action( "edited_{$taxname}", [ $this, '_updated_term__handler' ], 10 );

			// table columns
			add_filter( "manage_edit-{$taxname}_columns", [ $this, '_add_image_column' ] );
			add_filter( "manage_{$taxname}_custom_column", [ $this, '_fill_image_column' ], 10, 3 );
		}

	}

	/**
	 * @param int|\WP_Term $term The term which image you want to get.
	 *
	 * @return int 0 if no image.
	 */
	public static function get_image_id( $term ){

		return (int) get_term_meta( is_object( $term ) ? $term->term_id : $term, self::$meta_key, true );
	}

	/**
	 * Fields at term creation.
	 *
	 * @param string $taxonomy
	 *
	 * @return void
	 */
	public function _add_term__image_fields( $taxonomy ){

		// add the media styles, if they are not present
		wp_enqueue_media();

		add_action( 'admin_print_footer_scripts', [ $this, '_add_script' ], 99 );
		$this->_css();
		?>
		<div class="form-field term-group">
			<label><?php _e( 'Image', 'default' ) ?></label>

			<div class="term__image__wrapper">
				<a class="termeta_img_button" href="#">
					<img width="100" height="100" alt="" src="<?= self::$args['noimage_src'] ?>">
				</a>
				<input type="button" class="button button-secondary termeta_img_remove_js"
				       value="<?php _e( 'Remove', 'default' ) ?>"/>
			</div>

			<input type="hidden" id="term_imgid" name="term_imgid" value="">
		</div>
		<?php
	}

	/**
	 * Fields when editing a term.
	 *
	 * @param \WP_Term $term
	 * @param string   $taxonomy
	 *
	 * @return void
	 */
	public function _update_term__image_fields( $term, $taxonomy ){

		wp_enqueue_media();

		add_action( 'admin_print_footer_scripts', [ $this, '_add_script' ], 99 );

		$image_id = self::get_image_id( $term );

		$image_url = $image_id
			? wp_get_attachment_image_url( $image_id, 'thumbnail' )
			: self::$args['noimage_src'];

		$this->_css();
		?>
		<tr class="form-field term-group-wrap">
			<th scope="row"><?php _e( 'Image', 'default' ) ?></th>
			<td>
				<div class="term__image__wrapper">
					<a class="termeta_img_button" href="#">
						<?= '<img src="' . $image_url . '" alt="">' ?>
					</a>
					<input type="button" class="button button-secondary termeta_img_remove_js"
					       value="<?php _e( 'Remove', 'default' ) ?>"/>
				</div>

				<input type="hidden" id="term_imgid" name="term_imgid" value="<?= $image_id ?>">
			</td>
		</tr>
		<?php
	}

	private function _css(){
		?>
		<style>
			.termeta_img_button{ display:inline-block; margin-right:1em; }
			.termeta_img_button img{ display:block; float:left; margin:0; padding:0;
				width:100px; height:100px;
				background:rgba(0, 0, 0, .07);
			}
			.termeta_img_button:hover img{ opacity:.8; }
			.termeta_img_button:after{ content:''; display:table; clear:both; }
		</style>
		<?php
	}

	public function _add_script(){

		// выходим если не на нужной странице таксономии
		//$cs = get_current_screen();
		//if( ! in_array($cs->base, array('edit-tags','term')) || ! in_array($cs->taxonomy, (array) $this->for_taxes) )
		//  return;

		$title = __( 'Featured Image', 'default' );
		$button_txt = __( 'Set featured image', 'default' );
		?>
		<script>
		jQuery( document ).ready( function( $ ){
			let frame
			let $imgwrap = $( '.term__image__wrapper' )
			let $imgid = $( '#term_imgid' )

			// добавление
			$( '.termeta_img_button' ).click( function( ev ){
				ev.preventDefault();

				if( frame ){
					frame.open();
					return;
				}

				// задаем media frame
				frame = wp.media.frames.questImgAdd = wp.media( {
					states: [
						new wp.media.controller.Library( {
							title   : '<?= $title ?>',
							library : wp.media.query( { type: 'image' } ),
							multiple: false
							//date:   false
						} )
					],
					button: {
						text: '<?= $button_txt ?>' // Set the text of the button.
					}
				} );

				// выбор
				frame.on( 'select', function(){
					let selected = frame.state().get( 'selection' ).first().toJSON();
					if( selected ){
						$imgid.val( selected.id );
						let src = selected.sizes.thumbnail ? selected.sizes.thumbnail.url : selected.url
						$imgwrap.find( 'img' ).attr( 'src', src );
					}
				} );

				// открываем
				frame.on( 'open', function(){
					if( $imgid.val() ) frame.state().get( 'selection' ).add( wp.media.attachment( $imgid.val() ) );
				} );

				frame.open();
			} );

			// удаление
			$( '.termeta_img_remove_js' ).click( function(){
				$imgid.val( '' );
				$imgwrap.find( 'img' ).attr( 'src', '<?= str_replace( "'", "\'", self::$args['noimage_src'] ) ?>' );
			} );
		} );
		</script>
		<?php
	}

	// Adds a image column to the term table
	public function _add_image_column( $columns ){

		// fix column width
		add_action( 'admin_notices', function(){
			echo '<style>.column-image{ width:50px; text-align:center; }</style>';
		} );

		// column without name
		return array_slice( $columns, 0, 1 ) + [ 'image' => '' ] + $columns;
	}

	public function _fill_image_column( $string, $column_name, $term_id ){

		if( 'image' === $column_name ){
			$image_id = self::get_image_id( $term_id );

			$string = $image_id
				? sprintf( '<img src="%s" width="50" height="50" alt="" style="border-radius:4px;" />',
					wp_get_attachment_image_url( $image_id, 'thumbnail' ) )
				: '';
		}

		return $string;
	}

	// Save the form field
	public function _create_term__handler( $term_id, $tt_id ){

		if( isset( $_POST['term_imgid'] ) && $attach_id = (int) $_POST['term_imgid'] ){
			update_term_meta( $term_id, self::$meta_key, $attach_id );

			self::up_attach_term_id( $attach_id, 'add', $term_id );
		}
	}

	// Update the form field value
	public function _updated_term__handler( $term_id ){

		if( ! isset( $_POST['term_imgid'] ) ){
			return;
		}

		$old_attach_id = self::get_image_id( $term_id );

		$attach_id = (int) $_POST['term_imgid'];

		// update metas
		if( $attach_id ){
			update_term_meta( $term_id, self::$meta_key, $attach_id );

			self::up_attach_term_id( $attach_id, 'add', $term_id );
		}
		else{
			delete_term_meta( $term_id, self::$meta_key );
		}

		// delete attachment
		$old_attach = get_post( $old_attach_id );
		if( $old_attach && (int) $old_attach_id !== (int) $attach_id ){

			$old_attach_term_ids = self::up_attach_term_id( $old_attach_id, 'remove', $term_id );

			// Вложение не прикреплено к посту или оно прикреплено к другому термину
			if( ! $old_attach_term_ids && ! $old_attach->post_parent ){
				wp_delete_attachment( $old_attach_id );
			}
		}

	}

	/**
	 * Updates post-meta field of specified attachment (post) - adds or removes term id from the field.
	 *
	 * @param int    $attach_id
	 * @param string $action    add|remove.
	 * @param int    $term_id   Term id to add/remove to attachment.
	 *
	 * @return array term ids updated fo attachment.
	 */
	private static function up_attach_term_id( $attach_id, $action, $term_id ){

		$term_ids = wp_parse_list( get_post_meta( $attach_id, self::$attach_meta_key, true ) );

		// add
		if( 'add' === $action ){
			$term_ids[] = $term_id;
		}
		// remove
		else {
			$term_ids = array_diff( $term_ids, [ $term_id ] );
		}

		// join - save as: ,12,34,54,
		$term_ids = array_unique( (array) $term_ids );
		$term_ids = array_map( 'sanitize_text_field', $term_ids );
		$joined_term_ids = $term_ids ? ','. implode( ',', $term_ids ) .',' : '';

		update_post_meta( $attach_id, self::$attach_meta_key, $joined_term_ids );

		return $term_ids;
	}

}

Usage

Connection

Include php file WP_Term_Image.php:

require_once __DIR__ . '/WP_Term_Image.php';

Or use Composer:

composer require doiftrue/wp_term_image
Initialization

Basic without parameter transfer:

add_action( 'admin_init', '\\Kama\\WP_Term_Image::init' );

With parameter transfer:

add_action( 'admin_init', 'kama_wp_term_image' );

function kama_wp_term_image(){

	// Let's specify for which taxonomy it should works.
	// It is possible not to specify, then the possibility will be
	// automatically added for all public taxonomies.

	\Kama\WP_Term_Image::init( [
		'taxonomies' => [ 'post_tag' ],
	] );
}

Displaying an image of a taxonomy element in the frontend

We have the background to add a picture, now we need to figure out how to get such pictures of the term in the frontend.

This is done through a native WordPress function get_term_meta(), which I remind you again exists since version 4.4.

The ID of pictures (attachments) is stored in the meta field of the term _thumbnail_id, it will be obtained.

// Get the term ID on the term page
$term_id = get_queried_object_id();

// get the picture ID from the meta-field of the term
$image_id = get_term_meta( $term_id, '_thumbnail_id', 1 );

// link to the full picture size by attachment ID
$image_url = wp_get_attachment_image_url( $image_id, 'full' );

// Display the picture on the screen
echo '<img src="'. $image_url .'" alt="" />';

If you don't want full size, change full to your_size in wp_get_attachment_image_url(). If you want other image data, use another function to get attachment ID.

Plugins for inserting taxonomy thumbnails

There are other plugins out there:

  • Taxonomy Thumbnail - everything is cool by code, but there is a lot of it. Supports WP versions up to 4.4 (when taxonomy metadata table was added)...

  • WP Multiple Taxonomy Images - the plugin code is very similar to the code from this article - there's not much, just what you need. The trick of the plugin is that it allows you to set several thumbnails for the term.

  • Taxonomy Images is the highest rated plugin on this subject. The plugin as a whole is not bad, but the code is a lot, "unnecessary" a lot, it seems. If you do not need extra, better use previous plugins or code from this article.