How to implement WordPress Media Gallery in plugins

WordPress Media Gallery can prove itself very useful in plugins that make use of images, such as slideshows or carousels. Implementing the Media Gallery in plugins using nothing more than hooks and filters is a fairly simple process. This article assumes that you know how to use actions and filters and how AJAX works in WordPress.

The scenario

Learning by example is the fastest way so let’s set up a task: we want to create a plugin that adds a new panel in post/page editing screen and into that panel there’s a button that when clicked will open a modal window of WordPress Media Gallery that allows users to attach image(s) to the edited post or page.

Media gallery demo  - Post meta panel

All that will be accomplished simply by setting $_GET or $_POST variables using different filters and than, by checking if our variable is set, the output of Media Gallery will be altered to contain some custom HTML needed in this demonstration.

The steps

1. On admin init, we want to check if currently viewed page is WordPress’s media-upload.php and if our variable is set (user clicked the link set up by the plugin that opens media-upload.php with a GET variable on it).

Next, if conditions above are met, we set up a filter to add our own buttons and also add a JavaScript file for functionality.

// store GET/POST var name into a constant
define('CBD_VAR_NAME', 'cbd_media');
// store plugin URL in constant
define('CBD_PLUGIN_URL', plugin_dir_url( __FILE__ ));

/**
 * On admin_init, check if page is media page and if plugin variable is set.
 */
function CBD_is_media_page(){
	
	$is_plugin_triggered = isset( $_REQUEST[ CBD_VAR_NAME ] );	
	
	if( $is_plugin_triggered ){
		// Set filter on attachment fields to add new elements for each image
		add_filter('attachment_fields_to_edit', 'CBD_add_attachment_fields', 10, 2 );
	}

	global $pagenow;
	if( $is_plugin_triggered && 'media-upload.php' ==  $pagenow ){
		wp_enqueue_script(
			'cfd-media-setup', 
			CBD_PLUGIN_URL.'assets/script/media_setup.js' , 
			array('jquery')
		);
	}
}
add_action('admin_init', 'CBD_is_media_page');

The interesting thing above is filter attachment_fields_to_edit ( in WordPress 3.5.1 filter is applied in file wp-admin/includes/media.php on line 1013 ). It passes 2 arguments: first is an array containing all form fields for current media item being processed and the second is the post object returned from database.

/**
 * Callback function for filter attachment_fields_to_edit
 */
function CBD_add_attachment_fields( $form_fields, $post ){	
	// check if item is image
	if ( 'image' != substr( $post->post_mime_type, 0, 5 ) ){
		return $form_fields;
	}
	
	// create a nonce
	$ajax_nonce = wp_create_nonce('cdb_set_post_image');
	// text of link displayed in media gallery
	$link_text = __('Media Gallery Demo - Attach image to post', 'cbd_media');
	// create the HTML for our link
	$html = <<<HTML
		<tr class='submit'>
			<td></td>
			<td class='savesend'>
				<a href="#" onclick="CBD_set_post_image( {$post->ID}, '{$ajax_nonce}' ); return false;">{$link_text}</a>
			</td>
		</tr>
HTML;
	// put additional HTML in buttons array
	$form_fields[ 'buttons' ] = array(
		'tr' => $html
	);
	
	return $form_fields;
}

Our filter callback function simply creates a new table row containing a link that when clicked will send some details to a JavaScript function defined in file media_setup.js. As you may have noticed, we used key buttons from $form_fields; under this key are stored the default buttons in media gallery.

Now we have our button. Whenever media gallery file will be opened having our variable on the link, the default buttons ( like Insert into post ) will be replaced by our link. Next, let’s handle file uploads.

There are 2 cases here: the simple browser upload and SWF upload. For the SWF upload we’ll simply use filter upload_post_params to set up our variable (for WordPress 3.5.1, filter is applied in file wp-admin/includes/media.php on line 1504). This way, whenever an upload made with the gallery opened from our plugin is completed, it will send on $_POST our variable too so the plugin knows how to manage the image and only show our button instead of WordPress’s.

// add our variable to SWF upload params
function CBD_swfupload_post_param( $params ){
	// check if our plugin initiated the upload
	if( !isset( $_GET[ CBD_VAR_NAME ] ) ){
		return $params;
	}
	// set a post param to know whe're uploading an image for our plugin
	$params[ CBD_VAR_NAME ] = 1;
	/* 
	 * if short param is set, after upload async-upload returns only the image id and a new AJAX request will be made for the actual content. 
	 * If it isn't set, it will return directly the HTML and POST variable will be available.
	 */
	$params['short'] = 0;
	return $params;
}
add_filter('upload_post_params', 'CBD_swfupload_post_param', 15, 1);

For the simple browser upload things are a bit tricky. There’s no filter to be used here to add our field to the form so we’ll have to hack into the form action URL and add our variable there. Not the best solution but will do the job.

// filter urls
function CBD_hijack_upload( $url ){
	// if url is for media upload file and our variable is set, add our GET variable
	if( strstr( $url, 'media-upload.php') && isset( $_GET[ CBD_VAR_NAME ] ) ){
		// add variable
		$url = add_query_arg(
			array( 
				CBD_VAR_NAME => 1 
			), $url
		);
	}
	return $url;
}
add_filter('admin_url', 'CBD_hijack_upload');

Upload is all covered. Next we have to handle gallery filtering. Again, we use a filter called media_upload_mime_type_links that will allow us to put a hidden input with our variable that will get send whenever a user uses filtering in media gallery.

// use media types links filter to put hidden field in filtering form
function CBD_filter_form_param( $links_arr ){
	// again, check that our variable is set
 	if( !isset( $_GET[ CBD_VAR_NAME ] ) ) 
		return $links_arr;
	// input
	$input = sprintf( '<input type="hidden" name="%s" value="1" />', CBD_VAR_NAME );
	// add our variable as a hidden input field
	if( isset( $links_arr[0] ) ){
		$links_arr[0] .=  $input;
	}else{
		$links_arr[] = $input;
	}
	return $links_arr;	
}
add_filter('media_upload_mime_type_links', 'CBD_filter_form_param', 1, 10);

This is it. Whenever a user opens media-upload.php with variable cbd_media being set, it will see our link and not WordPress’s default. Selected images can be stored in post meta for example and used depending on what you plugin functionality is.

But wait, how do I use all this?

Well, basically, this can be used in any way needed. Below is an example that creates a new panel in post/page editing that contains the link for opening media gallery and sends the selected image ID back to PHP to be used in any suitable way you may see fit.

Let’s start by creating the panel. For this, we’ll use function add_meta_box().

// register meta boxes on post/page editing
function CBD_post_meta_panel(){
	add_meta_box( 'CBD-Media', __('Media Gallery Demo', 'cbd_media'), 'CBD_meta_box_output', 'post', 'normal', 'high' );
	add_meta_box( 'CBD-Media', __('Media Gallery Demo', 'cbd_media'), 'CBD_meta_box_output', 'page', 'normal', 'high' );
}
add_action('admin_init', 'CBD_post_meta_panel');

Next, let’s create the function that actually puts something into our new meta boxes.

/**
 * Meta box output on posts and pages
 */
function CBD_meta_box_output(){
	global $post;
	$url = add_query_arg(
		array(
			'type' 			=> 'image', // tell WP we want images
			'tab' 			=> 'library', // open library tab
			'post_id' 		=> $post->ID, // set post id
			CBD_VAR_NAME 	=> 1, // plugin variable to flag that our plugin opened the gallery
			'TB_iframe'		=> 1 // tell WP that gallery is opened into iframe
		), 'media-upload.php'
	);
?>
<a href="<?php echo $url;?>" class="thickbox button"><?php _e('Click me to open Media Gallery', 'cbd_media');?></a>
<?php	
}

And last thing to do in PHP is to create the AJAX response function needed to process the data sent when user selects a certain image from gallery.

// ajax response
function CBD_set_image(){
	// check the nonce
	if( !wp_verify_nonce($_POST['nonce'], 'cdb_set_post_image') ){
		die( __("Sorry, nonce didn't pass.", 'cbd_media') );
	}
	
	$image_id 	= (int)$_POST['image_id'];
	$post_id 	= (int)$_POST['post_id'];

	/*CODE TO PROCESS THE DATA AND OUTPUT A RESPONSE TO jQuery*/
	
	exit();
}
add_action('wp_ajax_cbd_set_image', 'CBD_set_image');

Remeber the JavaScript file from function CBD_is_media_page()? Here’s a very simple implementation that sends the information with POST to function CBD_set_image() (if all this seems confusing, please see AJAX in plugins).

var CBD_set_post_image = function( image_id, ajax_nonce ){
	// send data with POST
	jQuery.post( ajaxurl, {
		'image_id' 	: image_id,
		'post_id'	: post_id, // post_id variable is set by WordPress
		'nonce' 	: ajax_nonce,
		'action' 	: 'cbd_set_image'
	}, function( response ){
		alert( response );
	});	
}

For any questions or ideas of improvement, leave us a comment, we’re all trying to get better here and sharing is the only way.