Welcome WordPress Theme and Plugin developers! We all would like our theme users to be able to upload their own images or logos by using a Theme Options page created by us (including plugins pages) but, how do you program it? Do you use WordPress Media Uploader (as when you upload a featured image or insert an image in a post) or do you just add a file input field and forget about all the other things? Do you upload images to an upload folder properly? Do you attach the image to the WordPress Media Library? And, this is an important point, do you delete the file (if this is what the user wants) properly? Well, it is time to give shape to our Theme Options page using the WordPress interface. We want happy users, we want a user-friendly interface.
This tutorial is focused on uploading images to a Theme Options page, so if you are not quite sure about how to create one, I seriously recommend you first of all, have a look at the amazing Tom McFarlin's tutorial The Complete Guide To The WordPress Settings API series.
What Are We Going to Do in This Tutorial?
- We will add a button to our form to upload images or logos to our server file system and another button to delete this image.
- We will create an input field to preview the image.
- We will use the WordPress Media Uploader to upload the file or to pick an existing one so we will not have to worry about the whole process. We will also manage to upload the image to the right folder and we will attach it to the WordPress Media Library.
- We will be able to delete the image itself as well as its WordPress Media Library attachment. We do not want to waste server space.
Preparation Creating a Theme Options Page
We need to create a folder called wptuts-options in the theme root folder containing a file called wptuts-options.php where the whole code needed to create our Theme Options page will be defined. We also must create a folder called js where we will save the JavaScript files that we will need.
First of all, we must call our wptuts-options.php file from within our functions.php:
require_once( 'wptuts-options/wptuts-options.php' );
Inside our wptuts-options.php file we will create a function where we will specify the default values. In this case, the value will be the image URL on our server. We will assign an empty string by default but we could also assign the URL of an image that we already have in some theme folder.
function wptuts_get_default_options() { $options = array( 'logo' => '' ); return $options; }
Now we are going to create a function that, if our option does not exist in the database (we will call it theme_wptuts_options
), will initiate it with the values given by our previous function.
function wptuts_options_init() { $wptuts_options = get_option( 'theme_wptuts_options' ); // Are our options saved in the DB? if ( false === $wptuts_options ) { // If not, we'll save our default options $wptuts_options = wptuts_get_default_options(); add_option( 'theme_wptuts_options', $wptuts_options ); } // In other case we don't need to update the DB } // Initialize Theme options add_action( 'after_setup_theme', 'wptuts_options_init' );
Now it is time to create our Theme Options page, add it to the Admin Panel and create a form.
// Add "WPTuts Options" link to the "Appearance" menu function wptuts_menu_options() { // add_theme_page( $page_title, $menu_title, $capability, $menu_slug, $function); add_theme_page('WPTuts Options', 'WPTuts Options', 'edit_theme_options', 'wptuts-settings', 'wptuts_admin_options_page'); } // Load the Admin Options page add_action('admin_menu', 'wptuts_menu_options'); function wptuts_admin_options_page() { ?> <!-- 'wrap','submit','icon32','button-primary' and 'button-secondary' are classes for a good WP Admin Panel viewing and are predefined by WP CSS --> <div class="wrap"> <div id="icon-themes" class="icon32"><br /></div> <h2><?php _e( 'WPTuts Options', 'wptuts' ); ?></h2> <!-- If we have any error by submiting the form, they will appear here --> <?php settings_errors( 'wptuts-settings-errors' ); ?> <form id="form-wptuts-options" action="options.php" method="post" enctype="multipart/form-data"> <?php settings_fields('theme_wptuts_options'); do_settings_sections('wptuts'); ?> <p class="submit"> <input name="theme_wptuts_options[submit]" id="submit_options_form" type="submit" class="button-primary" value="<?php esc_attr_e('Save Settings', 'wptuts'); ?>" /> <input name="theme_wptuts_options[reset]" type="submit" class="button-secondary" value="<?php esc_attr_e('Reset Defaults', 'wptuts'); ?>" /> </p> </form> </div> <?php }
In summary: By using the hook admin_menu
we have added our page to the Admin Panel under Appearance -> WPTuts Options and it could be identified by the slug wptuts-settings
. After this, we have created a form that still doesn't have any input field based on setting_fields
and do_settings_sections
functions. As I have said before, the target of this tutorial is not to describe how these functions work, so we are not going to explain what they are for. You can read Tom's series on that linked above.
But, pay attention to the fact that, apart from creating the button submit
, we have also created another one, a Reset
button. When we press it, the image value will be that established by default.
Finally we are going to create our button to upload images and an input field where, once uploaded, it shows its URL.
function wptuts_options_settings_init() { register_setting( 'theme_wptuts_options', 'theme_wptuts_options', 'wptuts_options_validate' ); // Add a form section for the Logo add_settings_section('wptuts_settings_header', __( 'Logo Options', 'wptuts' ), 'wptuts_settings_header_text', 'wptuts'); // Add Logo uploader add_settings_field('wptuts_setting_logo', __( 'Logo', 'wptuts' ), 'wptuts_setting_logo', 'wptuts', 'wptuts_settings_header'); } add_action( 'admin_init', 'wptuts_options_settings_init' ); function wptuts_settings_header_text() { ?> <p><?php _e( 'Manage Logo Options for WpTuts Theme.', 'wptuts' ); ?></p> <?php } function wptuts_setting_logo() { $wptuts_options = get_option( 'theme_wptuts_options' ); ?> <input type="text" id="logo_url" name="theme_wptuts_options[logo]" value="<?php echo esc_url( $wptuts_options['logo'] ); ?>" /> <input id="upload_logo_button" type="button" class="button" value="<?php _e( 'Upload Logo', 'wptuts' ); ?>" /> <span class="description"><?php _e('Upload an image for the banner.', 'wptuts' ); ?></span> <?php }
There is not much more to say here, just that the value of the logo
field shows the escaped image URL. Right now this is what our screen shows:
And don't forget about our data validation function:
function wptuts_options_validate( $input ) { $default_options = wptuts_get_default_options(); $valid_input = $default_options; $submit = ! empty($input['submit']) ? true : false; $reset = ! empty($input['reset']) ? true : false; if ( $submit ) $valid_input['logo'] = $input['logo']; elseif ( $reset ) $valid_input['logo'] = $default_options['logo']; return $valid_input; }
Let's save the value of the logo
field just as it is if we submit the form or leave it with its default value if we reset the form. It is advisable to check the value of the input field, validating the URLs.
If we have arrived to this point (I hope so) and we are not really tired we can try the form. We will see that the value of the input field is saved without problems and that shows, after this, a URL as value.
Now, let's continue with what it is really important.
Step 1 Adding the Necessary JavaScript
If we want the WordPress Media Uploader to work properly we must import several JavaScript libraries as well as some additional CSS:
- Thickbox (JS) – Responsible for managing the modal window, to which we will be able to drag or select files. It is provided by WordPress Core.
- Thickbox (CSS) – Provides the styles needed for this window. It also comes with the WordPress installation.
- Media Upload (JS) – Provides all the functions needed to upload, validate and give format to files. It is the WordPress Media Uploader heart.
- Our own JS – It will initialize the parameters needed to show the window properly.
We have to introduce the following code in the wptuts-options.php file:
function wptuts_options_enqueue_scripts() { wp_register_script( 'wptuts-upload', get_template_directory_uri() .'/wptuts-options/js/wptuts-upload.js', array('jquery','media-upload','thickbox') ); if ( 'appearance_page_wptuts-settings' == get_current_screen() -> id ) { wp_enqueue_script('jquery'); wp_enqueue_script('thickbox'); wp_enqueue_style('thickbox'); wp_enqueue_script('media-upload'); wp_enqueue_script('wptuts-upload'); } } add_action('admin_enqueue_scripts', 'wptuts_options_enqueue_scripts');
There are a couple of things that we must clarify: in the first line we are registering a script (we have not talked about it yet) that will handle the process aimed to open the model window and to collect image data. As we have explained before we are going to create a folder called js. A peculiarity of this script is that it depends on a number of other libraries such as jQuery, Media-Upload and Thickbox, all of them come when you install WordPress.
In the second line we use the get_current_screen()
function that provides us the slug of the page we are working on. This function cannot always be used and depending on the hook we are using, it will be available or not. With the hook admin_enqueue_scripts
the function will work without problems. get_current_screen() -> id
gives us a slug of the page where we are working on. For the pages that come by default in the WordPress Admin Panel, this may be 'themes
','edit-post
','plugins
' etc. In our case, this slug looks like appearance_page_{OUR_SLUG}
. Do you remember the slug that we defined in the add_theme_page
function? Well, our Theme Options page, finally has the following slug: appearance_page_wptuts-settings
. So, we will only load scripts when appropriate.
The other two lines add the Javascript libraries jQuery, Thickbox, Media Upload and our JS, wptuts-upload.js
. Furthermore we also add the Thickbox CSS.
Our Script: wptuts-upload.js
Despite how it looks, our script is going to be easier than it may appear. It is only necessary to get to know some Thickbox functions and the Media Uploader to make it work. The trouble is that it is difficult to find information about it and in the end, as the good programmers that we are, we have no other choice than working with code. As we are just about to see, it is really easy to do. Let's continue directly to our first version code:
jQuery(document).ready(function($) { $('#upload_logo_button').click(function() { tb_show('Upload a logo', 'media-upload.php?referer=wptuts-settings&type=image&TB_iframe=true&post_id=0', false); return false; }); });
Success! If we press right now our Upload Logo
button the WordPress Media Uploader will appear. Great, we have finished, see you soon! No, this is not true, but it won't take much longer to make our Theme Options page work, in a simple way.
Reviewing the code we can see that we have assigned a click event to the button that launches a Thickbox function aimed to show the modal window. This function accepts three parameters:
-
Name of the window – In our case '
Upload a Logo
' - URL – Executes a WordPress library that handles and validates files, besides creating content for the window.
-
imageGroup – We have chosen the option
false
because we are not going to work with groups of images but just with one.
Among them, the most interesting one is the URL. WordPress uses a file called media-upload.php to manage the window and also allows several $_GET
parameters. We must remember that &
characters must be coded with their HTML entity so the URL will work without any problem.
-
referer
– This parameter is optional. We'll use it later to do a little trick. -
type
– It is the type of file. It can bevideo
,audio
,image
orfile
. -
TB_iframe
– It must be always selectedtrue
so the window is shown in an iframe, or it will not work. Though you may find this hard to believe, it is the most important parameter and now we will see why. -
post_id
– It is used to indicate that the image will not be attached to any post and that it will be free like a little bird.
Well, I do not want to lie to you. Just one of these three parameters is really necessary: TB_iframe
. We can forget about the other ones. Some versions ago, WordPress unified its Media Uploader to upload any type of file, not needing to differentiate images from videos or music, so type
is not necessary and the post ID is 0 by default. Anyway, there is no harm in leaving them just in case we have some problem with compatibilities. It would be interesting to indicate post_id
if it is a Meta Box in a post.
The following part of our JavaScript must contain the following function:
window.send_to_editor = function(html) { var image_url = $('img',html).attr('src'); $('#logo_url').val(image_url); tb_remove(); }
send_to_editor
is an event included in the WordPress JavaScript Media Uploader library. It will deliver image data in HTML format, so we can put them wherever we want.
This event delivers a parameter to the handler function, html
that includes the following code (as an example):
<a href="{URL ON THE SERVER FOR THE UPLOADED IMAGE}"><img src="{URL ON THE SERVER FOR THE UPLOADED IMAGE}" alt="" title="" width="" height"" class="alignzone size-full wp-image-125" /></a>
So it is easy to extract the image URL once loaded to the server using the line $('img',html).attr('src');
then it will be stored in our input field with the line $('#logo_url').val(image_url);
.
The function tb_remove
closes the modal window, that's all. Now, we are already able to submit the form and save the image URL in the database. We could stop now, but the result would not look really beautiful or user-friendly so let's make an improvement.
If we pay attention, when we upload the image using the Media Uploader we can insert the image URL in our input field through the Insert into Post
button. This could confuse the user. For that reason, we can change this text by using filters in WordPress. We type the following code in our wptuts-options.php file:
function wptuts_options_setup() { global $pagenow; if ( 'media-upload.php' == $pagenow || 'async-upload.php' == $pagenow ) { // Now we'll replace the 'Insert into Post Button' inside Thickbox add_filter( 'gettext', 'replace_thickbox_text' , 1, 3 ); } } add_action( 'admin_init', 'wptuts_options_setup' ); function replace_thickbox_text($translated_text, $text, $domain) { if ('Insert into Post' == $text) { $referer = strpos( wp_get_referer(), 'wptuts-settings' ); if ( $referer != '' ) { return __('I want this to be my logo!', 'wptuts' ); } } return $translated_text; }
Using the hook admin_init
, we check that the pages we are working on are the ones used by the Media Uploader. These pages are: media-upload.php and async-upload.php. The first one opens the modal window and the second one is loaded once the image has been uploaded. To confirm that we are working on any of them we have to use the global variable $pagenow
and not the function get_current_screen()
because admin_init
still does not allow this function.
Now, why are we using the referer
variable? Ok, this is a little tricky and it works like this:
- When we click on the
Upload Image
button, the referer URL is something likehttp://www.ourdomain.com/.../ wp-admin/themes.php?page=wptuts_settings
- If we then click on a tab like Media Library inside the Media Uploader, the referer URL changes and takes the next value:
http://localhost/.../wp-admin/media-upload.php?referer=wptuts-settings&type=image.
- The same thing happens when we upload a new image. The referer URL changes and takes the same value.
See now why we included the referer parameter in our JavaScript? We need to know from which page we are launching the Media Uploader because we need to replace the Insert into Post
text in the button just in our Theme Options page and not in a Post Page for instance. That's why I included the referer parameter. Now, using the wp_get_referer()
function we get the referer URL and we just have to find the wptuts-settings
string inside that URL. With this method we'll replace it in the right context.
We now apply the gettext
filter and every sentence containing 'Insert into Post'
we replace with 'I want this to be my logo!'
. If we open once again the Thickbox window and load a new file, we will see that the text of the button has changed. If you are not quite sure about how to use the gettext
filter, and since it is not one of the targets of this tutorial, you can visit the WordPress Codex.
Some improvements have been done, haven't they?
Step 2 Previewing the Image
The user always needs to watch things happening on the screen. It is not enough for the user to upload an image and go to the page to check that the image is there. Now, we are going to add an input field to our Theme Options page so the user will be able to see the beautiful image already loaded.
We have to write the following code in our wptuts_options_settings_init()
function:
// Add Current Image Preview add_settings_field('wptuts_setting_logo_preview', __( 'Logo Preview', 'wptuts' ), 'wptuts_setting_logo_preview', 'wptuts', 'wptuts_settings_header');
And also we have to create a new function for the preview:
function wptuts_setting_logo_preview() { $wptuts_options = get_option( 'theme_wptuts_options' ); ?> <div id="upload_logo_preview" style="min-height: 100px;"> <img style="max-width:100%;" src="<?php echo esc_url( $wptuts_options['logo'] ); ?>" /> </div> <?php }
If we upload a new image right now and we submit the form, we will see this:
Cool! Take it easy, don't run. There are two steps to take, at first we upload the image and then we are forced to submit the form if we want to save the changes. The user could think once the image is uploaded, where the hell is my logo? Do I have to submit the form? Avoid getting upset by adding some simple lines to our JavaScript:
window.send_to_editor = function(html) { var image_url = $('img',html).attr('src'); $('#logo_url').val(image_url); tb_remove(); $('#upload_logo_preview img').attr('src',image_url); $('#submit_options_form').trigger('click'); }
We upload the image and we can see that the form has been submitted! Just adding a sentence: now when the image is loaded we trigger the click
event on the button Submit
and the form is submitted immediately, updating the database and the image preview at the same time. Perfect!
Removing What's Unnecessary
Up to now, the form is attractive, usable and it works more than ok but there is something that begins to disturb us. Why do we need the input field? Hey, we need it to save the image URL. Let's see it another way: why the user needs input field? For nothing. It is enough to show the user the image that has uploaded and that everything works properly.
Let's convert our form a little more with the wptuts_setting_logo()
function:
function wptuts_setting_logo() { $wptuts_options = get_option( 'theme_wptuts_options' ); ?> <input type="text" id="logo_url" name="theme_wptuts_options[logo]" value="<?php echo esc_url( $wptuts_options['logo'] ); ?>" /> <input id="upload_logo_button" type="button" class="button" value="<?php _e( 'Upload Logo', 'wptuts' ); ?>" /> <span class="description"><?php _e('Upload an image for the banner.', 'wptuts' ); ?></span> <?php }
If you hadn't noticed, the only thing we have done is to change the input type of the form. We are talking now about a hidden
input field and not a text
input field. The form keeps the same functionality but it is much more pleasant for the user:
Step 3 Deleting the Image
Naturally, at some point, the user will want to delete the image. To facilitate things we are going to create a button to delete it. But the image shouldn't be deleted only when the user clicks on the button, also it should be removed when they upload a new image or we reset the form.
First things first. We are going to create the new button in the wptuts_setting_logo()
function:
function wptuts_setting_logo() { $wptuts_options = get_option( 'theme_wptuts_options' ); ?> <input type="hidden" id="logo_url" name="theme_wptuts_options[logo]" value="<?php echo esc_url( $wptuts_options['logo'] ); ?>" /> <input id="upload_logo_button" type="button" class="button" value="<?php _e( 'Upload Logo', 'wptuts' ); ?>" /> <?php if ( '' != $wptuts_options['logo'] ): ?> <input id="delete_logo_button" name="theme_wptuts_options[delete_logo]" type="submit" class="button" value="<?php _e( 'Delete Logo', 'wptuts' ); ?>" /> <?php endif; ?> <span class="description"><?php _e('Upload an image for the banner.', 'wptuts' ); ?></span> <?php }
If we pay attention, the new button will only appear when there would be a logo already loaded. Besides, we are talking about a submit-type button, so we will submit the form when we click on it.
We will have to add the following validation functionality so the button will act as we want, wptuts_options_validate()
:
$default_options = wptuts_get_default_options(); $valid_input = $default_options; $wptuts_options = get_option('theme_wptuts_options'); $submit = ! empty($input['submit']) ? true : false; $reset = ! empty($input['reset']) ? true : false; $delete_logo = ! empty($input['delete_logo']) ? true : false; if ( $submit ) { if ( $wptuts_options['logo'] != $input['logo'] && $wptuts_options['logo'] != '' ) delete_image( $wptuts_options['logo'] ); $valid_input['logo'] = $input['logo']; } elseif ( $reset ) { delete_image( $wptuts_options['logo'] ); $valid_input['logo'] = $default_options['logo']; } elseif ( $delete_logo ) { delete_image( $wptuts_options['logo'] ); $valid_input['logo'] = ''; } return $valid_input;
All right, what are we doing here? We have added a new $wptuts_options
variable to verify if the user has clicked on the Delete Logo
button. If the user does this, the delete_image
function is executed and we set the value of the logo URL as an empty string. Additionally, the logo will be deleted if we submit and we upload a different image to the one we already have or even if we reset the form.
Careful! Resetting the form and deleting the image does not have to be the same process. In our case, the default value is an empty string, so they match.
Now we will add the delete_image()
function:
function delete_image( $image_url ) { global $wpdb; // We need to get the image's meta ID. $query = "SELECT ID FROM wp_posts where guid = '" . esc_url($image_url) . "' AND post_type = 'attachment'"; $results = $wpdb->get_results($query); // And delete it foreach ( $results as $row ) { wp_delete_attachment( $row->ID ); } }
The truth is that this step needs a deeper explanation, but it is really easy. The first thing we are doing is executing a query that will find out the Meta ID of our image in the database. You may think it is a lie, but our image data is in the wp_posts
table. Well, the query tries to select those registers whose guid (the image, post or page URL) will match up with our image's and post_type = 'attachment'
(It is an attachment, isn't it?). We store this ID (there shouldn't be more than one) in $results
and pass it as a parameter to the WordPress wp_delete_attachment()
that will delete the image itself and also delete the attachment from the Media Library. It is easy, clean and optimal.
Step 4 Showing Our Logo in Our Site Header
Let's see where all this mess led us to. We need the header.php template where we are going to insert a space for our dear logo, where we will insert this piece of code in the place we like the best:
<body <?php body_class(); ?>> <div id="container" class="container_12"> <?php $wptuts_options = get_option('theme_wptuts_options'); ?> <?php if ( $wptuts_options['logo'] != '' ): ?> <div id="logo"> <img src="<?php echo $wptuts_options['logo']; ?>" /> </div> <?php endif; ?>
Here is the result:
Final Notes
The truth is that there is not much more to say. Is it possible to do this another way? Of course, and in fact I find examples constantly, but from my point of view the WordPress Media Uploader is really useful and once known in depth it really makes life easy. We save code, validation (in the tutorial we have not used a lot, we should have used more, and recommend you read up on this) and use the file system that WordPress puts at our disposal. All are advantages for the user, who is used to working with WordPress' interface and can see how everything works properly and according to WordPress' standard functionality. In fact, it could be thought that it is WordPress default functionality.
External Resources
Although we have talked about specific WordPress functionality, the truth is that there is a lot of intermediate knowledge that is necessary. Here we have a list with related resources:
Comments