When you upload images into WordPress, it automatically creates smaller derivative images to serve as thumbnails. You have little control over these derivatives, aside from setting their maximum dimensions. If you upload any non-image media (e.g., a PDF, a video, an MP3, a tarball, etc.), you don’t get any thumbnail, for fairly obvious reasons.
Let’s say you want more control over your image thumbnails, or you would like to have thumbnails attached to your other uploads. You would need to install a plugin like the Custom Thumbnails plugin I just wrote.
What it does
WordPress associates each upload with a post or a page, using the item’s post_parent
attribute. This plugin adds a field to the editing screen for your upload, allowing you to associate it with a “Parent Item”. Once thus associated, any time WordPress requests the thumbnail image of the parent item, it will retrieve the thumbnail image you uploaded, instead.
How it works
Writing this plugin took me much farther into the bowels of WordPress than I had reached before. As it turns out, I needed to use four completely undocumented filters to achieve the effect I wanted.
attachment_fields_to_edit
The first step is to add the fields you want edit for your uploads. WordPress uses the function get_attachment_fields_to_edit
to build the form fields. You can use the filter attachment_fields_to_edit
to add or remove fields from this form. Your filter function needs to take two arguments, so add your filter using:
add_filter('attachment_fields_to_edit', 'custom_thumbnail_fields_to_edit', 11, 2);
The first argument is an associative array of form fields ($form_fields
in the following code), the second is the attachment object we are currently editing ($post
, since WordPress attachment objects are the same as post objects).
Fields for images
We add a select box to the form for image uploads. This select box will have a list of all other media files. So first we query the database for the list of files:
$attachments = get_children( array( 'post_type' => 'attachment', 'order' => 'ASC', 'orderby' => 'post_title', 'exclude' => $post->ID ) );
Next we start to build the HTML for our select box:
$name = "attachments[$post->ID][post_parent]"; $html = "<select id='$name' name='$name'>"; $html .= "<option value='0'></option>"; foreach ( $attachments as $attachment ) { $parent = get_post($attachment->post_parent); if ( $parent && $parent->post_type == 'attachment' ) { continue; } if ( $attachment->ID == $post->post_parent ) { $selected = " selected='selected'"; } else { $selected = ''; } $html .= "<option value='$attachment->ID'$selected>"; $option_text = array( $attachment->post_title, ' ('.basename($attachment->guid).')'); if ( strlen($option_text[1]) > 62 ) { $option_text[0] = '...'; } elseif ( strlen($option_text[0].$option_text[1]) > 65 ) { $option_text[0] = substr($option_text[0], 0, 62-strlen($option_text[1])).'...'; } $html .= $option_text[0].$option_text[1]; $html .= "</option>"; } $html .= "</select>";
We create an empty option, then an option for each media item, with its ID as the value. I limited the text of the option to 65 characters so it doesn’t overflow the item editing box.
Finally, we add our new field to the $form_fields
array:
$form_fields['post_parent'] = array( 'label' => __("Parent Item"), 'value' => '', 'input' => 'select', 'select' => $html, 'helps' => __("If this is a thumbnail for another image, choose that image here.") );
The array keys are somewhat self-explanatory. label
is the label, value
is the default value, input
can be one of text, textarea, or any arbitrary string. If you use an arbitrary string, you’ll use that string as a key for the HTML that will be printed for the form field (in this case, select
). helps
contains helpful text you want to include along with the field.
Fields for other media
Other media items cannot serve as thumbnails, so they don’t need the “Parent Item” field. They do, though, need the same options as images have for inserting your items into your post, including options to align the image and to display the thumbnail.
I copied the “Alignment” field directly from the image_attachment_fields_to_edit
function that adds the field to image items. The “Display” field took a bit of editing from the “Size” field in image_attachment_fields_to_edit
. First, we see if the item has a thumbnail image, using a function we’ll define later:
$thumb = custom_thumbnail_image_downsize(false, $post->ID, 'thumbnail');
Then we add the radio buttons to choose among the thumbnail (if it exists), the text from the “Title” field, or the media icon. (“Media icon?”, you ask. Yes, WordPress comes with nine icons for representing various kinds of media files (e.g., audio, video, spreadsheets, documents, etc.). It doesn’t give you any easy way to use these icons anywhere except on the Media Library page.)
Finally, return $form_fields
and close the function.
attachment_fields_to_save
We now need to save date from our new form field when we save our images. We can use the attachment_fields_to_save
filter hook to make sure our field is saved.
The attachment_fields_to_save
filter function takes two arguments: the first is an associative array of data that will be saved (called $post
); the latter is an associative array of the data that was submitted ($attachment
$post[‘post_parent’] to the value of $attachment['post_parent']
, using a series of if
statements. In the event that we do change the post_parent
of the item, assigning it as the thumbnail of another item, we need to make sure to unset the post_parent
of the current thumbnail, if there is one.
$children = get_children( array( 'post_parent' => $attachment['post_parent'], 'post_type' => 'attachment', 'post_mime_type' => 'image' ) ); if ( $children ) { $old_attachment = current($children); wp_update_post( array( 'ID' => $old_attachment->ID, 'post_parent' => 0 ) ); }
If $attachment['post_parent']
is set to ‘0’, we need determine if the file was previously set as a thumbnail to another item and is now being removed, or if it is an attachment to a post/page and should remain as such.
$parent = get_post( $post['post_parent'] ); if ( $parent->post_type != 'attachment' ) { return $post; }
Finally, change $post['post_parent']
if necessary:
$post['post_parent'] = $attachment['post_parent'];
And return the final version of $post
.
image_downsize
Now that we have a way to record and store our associations, it’s time to tell WordPress to do something useful with that information.
When creating the <img>
element for an image, WordPress calls the function wp_get_attachment_image
, which, in turn, gets it src
attribute from the function wp_get_attachment_image_src
. These functions take a $size
argument, which can be one of “thumbnail”, “medium”, or “full”, or an array containing the width and height of the image you want. This and the attachment ID get passed to the function image_downsize
to see if there is a smaller version of the image to meet your size criteria.
This is where WordPress finally gives us a hook to latch onto. The image_downsize
function calls the image_downsize
filter. The filter function needs to take three arguments. The first seems to be deprecated (with no documentation and only one occurrence, it’s hard to say what it’s supposed to do), and can be safely ignored, in any case. The attachment ID and size fill out the remaining arguments. The filter should return an array containing the URL for the image, its width, and its height.
Since this plugin is only dealing with thumbnail images, we don’t want to change anything if the $size
argument is something other than “thumbnail”. We have the option of returning false
if the filter doesn’t need to do anything.
if ( $size != 'thumbnail' ) { return false; }
Now we can see if our item has any images attached to it:
$attachments = get_children( array( 'post_parent' => $id, 'post_type' => 'attachment', 'post_mime_type' => 'image' ) );
If so, we can call wp_get_attachment_url
to get the URL for the first attached image, returning false
if there are no attached thumbnails.
if ( $attachments ) { return array( wp_get_attachment_url(key($attachments)), '', '' ); } else { return false; }
You may notice that I didn’t return aything for the width and height of the image. These really aren’t necessary, as they are just used to set the deprecated width
and height
attributes for the <img>
tag. By returning empty strings, we ensure that those attributes are not created.
If you recall from the discussion earlier from the custom_thumbnail_fields_to_edit
function, we use the custom_thumbnail_image_downsize
function to see if non-image attachments have a thumbnail image.
$thumb = custom_thumbnail_image_downsize(false, $post->ID, 'thumbnail');
If you’re really doing your homework, you might have noticed that I used this in place of
$thumb = wp_get_attachment_thumb_url($post->ID);
which is used for the same purpose in the image_attachment_fields_to_edit
function. Why the difference? wp_get_attachment_thumb_url
calls image_downsize
. Unfortunately, image_downsize
uses wp_attachment_is_image
to see if your attachment is an image before it will look for a thumbnail version and before it calls the image_downsize
filter. That means that it won’t look for a thumbnail for non-image uploads. We have to bypass that and go straight to our filter function to see if our attachments have thumbnails. Simply rearranging a few lines of code would fix this issue, and I’ll be requesting that the WordPress developers do just that.
media_send_to_editor
Our last function handles the job of creating the HTML to send back to the editor when the user clicks the “Insert into Post” button. This filter takes three arguments: $html
, the HTML created by the built-in functions and any other functions attached to this filter; $attachment_id
, the ID of the item being inserted; and $attachment
, the associative array that was posted when you clicked the “Insert into Post” button. The function should return the HTML that should be sent to the editor.
After checking that our item is not an image (since WordPress can handle images on its own), we look at $attachment
to see what options the user chose. The value of $attachment['media-display']
will determine the path our code takes. If the user chose “thumbnail”, we get the child images of the item:
$thumbnails = get_children( array( 'post_parent' => $attachment_id, 'post_type' => 'attachment', 'post_mime_type' => 'image' ) );
If it has a thumbnail, we get an <img>
tag for the thumbnail image and put it into the $html
variable:
$html = get_image_tag(key($thumbnails), $attachment['post_excerpt'], $attachment['post_title'], $align, $size);
It the user chose “icon”, or if the thumbnail disappeared for some reason before the user submitted the form, we get an <img>
tag for the appropriate icon:
$html = wp_get_attachment_image($attachment_id, 'thumbnail', true);
The third argument to wp_get_attachment_image
tells it to return an icon.
If the user chose “text”, then we can just let WordPress handle it like it normally would by returning $html
unchanged.
Next we wrap the <img>
in a link:
$url = $attachment['url']; if ( $url == get_attachment_link($attachment_id) ) { $rel = ' rel="attachment wp-att-'.attribute_escape($id).'"'; } else { $rel = ''; } if ( $url ) { $html = '<a href="' . clean_url($url) . "\"$rel>$html</a>"; }
Finally, we let any other filters (e.g., to create captions or align the image) run on $html
before we return it:
$html = apply_filters( 'image_send_to_editor', $html, $attachment_id, $attachment['post_excerpt'], $attachment['post_title'], $align, $url );
At long last, we’re done. Once again, the Custom Thumbnails plugin is available from the WordPress Plugin Directory if you would like to use it or see the full code.
hi. Thanks for plugin
perfect.
Regards
Thanks for clarifying “attachment_fields_to_edit” and “attachment_fields_to_save” – helped me a lot!
Love the detail in this article. Thanx so much.
Are you planning to expand on this plugin?
I think the image insert screen could use a great deal of work:
– insert any image size available (I’ve a plugin which creates minithumbs)
– link to any image size, (popover to a midsize instead of the full)
– and of course, all that your plugin does, should maybe be system default.
Let us know if you have plans to expand
Thanx again
The plugin looks great! However in the admin screen in the media library I get the following:
Warning: Invalid argument supplied for foreach() in /plugins/custom-thumbnails/custom_thumbnails.php on line 39
And when I try to parent one item to another, it gives me an empty dropdown. Any ideas?
I think my error was the result of a plugin conflict. Now I have a new question. If I want to show the resulting thumbnails associated with media items within a gallery, how do I call it inside the template (instead of inserting it into a post)?
“The plugin looks great! However in the admin screen in the media library I get the following:
Warning: Invalid argument supplied for foreach() in /plugins/custom-thumbnails/custom_thumbnails.php on line 39
And when I try to parent one item to another, it gives me an empty dropdown. Any ideas?”
I have the same problem.
What is the solution?
Please.
Thanx
This is a fantastic description of something that I have been searching all over for, flipping marvelous.
i was so stoked when i found this plugin, but it won’t work with WP 2.9. any plans to upgrade it?
This would be a great addition to the 2.9 custom thumb feature, WP is still missing it. Any plans to update the plugin?
Hi, i cannot set as thumbnail for a post? Because i can set just for a few photos… Thank you for answer.
Hi, i install the plugin, great indeed, but i need to link to a post without images; i use a plugin that make slide show in post with the pictures which are not insert in the post, and another plugin that maks slide in home. I want in home slide to be a picture who link to post, but in that post dont show this picture. I use 2.9.2. Thanks
Dude, this is a brilliant post. You could have just given us the link to your plugin and been done with it. Instead you took the time to explain some really deep action and filter hooks that will be totally helpful for other plugin developers who might want to boldly go where you have just gone before. Thank you for taking the time and giving back to the community.
Great Idea for a plugin, when I tried using it with WP 3.3.1 the dropdown list was not populated with anything. Any Ideas?
Thanks,
David