Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

How to Add Custom Fields to Attachments

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

You should be familiar with custom fields in WordPress. We use them on a post or a page to add extra data. In WordPress attachments are also saved as posts, so custom fields are available for them too.

Today we'll see how we can add some custom fields so that attachments can carry more information than just the default data.


What We'll Do

First of all, we are going to create a plugin to handle the attachments custom fields. It will get a set of options, bake them so they become part of the form when we edit an attachment, and save them into the database.

For this, we will use two WordPress hooks:


1. Create the Plugin

I will pass quickly on this part as it's not the main purpose of this tutorial.

Create a new folder in the plugins directory (wp-content/plugins/media-fields/ for example) and put a file (named plugin.php) inside. Let's also put a file called custom_media_fields.php which will hold our options.

This is what your plugin.php file should look like at first:

/*
Plugin Name: Wptuts+ Custom Media Fields
Plugin URI:
Description: Create attachments custom fields
Version: 0.1
Author: Guillaume Voisin
Author URI: http://wp.tutsplus.com/author/guillaumevoisin
License: GPL2
*/

require_once( plugin_dir_path( __FILE__ ) . '/custom_media_fields.php' );

Class Wptuts_Custom_Media_Fields {

	private $media_fields = array();

	function __construct( $fields ) {

	}

	public function applyFilter( $form_fields, $post = null ) {

	}

	function saveFields( $post, $attachment ) {

	}

}

$cmf = new Wptuts_Custom_Media_Fields( $attchments_options );

This is the base we'll populate in the following sections. For now, let's define our set of options.


2. Define Our Options

In the other file, let's add some options to enhance the attachment edit form. We'll consider, for this tutorial, options to improve images. For instance, we'll add copyright, author description, watermark, rating, and image disposition fields.

$themename = "twentytwelve";
$attchments_options = array(
	'image_copyright' => array(
		'label'       => __( 'Image copyright', $themename ),
		'input'       => 'text',
		'helps'       => __( 'If your image is protected by copyrights', $themename ),
		'application' => 'image',
		'exclusions'  => array( 'audio', 'video' ),
		'required'    => true,
		'error_text'  => __( 'Copyright field required', $themename )
	),
	'image_author_desc' => array(
		'label'       => __( 'Image author description', $themename ),
		'input'       => 'textarea',
		'application' => 'image',
		'exclusions'   => array( 'audio', 'video' ),
	),
	'image_watermark' => array(
		'label'       => __( 'Image watermark', $themename ),
		'input'       => 'checkbox',
		'application' => 'image',
		'exclusions'   => array( 'audio', 'video' )
	),
	'image_stars' => array(
		'label'       => __( 'Image rating', $themename ),
		'input'       => 'radio',
		'options' => array(
			'0' => 0,
			'1' => 1,
			'2' => 2,
			'3' => 3,
			'4' => 4
		),
		'application' => 'image',
		'exclusions'   => array( 'audio', 'video' )
	),
	'image_disposition' => array(
		'label'       => __( 'Image disposition', $themename ),
		'input'       => 'select',
		'options' => array(
			'portrait' => __( 'portrtait', $themename ),
			'landscape' => __( 'landscape', $themename )
		),
		'application' => 'image',
		'exclusions'   => array( 'audio', 'video' )
	)
);

It is basically an associative array which contains these parameters:

  • label - the field name that will be displayed
  • input - the input type (e.g text, select, radio, ...)
  • helps - information to help the user filling in the field
  • application - which attchment mime type to apply
  • exclusions - which attchment mime type to exclude
  • required - is the field required? (default false)
  • error_text - optional field to describe the error (if required is set to true)
  • options - optional field for radio and select types
  • show_in_modal - whether to show the field in modal or not (default true)
  • show_in_edit - whether to show the field in classic edit view or not (default true)
  • extra_rows - additional rows to display content (within the same "tr" tag)
  • tr - additional rows ("tr" tag)

The highlitghted options represent options we will manually deal with whereas others are default ones WordPress will process automatically.

As we are dealing with images, we set the application parameter to "image". It will actually apply to all kinds of images whose mime type starts with "image" such as image/jpeg, image/png and so on. You could exlude the gif mime type by setting it in the exclusions field for example.

Now our options are set, let's dig into the hooks.


3. The Hooks

As mentionned earlier, we'll deal with two hooks.

We bind our two functions to those hooks in the constructor method.

function __construct( $fields ) {
	$this->media_fields = $fields;

	add_filter( 'attachment_fields_to_edit', array( $this, 'applyFilter' ), 11, 2 );
	add_filter( 'attachment_fields_to_save', array( $this, 'saveFields' ), 11, 2 );
}

Now let's see those hooks in detail.

attachment_fields_to_edit

It has two parameters:

  1. $form_fields - An array of fields contained in the attachment edit form
  2. $post - Object which represents the attachment itself

We will use the $form_fields parameter to merge our own fields and check each one of them against attachment requirements (mime type for instance).

public function applyFilter( $form_fields, $post = null ) {
	// If our fields array is not empty
	if ( ! empty( $this->media_fields ) ) {
		// We browse our set of options
		foreach ( $this->media_fields as $field => $values ) {
			// If the field matches the current attachment mime type
			// and is not one of the exclusions
			if ( preg_match( "/" . $values['application'] . "/", $post->post_mime_type) && ! in_array( $post->post_mime_type, $values['exclusions'] ) ) {
				// We get the already saved field meta value
				$meta = get_post_meta( $post->ID, '_' . $field, true );

				// Define the input type to 'text' by default
				$values['input'] = 'text';

				// And set it to the field before building it
				$values['value'] = $meta;

				// We add our field into the $form_fields array
				$form_fields[$field] = $values;
			}
		}
	}

	// We return the completed $form_fields array
	return $form_fields;
}

At this step, you should have your attachment edit form enhanced with the new fields we've added. But they will look like text inputs. We now have to consider different kinds of inputs (radio, checkbox, etc...).

So let's edit our function to handle this. Replace the $values['input'] = 'text'; with the following code:

switch ( $values['input'] ) {
	default:
	case 'text':
		$values['input'] = 'text';
		break;

	case 'textarea':
		$values['input'] = 'textarea';
		break;

	case 'select':

		// Select type doesn't exist, so we will create the html manually
		// For this, we have to set the input type to 'html'
		$values['input'] = 'html';

		// Create the select element with the right name (matches the one that wordpress creates for custom fields)
		$html = '<select name="attachments[' . $post->ID . '][' . $field . ']">';

		// If options array is passed
		if ( isset( $values['options'] ) ) {
			// Browse and add the options
			foreach ( $values['options'] as $k => $v ) {
				// Set the option selected or not
				if ( $meta == $k )
					$selected = ' selected="selected"';
				else
					$selected = '';

				$html .= '<option' . $selected . ' value="' . $k . '">' . $v . '</option>';
			}
		}

		$html .= '</select>';

		// Set the html content
		$values['html'] = $html;

		break;

	case 'checkbox':

		// Checkbox type doesn't exist either
		$values['input'] = 'html';

		// Set the checkbox checked or not
		if ( $meta == 'on' )
			$checked = ' checked="checked"';
		else
			$checked = '';

		$html = '<input' . $checked . ' type="checkbox" name="attachments[' . $post->ID . '][' . $field . ']" id="attachments-' . $post->ID . '-' . $field . '" />';

		$values['html'] = $html;

		break;

	case 'radio':

		// radio type doesn't exist either
		$values['input'] = 'html';

		$html = '';

		if ( ! empty( $values['options'] ) ) {
			$i = 0;

			foreach ( $values['options'] as $k => $v ) {
				if ( $meta == $k )
					$checked = ' checked="checked"';
				else
					$checked = '';

				$html .= '<input' . $checked . ' value="' . $k . '" type="radio" name="attachments[' . $post->ID . '][' . $field . ']" id="' . sanitize_key( $field . '_' . $post->ID . '_' . $i ) . '" /> <label for="' . sanitize_key( $field . '_' . $post->ID . '_' . $i ) . '">' . $v . '</label><br />';
				$i++;
			}
		}

		$values['html'] = $html;

		break;
}

Now, we can build common HTML elements. Let's check out our attachment edit form. It should look just like this:

Attachment edit form with custom fields
Attachment edit form with custom fields

The custom fields, depending on whether you set their modal options to true or not, will also appear in the media modal form when you edit a post.

Custom fields in modal

Custom fields in modal

Now our fields are displayed in our attachment edit form, we have to save them in the database. For this, we are going to use the second hook.

attachment_fields_to_save

This hook also has two parameters:

  1. $post - array which represents the attachment entity
  2. $attachment - contains all fields attached to the attachment post

Now, let's fill the function saveFields we left in the previous section.

function saveFields( $post, $attachment ) {
	// If our fields array is not empty
	if ( ! empty( $this->media_fields ) ) {
		// Browser those fields
		foreach ( $this->media_fields as $field => $values ) {
			// If this field has been submitted (is present in the $attachment variable)
			if ( isset( $attachment[$field] ) ) {
				// If submitted field is empty
				// We add errors to the post object with the "error_text" parameter we set in the options
				if ( strlen( trim( $attachment[$field] ) ) == 0 )
					$post['errors'][$field]['errors'][] = __( $values['error_text'] );
				// Otherwise we update the custom field
				else
					update_post_meta( $post['ID'], '_' . $field, $attachment[$field] );
			}
			// Otherwise, we delete it if it already existed
			else {
				delete_post_meta( $post['ID'], $field );
			}
		}
	}

	return $post;
}

Ok, now our custom fields are saved into the database and will be available for the front-end.

  • Please be careful when manipulating the post parameter in both hooks. It is an object in the first one and an array in the second one.
  • Tip: the update_post_meta will create the meta if it doesn't exist.
  • Tip: We prefix the custom field key with an underscore "_" so that they won't be listed in the custom fields metaboxes on post edit pages.

Error Considerations

As of version 3.5, it seems that errors still don't appear on attachment edit forms. I tried to investigate the core code, and despite the fact it should have been fixed (http://core.trac.wordpress.org/ticket/13810), it seems not.

For the ajax save process, it is certain it's not done yet as mentionned in the ajax-actions.php file:

$errors = $post['errors']; // @todo return me and display me!

So right now, errors are not going to work properly, but the code is done so that when those bugs are fixed, it'll work.


Front End

To use those custom fields in your templates, you just have to retrieve post metas the same way you would for regular posts. Don't forget to add the "_" prefix to the custom fields' keys.

For instance, you could do like so:

echo "<ul>";
echo "	<li><strong>Copyright</strong>: " . get_post_meta( get_the_ID(), '_image_copyright', true ) . "</li>";
echo "	<li><strong>Rating</strong>: " . get_post_meta( get_the_ID(), '_image_stars', true ) . "</li>";
echo "	<li><strong>Author description</strong>: " . get_post_meta( get_the_ID(), '_image_author_desc', true ) . "</li>";
echo "	<li><strong>Image disposition</strong>: " . get_post_meta( get_the_ID(), '_image_disposition', true ) . "</li>";
echo "	<li><strong>Watermark?</strong> " . ( get_post_meta( get_the_ID(), '_image_watermark', true ) == "on" ? "yes" : "no" ) . "</li>";
echo "</ul>";
Show custom fields on front-end

Show custom fields on front-end

Go Further

There are several points of improvements depending on your needs:

  • You could have your settings in the database so it becomes more flexible to add, edit, and remove them
  • You could have a default value that is set for all new attachments when a value is not set
  • You should set some style for the modal form so it displays the custom fields properly

Conclusion

Don't hesitate to share with us your ideas on how to improve this plugin and what you would expect from such functionality.

Advertisement