Multiple upload inputs in a WordPress theme options page?

Posted on

WordPress settings API

Lots of info and tutorials on the net on the subject of WordPress “theme options” or  ”admin” pages. Many of them are out dated and don’t subscribe to the current best practices as outlined by WordPress – in other words the settings API. Nettuts is always a great place to start. In my case, the tutorial the resinated the most with me was a fairly recent post by Aliso The Geek. Her tutorial is clear, easy to follow, results in a organized tabbed theme options page and best of all, your new WordPress theme options page will be 100% contained within a tidy class file – easily transferred from theme to theme is so desired.

So what’s left to expound upon?

Not much, unless you’d like to include and upload field. The easiest way to implement this, with some possible pitfalls (more on this later), is to use WordPress’ built in Media Uploader and Thinkbox script.

Step one: Back to your theme options class

Since we are adding a new “type” we’re going to need to add a new case to the switch statement.

case 'upload':
default:
   echo '<input id="' . $id . '" class="upload-url' . $field_class . '" type="text" name="mytheme_options[' . $id . ']" value="' . esc_attr( $options[$id] ) . '" />'
   '<input id="st_upload_button" class="st_upload_button" type="button" name="upload_button" value="Upload" />';

if ( $desc != '' )
   echo '
   <span class="description">' . $desc . '</span>';

break;

Essentially the same as the ‘text’ case. Change type to “file”, add an input button with a class of “st_upload_button” (or whatever you so desire) that will later trigger the media uploader.

Step Two: Add the upload field Array

		$this->settings['st_upload'] = array(
			'title'   => __( 'Example upload Input' ),
			'desc'    => __( 'This is a description for the upload input.' ),
			'std'     => 'My logo',
			'type'    => 'upload',
			'section' => 'general'
		);

Nothing new here…

  1. Name it what you want
  2. Your title
  3. Your description
  4. Default Value (for the text field in this case)
  5. Type (our new type, yay)
  6. Whatever section you’d like to appear

Refresh your WordPress theme options page and your upload field should be present and look something like this:

Step Three: Gather the tools

First we need to load a few scripts and styles.

  1. Medial Uploader script
  2. Thickbox script
  3. Thickbox CSS
  4. And finally, “my-upload” script

We already have a scripts() and styles() function in our class, so this is really quite easy.

//Media Uploader Scripts
wp_enqueue_script('media-upload');
wp_enqueue_script('thickbox');
wp_register_script('my-upload', get_bloginfo( 'stylesheet_directory' ) . '/js/uploader.js', array('jquery','media-upload','thickbox'));
wp_enqueue_script('my-upload');
//Media Uploader Style
wp_enqueue_style('thickbox');

Step Four: Make it do something cool

jQuery(document).ready(function() {

	jQuery('.st_upload_button').click(function() {
		 targetfield = jQuery(this).prev('.upload-url');
		 tb_show('', 'media-upload.php?type=image&amp;TB_iframe=true');
		 return false;
	});

	window.send_to_editor = function(html) {
		 imgurl = jQuery('img',html).attr('src');
		 jQuery(targetfield).val(imgurl);
		 tb_remove();
	}

});

When the upload button is clicked, we launch the WordPress media uploader and set the ‘targetfield’ variable to the previous text input. We’ll swing back around for this later. Next we overwrite send_to_editor,  get the image URL from the SCR attribute and then finally put that value in the target field.

Pitfalls?

My main complaint with the media uploader in this situation is it’s not particularly intuitive for clients or people not familiar with the finner details of WordPress. One has to either upload an image or choose from the media library and then click “insert into post”. On the other hand, it is consistent with the rest of the WordPress GUI and allows you to choose from already uploaded images.


Tags: , , , , ,

29 comments on Multiple upload inputs in a WordPress theme options page?
  • Genevieve

    You are my hero. I have been searching for EONS for a simple way to include uploads in an options page. Found lots of frameworks, but nothing that was simple and straight-forward. Aliso’s options page tutorial was perfect, except that it was missing this. And now my world is complete. THANK YOU!!! :)

  • mira

    HI! Thank you for this script.
    I’m using the theme option from Alison:
    http://alisothegeek.com/2011/04/wordpress-settings-api-tutorial-follow-up/

    And your script works when i click over UPLOAD button, the wordpress media upload pop up, i choose a image, i click on ‘insert in post’ and it comes back to the previous upload field. When I click SAVE, it works fine. The image is stored in Media attachs.

    But…

    When i come back to MENU OPTION, the field is blank. No more url shows…

    I wonder if you plan to to do something like that:

    When clicking again to show this option, that image stored could be printed in the field upload, and when clicking again over UPLOAD button, maybe load wordpress media again, but appointing to the same stored image…

  • mira

    Other thing… the image is uploaded fine! But is not stored in option inside database. so i can’t do ‘echo’ option(‘image_id’);

  • mira

    Hi Jusin,

    Yes. The string (image url) is not saved :(((( just imahge is uploaded :((((

  • mira

    Justin, i fixed! YEAHHH! :)

    I forgot to switch ‘mytheme_option’. Now it is ok! Thank you dude!!!

    Do you plan to do a color pick too?

  • Eric

    Using tagetfield has a varible isnt working.

    jQuery(targetfield)

    That wont work but

    jQuery(‘#logo_image’)

    will… Why is this?

  • Eric

    I think here is my issue.

    For some odd reason the variable isnt working.

    Say like

    jQuery(targetfield).val(imgurl)

    Will not work at all. However…

    jQuery(.upload-url).val(imgurl)

    will work just fine, however this stops me from using multi fields.

  • Joan

    Excellent!, i recently implemented on my theme options ;).

    Now i’m trying to implement the resize/crop function like in WordPress Custom Header, after upload they have Cut & publish, if in your upload field Array put 2 new options (width & height) and call this cool function for crop or resize image to the array values, any idea :P?

    Thank you and regards!

  • Joan

    @Justin:

    What i meant is to use the functions from Custom Header (wp-admin/custom-header.php) for the upload field in our new upload type in theme options, extending it with 2 new fields for width & height.

    It can be a very cool feature for average users, with this implemented they can upload any image size and the functions will resize/crop automatically to the field values, exactly the same what the “Custom Header” theme feature does.

    The functions:

    function js_2() { ?>
    <script type="text/javascript">
    /* <![CDATA[ */
    	function onEndCrop( coords ) {
    		jQuery( '#x1' ).val(coords.x);
    		jQuery( '#y1' ).val(coords.y);
    		jQuery( '#width' ).val(coords.w);
    		jQuery( '#height' ).val(coords.h);
    	}
    
    	jQuery(document).ready(function() {
    		var xinit = <?php echo HEADER_IMAGE_WIDTH; ?>;
    		var yinit = <?php echo HEADER_IMAGE_HEIGHT; ?>;
    		var ratio = xinit / yinit;
    		var ximg = jQuery('img#upload').width();
    		var yimg = jQuery('img#upload').height();
    
    		if ( yimg < yinit || ximg < xinit ) {
    			if ( ximg / yimg > ratio ) {
    				yinit = yimg;
    				xinit = yinit * ratio;
    			} else {
    				xinit = ximg;
    				yinit = xinit / ratio;
    			}
    		}
    
    		jQuery('img#upload').imgAreaSelect({
    			handles: true,
    			keys: true,
    			aspectRatio: xinit + ':' + yinit,
    			show: true,
    			x1: 0,
    			y1: 0,
    			x2: xinit,
    			y2: yinit,
    			maxHeight: <?php echo HEADER_IMAGE_HEIGHT; ?>,
    			maxWidth: <?php echo HEADER_IMAGE_WIDTH; ?>,
    			onInit: function () {
    				jQuery('#width').val(xinit);
    				jQuery('#height').val(yinit);
    			},
    			onSelectChange: function(img, c) {
    				jQuery('#x1').val(c.x1);
    				jQuery('#y1').val(c.y1);
    				jQuery('#width').val(c.width);
    				jQuery('#height').val(c.height);
    			}
    		});
    	});
    /* ]]> */
    </script>
    <?php
    	}
    	/**
    	 * Display second step of custom header image page.
    	 *
    	 * @since 2.1.0
    	 */
    	function step_2() {
    		check_admin_referer('custom-header-upload', '_wpnonce-custom-header-upload');
    		if ( ! current_theme_supports( 'custom-header-uploads' ) )
    			wp_die( __( 'Cheatin’ uh?' ) );
    
    		$overrides = array('test_form' => false);
    		$file = wp_handle_upload($_FILES['import'], $overrides);
    
    		if ( isset($file['error']) )
    			wp_die( $file['error'],  __( 'Image Upload Error' ) );
    
    		$url = $file['url'];
    		$type = $file['type'];
    		$file = $file['file'];
    		$filename = basename($file);
    
    		// Construct the object array
    		$object = array(
    		'post_title' => $filename,
    		'post_content' => $url,
    		'post_mime_type' => $type,
    		'guid' => $url,
    		'context' => 'custom-header'
    		);
    
    		// Save the data
    		$id = wp_insert_attachment($object, $file);
    
    		list($width, $height, $type, $attr) = getimagesize( $file );
    
    		if ( $width == HEADER_IMAGE_WIDTH && $height == HEADER_IMAGE_HEIGHT ) {
    			// Add the meta-data
    			wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
    			update_post_meta( $id, '_wp_attachment_is_custom_header', get_option('stylesheet' ) );
    
    			set_theme_mod('header_image', esc_url($url));
    			do_action('wp_create_file_in_uploads', $file, $id); // For replication
    			return $this->finished();
    		} elseif ( $width > HEADER_IMAGE_WIDTH ) {
    			$oitar = $width / HEADER_IMAGE_WIDTH;
    			$image = wp_crop_image($file, 0, 0, $width, $height, HEADER_IMAGE_WIDTH, $height / $oitar, false, str_replace(basename($file), 'midsize-'.basename($file), $file));
    			if ( is_wp_error( $image ) )
    				wp_die( __( 'Image could not be processed.  Please go back and try again.' ), __( 'Image Processing Error' ) );
    
    			$image = apply_filters('wp_create_file_in_uploads', $image, $id); // For replication
    
    			$url = str_replace(basename($url), basename($image), $url);
    			$width = $width / $oitar;
    			$height = $height / $oitar;
    		} else {
    			$oitar = 1;
    		}
    		?>
    
    <div class="wrap">
    <?php screen_icon(); ?>
    <h2><?php _e( 'Crop Header Image' ); ?></h2>
    
    <form method="post" action="<?php echo esc_attr(add_query_arg('step', 3)); ?>">
    	<p class="hide-if-no-js"><?php _e('Choose the part of the image you want to use as your header.'); ?></p>
    	<p class="hide-if-js"><strong><?php _e( 'You need Javascript to choose a part of the image.'); ?></strong></p>
    
    	<div id="crop_image" style="position: relative">
    		<img src="<?php echo esc_url( $url ); ?>" id="upload" width="<?php echo $width; ?>" height="<?php echo $height; ?>" />
    	</div>
    
    	<input type="hidden" name="x1" id="x1" value="0"/>
    	<input type="hidden" name="y1" id="y1" value="0"/>
    	<input type="hidden" name="width" id="width" value="<?php echo esc_attr( $width ); ?>"/>
    	<input type="hidden" name="height" id="height" value="<?php echo esc_attr( $height ); ?>"/>
    	<input type="hidden" name="attachment_id" id="attachment_id" value="<?php echo esc_attr( $id ); ?>" />
    	<input type="hidden" name="oitar" id="oitar" value="<?php echo esc_attr( $oitar ); ?>" />
    	<?php wp_nonce_field( 'custom-header-crop-image' ) ?>
    
    	<?php submit_button( __( 'Crop and Publish' ) ); ?>
    	</p>
    </form>
    </div>
    		<?php
    	}
    function step_3() {
    		check_admin_referer('custom-header-crop-image');
    		if ( ! current_theme_supports( 'custom-header-uploads' ) )
    			wp_die( __( 'Cheatin’ uh?' ) );
    
    		if ( $_POST['oitar'] > 1 ) {
    			$_POST['x1'] = $_POST['x1'] * $_POST['oitar'];
    			$_POST['y1'] = $_POST['y1'] * $_POST['oitar'];
    			$_POST['width'] = $_POST['width'] * $_POST['oitar'];
    			$_POST['height'] = $_POST['height'] * $_POST['oitar'];
    		}
    
    		$attachment_id = absint( $_POST['attachment_id'] );
    		$original = get_attached_file($attachment_id);
    
    		$cropped = wp_crop_image( $attachment_id, (int) $_POST['x1'], (int) $_POST['y1'], (int) $_POST['width'], (int) $_POST['height'], HEADER_IMAGE_WIDTH, HEADER_IMAGE_HEIGHT );
    		if ( is_wp_error( $cropped ) )
    			wp_die( __( 'Image could not be processed.  Please go back and try again.' ), __( 'Image Processing Error' ) );
    
    		$cropped = apply_filters('wp_create_file_in_uploads', $cropped, $attachment_id); // For replication
    
    		$parent = get_post($attachment_id);
    		$parent_url = $parent->guid;
    		$url = str_replace(basename($parent_url), basename($cropped), $parent_url);
    
    		// Construct the object array
    		$object = array(
    			'ID' => $attachment_id,
    			'post_title' => basename($cropped),
    			'post_content' => $url,
    			'post_mime_type' => 'image/jpeg',
    			'guid' => $url,
    			'context' => 'custom-header'
    		);
    
    		// Update the attachment
    		wp_insert_attachment($object, $cropped);
    		wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $cropped ) );
    		update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', get_option('stylesheet' ) );
    
    		set_theme_mod('header_image', $url);
    
    		// cleanup
    		$medium = str_replace(basename($original), 'midsize-'.basename($original), $original);
    		@unlink( apply_filters( 'wp_delete_file', $medium ) );
    		@unlink( apply_filters( 'wp_delete_file', $original ) );
    
    		return $this->finished();
    	}

    Forgive the misunderstanding, my english is very basic…

  • Adeel

    Hey Justin W, i tried this but images are not getting saved, i m sorry for my poor english, only one image return the true value, rest just show blank, i think something wrong with the js or mytheme_options, can u please help me out from this mess???

  • Adeel

    hey Guys i just sorted it out, check this out….
    my_Script.js
    add this code into your JS file.

    jQuery(document).ready(function($) {

    $( ‘input.uploading’ ).click(function() {
    formfield = jQuery(this).attr(‘name’);
    tb_show(”, ‘media-upload.php?type=image&TB_iframe=true’);
    return false;
    });

    window.send_to_editor = function(html) {
    imgurl = jQuery(‘img’,html).attr(‘src’);
    jQuery(‘#’+formfield).val(imgurl);
    tb_remove();
    }

    });
    and add this to your theme option.php………..

    <input class="" name="” id=”" type=”text” value=”" />
    <input class="uploading" id="” type=”button” name=”" value=”Upload Image” />

  • Thomas

    I just have to say this worked PERFECTLY!!! you dah man!!! nice work. I see you did a tut on how to add a color picker and I am gonna go through that one next.

    I also noticed that you said you added a slider?…lol can’t wait for that one.

    If i can make a suggestion for another tutorial for you. I think a tutorial on adding sidebars would be really cool. I have tried to follow this one: http://wp.tutsplus.com/tutorials/theme-development/how-to-use-custom-sidebars-on-posts-and-pages/

    but can’t get my head around it as I am using what Aliso provided. So if you get a chance, this might be something cool to figure, wish i could, but I’m sure it would be a piece of cake for you.

    Keep up the good work and keep them coming.

  • Thomas

    Understandable, but none the less your tuts are wonderful and have helped a lot. Thank you very much.

    And if you get the time, maybe you can “take a peek” into a tut on adding those custom sidebars..lol (hint, hint) :)

  • Sathesh

    Thank you.. The class by ‘Aliso the Geek’ is far better than other scripts I’ve come across so far.. It’s clean and understandable.. and thanks for expanding it with the upload feature..

  • trswart

    I’m using the WordPress Settings API to create an options page for a plugin that handles images and links. I can register 3 add_settings_field calls and add 3 images and links to the site. Does anyone know of a way to dynamically let the user add a new field (add_settings_field) on the fly. For instance if it started with 1 field, but they needed a second for another picture.

  • Lap trinh trai tim

    HI.
    I think type of input tag should be file not text.

  • michellecantin

    Hey,

    Am I allowed to use this in a wordpress theme I will submit to themeforest?

  • Niţa Alexandr

    Hi,

    Very nice tutorial. Simple and clever. But I have next problem – when I press the Upload button, the black background is appearing, but the window itself – does not. Here is a screenshot: http://screencast.com/t/Ix7T9RdHBW3

    Thank you very much