Tag Archives: WordPress

What is a Taxonomy and a Term?

Despite the words Taxonomy and Term being quite common, i’ve come to notice very few users know the difference between a Taxonomy and a Term.

Michael Fields made a very cool video using a metaphor to explain what Taxonomies and Terms are, for those of you still unclear on the differences or meaning please see the following video.

Category (Taxonomy)
– My Category One (Term)
– My Category Two (Term)

Post Tag (Taxonomy)
– My Tag One (Term)
– My Tag Two (Term)

Sorry to rant, i just had to get that off my chest..


WordPress plugin translation

Plugin translation has never for me seemed an easy task, i could never quite follow the mish mash of differing information around the web, and none of the guides available ever seemed to answer the specifics of what i needed to know about setting up a plugin for translation.

Well a couple of months have passed since i last put my head down and read up on the translation process, and fortunately for me the persistance in grasping the technique has paid off, i’ve managed (well i hope) to grasp the fundamental points of how to prepare a plugin for translation, and additionally how to run through a translation process.

Those of who would just prefer to see example code can skip straight to the plugin download.

NOTE: This is intended for code literate users, those of you who can already code but just need a quick run down on how to utilise the translation functionality inside a plugin.

Firstly your plugin will need to register a text domain for performing translation tasks, you can read more on that here. Of course the codex entry does leave a little to be desired, not really giving much in the way of an easy to follow example.

However, let’s just run by the parameters for load_plugin_textdomain very briefly.

1. $domain

This is basically the name for your text domain. You’ll have to reference this alot in your plugin, so it certainly helps to make it something easy to remember and quick to type. The name (or domain) declared here will also prefix all translation files used in your plugin, so do try to KISS.

2. $abs_rel_path

We will ignore this parameter because it’s no longer used as of WordPress 2.7, originally you would use this parameter to declare the path to the language files in your plugin, based on the WordPress ABSPATH. I will not be using this parameter for the example, so i’ll stop there.

3. $plugin_rel_path

I think the codex description sums up what this parameter does pretty well (not perfect though).

Relative path to WP_PLUGIN_DIR. This is the preferred argument to use. It takes precendence over $abs_rel_path

Ok, so now the description is out of the way, where should you place the call to load_plugin_textdomain?

As far as i know textdomains should be registered at the init stage, so depending on whether you want to translate front side, admin side or both, will depend where you hook the registration onto, but for the sake of this example i’ve hooked mine onto admin_init.

Inside the main function of my plugin class is a line like so.

				// The action hook
				add_action( 'admin_init' , array( &$this , 'register_plugin_settings' ) );

Various other pieces of code are present in the functions, so to keep this blog trim here’s the line inside the above referenced function that registers the textdomain.

				// How the text domain looks inside the register plugin settings function referenced above
					$this->l10n_prefix,                        // Plugin text domain reference
					false,                                     // False - deprecated parameter
					basename( dirname( __FILE__ ) ) . '/lang'  // Path to translation files

In the case of my example plugin(at the end of this post) i’ve used a variable to store the name of the textdomain, this means i don’t have to keep remembering the reference name, i simply type in the variable each time i want to make some text translatable.

More information and a very similar example of load_plugin_textdomain can be found here.

Here’s my text domain variable for reference.

			$this->l10n_prefix = 'translatable_demo';

Also note the third parameter used in the text domain registration, this sets where to look for the translation files, you can however (as the codex entry suggests), leave this blank and WordPress will assume they reside in the same folder as your plugin. Of course it’s good to keep things organised, i know i do(just a little), so i’ve opted to have the text domain reference a folder called “lang” (no quotes) inside my plugin’s folder.

Ok you’ve come this far, you’ve registered a text domain, what next?

Next you’ll need to start converting all the strings in your plugin you wish to make translatable, and this is probably the most easiest step in the whole process, and rather then repeat the guides and information already available i’ll simply point you here, and provide a very brief example below based on my example plugin.

Example strings:

$var1 = 'WordPress';
echo 'Hello world';

Translation ready: (remembering the text domain name)

$var1 = __( 'WordPress', 'translatable_demo' ); // __() return value
_e( 'Hello world', 'translatable_demo'); // _e() - echoed value

That’s it as far as making the plugin translation ready, honestly, what more did you expect?

Techinically there is nothing left to do on your part, the plugin can be translated, but a good practice for plugin developers (or so i hear) is to include a .POT file along with the plugin. In the most simple terms this is a base translation file, that will in essence be like any translation of your plugin, but not include a translation for each string, providing a .POT file just means a translator can pick up that file and get translating without having to run your plugin’s folder/files through a translation program (you save them a few minutes, but it’s really very easy, and the process is pretty much identical to that of the translator).

You can download the example plugin here or here(sorry couldn’t host it here, not allowed zips on WordPress.com blogs).
EDIT (02/07/13): New download link(hopefully this one won’t just random expire)

For those of you still interested in learning how to create a .POT file and developing language files read on, i’m going to run down the procedure i followed for creating a .POT file. I’m a windows user, so those of you on another OS, you’ll unfortunately have to look toward existing guides on how to translate. Anyone still with me, read on..

  1. Download and Install Poedit (it’s free).
  2. Open Poedit and click on New Catalog, under File.
  3. Fill in a name for the project, and choose the appropriate language you write in (not required but i tend to do it anyway).
  4. Click on the Paths tab at the top of the box.
  5. Click the second little icon, and put the directory location of your plugin in, for example “C:\Documents and Settings\USER\Desktop\MY_PLUGIN”
  6. Click on the Keywords tab at the top of the box.
  7. Click the second icon, and enter “__” (no quotes, and that’s two underscores).
  8. Click the second icon again, this time entering “_e”
  9. Now click the Ok button.
  10. A save dialog will appear, so enter a name for your POT file and change the extension to .pot
  11. Choose an appropriate place to save the file (you’ll probably want to choose to the language folder inside your plugin).
  12. Click Save.
  13. Various things will happen now and poedit will shows some boxes on the screen. Don’t do anything, this is what a translator will see when he or she is translatin your POT file. Just close the file, job done.

NOTE: It’s most likely a good idea to name your POT file with the same prefix the translation files will require. The prefix is the name you gave you text domain inside your plugin, ie. the first parameter.

Doing translations is the easy part, so i’m leaving that out of my blog for now, but if anyone finds this useful and would like to see an additional “How to make a translation file” or similar, then post a comment, let me know.

If you struggled to keep up with anything covered above, or you think anything above could do with refinement, again feel free to provide feedback, i’m not the world’s best blogger, and my mind can wander when writing, so critique is most definately welcome..

Improving the WordPress.org support forums

Things are really looking up right now, with WP 3.0, the MU merge, and now an improved WordPress.org website on the way, i can’t help but wonder what all you guys are hoping and praying for the new WordPress website to bring.

Jane recently posted here at the WordPress forums, to ask for feedback from the community on what we’d like to see changed or implemented on the WordPress website… quoting from the opening post by Jane..

I think we all know that as helpful as the support forums are in some ways, in other ways there’s much to be desired. As we will be doing some work on improving wordpress.org this summer, let’s hear your suggestions for how we can improve the support experience here on wordpress.org. Thanks!

Ref: http://wordpress.org/support/topic/405965

So i thought i’d open a poll and see if i can grab your attention for a few moments, you can vote and not even say anything, click as many options as you like, or add you own preference at the bottom.

Answers are based on the suggestions in the current forum thread linked above, and a few of my own (and i’ll happily add yours on request).

Go on… it’ll only take you a moment..

If you’d like to post any suggestions, either here or in the thread, i’ll be happy to add them to the poll.

For me, the thread tracking is the most crucial feature… but i’d be most interested to see how you guys (the WordPress community at large) feel about it, and what matters to you..

Until the next blog, that’s me out… 😉

Prevent front and posts page being the same

I’m sure those of you familiar with WordPress will be acustom to the two settings provided under Admin -> Settings -> Reading, known as the Front Page and the Posts Page. Whilst some users find their usage easy, and others find it somewhat confusing, these two settings have never been intended to share a page, in short both should never be set to the same.

The following simple jQuery based plugin will prevent a user from applying the same page to these settings. This could be useful for those of you providing clients with WordPress sites that do not understand such features and attempt to configure these both to the same page.

Plugin Name: Prevent front and posts page being the same.
Description: Prevents the posts page and front page settings be set to the same page.
add_action( 'admin_head-options-reading.php' , 'postspage_frontpage_not_same' );
function postspage_frontpage_not_same() {
	<script type="text/javascript">
	jQuery(document).ready(function($) {		
		// Get the current selected values
		var wp_frontpage = $('#page_on_front option:selected').val();
		var wp_postspage = $('#page_for_posts option:selected').val();
		if( wp_postspage != '' ) {
			$('#page_on_front').find('option[value=' + wp_postspage + ']').attr("disabled","disabled");
		if( wp_frontpage != '' ) {
			$('#page_for_posts').find('option[value=' + wp_frontpage + ']').attr("disabled","disabled");
		// Attach a change function to the front page dropdown
		$('#page_on_front').change(function() {
				// Re-declare the variable for use inside the scope of the function
				var wp_frontpage = $('#page_on_front option:selected').val();
				if( wp_frontpage != '' ) {
					// Find a matching option in posts page dropdown
					$('#page_for_posts').find('option[value=' + wp_frontpage + ']').attr("disabled","disabled");
				$('#page_for_posts').find('option[value!=' + wp_frontpage + ']:disabled').removeAttr('disabled');
		// Attach a change function to the posts page dropdown
		$('#page_for_posts').change(function() {
				// Re-declare the variable for use inside the scope of the function
				var wp_postspage = $('#page_for_posts option:selected').val();
				if( wp_postspage != '' ) {
					// Find a matching option in the front page dropdown
					$('#page_on_front').find('option[value=' + wp_postspage + ']').attr("disabled","disabled");
				$('#page_on_front').find('option[value!=' + wp_postspage + ']:disabled').removeAttr('disabled');

As always feel free to post a comment, or if you have any suggestions regarding the code, please do share them.

WordPress post or page has embed

Recently someone on the WordPress forums asked if there was an easy way to determine if a post has any content embedded and if so, a link could be provided to indicate the visitor should click it to see the full post where the embedded content is available.

The well known function the_excerpt() does not show embedded content, so an archive listing displaying excerpts would have no ordinary way to indicate content such as a Youtube video is available inside the full posting.

I wrote this very simple function for checking if a post(or page presumably) has any content embedded inside.

function has_embed( $post_id = false ) {
	if( !$post_id )
		$post_id = get_the_ID();
		$post_id = absint( $post_id );
	if( !$post_id )
		return false;

	$post_meta = get_post_custom_keys( $post_id );

	foreach( $post_meta as $meta ) {
		if( '_oembed' != substr( trim( $meta ) , 0 , 7 ) )
		return true;
	return false;

The function is not designed to output any link, or data, it’s simpy a true or false return statement so you can determine if a given post(or page) has embedded content, what you place inside the conditional check is up to you, but here’s a very basic example that i provided along with the function initially.

<?php if( has_embed() ) : ?>
	<a href="<?php the_permalink(); ?>" title="Click to see the Video">Click here to see the Video</a>
<?php endif; ?>

What goes inside that condition is entirely down to you and what you want to have there, the example is simply for illustration of how to use the function.

Code works under the current release 2.9.2 and also has been tested under WordPress 3.0 Beta2.

If you find the function useful please feel free to share a comment or rate the post, no registration is required but i do moderate your first couple of comments (can you blame me with all the spam around?).

Styling the WordPress forums

Bored: I was bored again so decided to work on the forum styling a bit more, i also decided to write a Greasmoneky script that works alongside the style. Again, only really any use to Firefox users (unless you know how to inject HTML and JS into a page), but it does have a couple of useful functions.

Pages: Places page links in the thread listing title column, so you can click straight through to whichever page of a thread you like.
Version: Added version 3.0 into the thread form (i’ll remove it when it’s no longer needed).
Thread Status: Moved the thread support dropdown into it’s own row of the form table.
Replies not Posts: Updated the “Posts” column on thread listing to show as “Replies” instead and updated count accordingly.

Changes overall, script & style, are to aid in moderation tasks on the forum, i’m quite chuffed so far, even with hacky work… 🙂

I can’t force Automatic to work on the forum and update it, but i certainly can write hacks for making the pages more enjoyable to use…

// ==UserScript==
// @name           wpforums
// @namespace      https://t31os.wordpress.com
// @description    testingstuff
// @include        http://wordpress.org/support/*
// ==/UserScript==
var $;
	if (typeof unsafeWindow.jQuery == 'undefined') {
		var GM_Head = document.getElementsByTagName('head')[0] || document.documentElement,
		GM_JQ = document.createElement('script');
		//GM_JQ.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js';
		GM_JQ.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js';
		GM_JQ.type = 'text/javascript';
		GM_JQ.async = true;
		GM_Head.insertBefore(GM_JQ, GM_Head.firstChild);
function GM_wait() {
	if (typeof unsafeWindow.jQuery == 'undefined') {
		window.setTimeout(GM_wait, 100);
	else {
		$ = unsafeWindow.jQuery.noConflict(true);
function letsJQuery() {

	$('<option value="3.0">3.0</option>').insertAfter('select#wp-version option[value="-1"]'); 
	$('select#wp-version option[value="0"]').text('Other'); 

	$('h2 span.button').removeAttr( 'class' ).css('float','right');
	$('h2 span a').html( 'Start Thread' );
	var thread_status = $('.form-table tr td:nth-child(2) p:nth-child(3)').html();
	if( thread_status ) {
		$('.form-table tr td:nth-child(2) p:nth-child(3)').remove();
		var new_row = '<tr><th>Status</th><td class="thread_status">'+thread_status+'</td></tr>';
		$( new_row ).insertAfter('.form-table tr:last-child');
	// Change posts heading to replies
	$('.widefat tr th:nth-child(2)').text('Replies');
	// Now some post count jiggery 
	$('.widefat tr td:nth-child(2)').each( function() {
		// Get the reply count
		var thread_post_count = parseInt( $(this).text() );
		// If the count is more than 30 we have more then 1 page.
		if( thread_post_count > 30 ) {
			// Append the title column with a new element
			// Work out the amount of pages
			var thread_pages = Math.ceil( thread_post_count / 30 );
			// Get the URL of the thread
			var thread_link = $(this).parent().find('td:first-child a').attr('href');
			// Figure out where the query string starts
			var query_string_pos = thread_link.indexOf("?replies=");
			// Check for the query string
			if( query_string_pos ) {
				// Clone the link, keeping only the base url
				var thread_page_link = ( thread_link.substring( 0 , query_string_pos ) );
				// Counter
				var i = 1;
				// Loop to build page links
				for( i = 1; i <= thread_pages; i++ ) {
					// Hard-coded limit of 25 pages
					if( i > 25 ) break;
					// Var to hold link code
					var page_link = '<a class="page-numbers" href="'+ thread_page_link +'/page/'+ i +'">'+ i +'</a>';
					// Append link to title element created earlier
					$(this).parent().children('td:not([class])').children('span').append( page_link );
		// Reply count is the posts count minus one, simples
		var thread_replies = ( thread_post_count - 1 );
		// 0 replies, hyphen is easier to spot, ie. a thread with no replies
		if( 1 > thread_replies )
			$(this).text( '-' );
			$(this).text( thread_replies );
	$('.post pre').click( function() { 
		// If the pre element has no style attribute
		if(! $(this).attr('style') ) {
			// Store current width in var
			var pre_w = $(this).width();
			// Set position to absolute
			$(this).css({ 'position':'absolute' });
			// If width is now reduced there's nothing to be done, so reset the change
			if( pre_w > $(this).width() )
			// Else no reset, insert dummy element to push content back down, matching the original element
				$('<pre style="border:0;height:' + ( $(this).height() ) + 'px;">&nbsp;</pre>').insertAfter(this);
		else {
			// If style is already applied, then we're resetting, remove style
			// Remove dummy element
		return false;
	// Stop deleted post links from being clickable (so i don't mistakenly follow any spammy links) 
	$('li.deleted .post a').click( function() { return false; });

— Original content below —

I had been feeling a little burnt out and bored today, so decided i’d revamp the Stylish template i’ve been using on the WordPress forums.

For those of you who don’t know what Stylish is, it’s an extension (or add-on if you prefer) for Firefox that allows you to apply any(pretty much) Firefox supported CSS selectors to any webpage, or a series of webpages (using a prefix).

Using Stylish means you can hide unwanted ads, restyle an area that you don’t like, or outright re-style a website, all with some basic (or complex if you like) CSS via an easy to use extension. A small box is shown on screen and changes can be previewed before saving, it’s faster than testing your own CSS on a local site as the preview button applies the custom CSS there and then.

Anyway, moving on, i spent some time writing some CSS to restyle the forum, so i don’t find it so frustrating to look at (seeing the same thing over and over does get naturally boring).

I am posting about this simply to offer the code to other Firefox users, who might be able to appreciate having a different look whilst using the forum, first though, let me show you some comparison images check out the pics, so you can decide if it’s really for you (of course testing the style takes only a few moments, and you don’t necessarily have to use Stylish, although frankly, using Stylish makes the task much easier).

WordPress Forums Thread Styling
WordPress Forums Thread Styling

WordPress Forums Thread List Styling
WordPress Forums Thread List Styling

WordPress Forums Profile Styling
WordPress Forums Profile Styling

It’s not a total overhaul, just some minor tweaks and changes here and there. There’s also some changes to the forum profile pages, but you’ll have to try out the style to see how it looks, the jpegs don’t really do the styling any justice at all.

Firefox users who have stylish, click your Stylish icon (toolbar by default), and select “Write new style -> Blank Style”, give the style a name (this isn’t important, so give it any name you like), then paste the following code into the bottom area, and click “Save”, or if you prefer click “Preview” to see the style in action.

@namespace url(http://www.w3.org/1999/xhtml);

@-moz-document url-prefix("http://wordpress.org/support/") { 
	Used across several pages 
	body { 
		background-color: #fff!important;
		background-image: url('https://t31os.files.wordpress.com/2010/05/75086759.gif')!important;
		background-repeat: no-repeat!important;
		background-position: top left!important; 
	p.login { font-size:120%!important;line-height:3em!important;padding-top: 3em!important }
	p.login small { font-size:100%!important }
	#pages { white-space:nowrap!important;}
	#pages .page-numbers, 
	.topicnav .page-numbers { color:#666!important;padding:6px 10px!important }
	#pages a.page-numbers, 
	.topicnav  a.page-numbers { background-color:#fafafa!important; }
	#pages a.page-numbers.prev,
	.topicnav a.page-numbers.prev,
	#pages a.page-numbers.next,
	.topicnav  a.page-numbers.next	{ background-color:transparent!important; }
	#pages span,
	.topicnav span { background-color:transparent!important;border-color:transparent!important }
	.bbcrumb { font-size:inherit!important;margin-top:1em!important;display:block!important; }
	.topicnav .col-6 { padding:0!important; }
	.topicnav p { margin:0!important; }
	.topictitle h2 { padding:0!important;margin:.8em 0!important; }
	h2.post-form { display:none!important; }
@-moz-document url-prefix("http://wordpress.org/support/forum") { 

	h2 > span.button { float:right!important;margin:0!important;line-height:1.1em!important; }
	#pagebody .button { -moz-border-radius:2px!important;border:none!important;background:none!important }
	#pagebody * { font-size:100%!important; }
	#pagebody h2 { font-size:2em!important }
	#pagebody h3 { font-size:1.65em!important; }
	#pagebody h4 { font-size:1.5em!important }
	.forumlist table { 
		border:1px solid #ccc!important;
	.forumlist .widefat * { font-size:inherit!important; }
	.forumlist .widefat td+td,
	.forumlist th { text-indent:.8em!important; }
	.forumlist th { border-bottom:none!important; color:#888; padding:0!important }
	.forumlist td { border-bottom:none!important; padding:0!important }

	.forumlist td.num { white-space:nowrap!important }
	.forumlist td.num:last-child { padding-right: 1em!important }
	.forumlist table th,
	.forumlist table td { background-color:transparent!important;line-height:2.2em!important; }
	.forumlist table td:first-child { padding-left:.5em!important; }
	.forumlist table a { outline:none!important; }
	.forumlist tr { background-color: transparent !important; }
	.forumlist tr > :first-child { padding:0!important }
	.forumlist .widefat tr > :nth-child(2) { text-align:center!important }
	.forumlist thead { background-color:#eaeaea!important }
	.forumlist .form-table { border-bottom:none!important; }
	.forumlist .form-table td > :not(p),
	.forumlist .form-table th > :not(p) { margin:.5em 0!important; }
	.forumlist .form-table tr+tr { border-top:1px solid #ccc }

	tr.alt { background-color: #fff!important; }
	tr.sticky { background-color: #FFFDE4!important; }
	tr.sticky.closed { background-color: #FFFcd0!important; }
	tr.message_active { background:#f1f1f1!important; }

	.postform { margin:2em 0 0!important }
	.postform fieldset > p { text-align:center!important; }
	.postform tr:nth-child(2) p { display:none;margin:.5em .5em .5em 0!important; float:left!important; }
	.postform tr:nth-child(2) p+p { display:block!important }
	.postform tr:nth-child(2) p+p+p { float:right!important }
	.postform tr:nth-child(3) p { display:none!important; }

	.postform input:not([class="ed_button"]),
	.postform select,
	.postform option { line-height:2em!important; }

	.postform textarea, 
	.postform input,
	.postform select { -moz-border-radius:.2em!important;border-color:#ccc!important }

	.postform input:not(.ed_button):not(#postformsub) { padding:0!important;text-indent:.5em!important; }
	.postform textarea { padding:.6em!important }

	#wp-version { width:10em!important; overflow:visible!important;white-space:nowrap!important }
	#wp-version option { width:auto!important; text-indent:.5em!important; }
	#wp-version option:empty {height:2em!important}
	#topic-paging { float:none!important;clear:both!important}

	#resolvedformsub { margin-top:1em!important}

	p.submit { padding:.5em 0!important; }
	p.submit #postformsub { padding:.5em!important }

	#profile-menu li:first-child { border:none!important}
	#post_content {  }
	#ed_toolbar { margin-bottom:0!important; } 
	.ed_button { font-size:100%!important;line-height:2em!important;padding:.8em!important;margin:0 .4em 0 0!important }
	#ed_pre { font-family:inherit !important}

@-moz-document url-prefix("http://wordpress.org/support/profile") { 

	#pagebody * { font-size:100%!important; }
	#pagebody h2 , 	
	#pagebody h3 , 
	#pagebody h4 { margin:0!important;padding:0!important;font-family:Georgia,Times,"Times New Roman",serif;font-weight:normal!important; }
	#pagebody h2 { font-size:2.2em!important }
	#pagebody h3 { font-size:1.65em!important; }
	#pagebody h4 { font-size:1.5em!important }

	.wrapper { margin:0 auto!important; }
	.wrapper .col-12,
	.wrapper .col-6 { 
		margin:0 auto!important;
		padding:0 14px!important;
		line-height: 3em!important;		
	.wrapper .col-6 { 
		border:1px solid #ccc!important;
		margin:0 14px 3em!important;
	#pagebody .topictitle h2 { margin:0 0 .5em!important; }
	.topicnav p { margin:0 0 1em!important;float:none!important;text-align:center!important }
	.topicnav .col-12 p { text-align:left!important;line-height:1.6em!important }
	#profile-menu { 
	#profile-menu li { padding:0!important;margin:0 0 .2em!important;border:none!important; }
	#profile-menu li a { padding:.2em 1em!important;border:1px solid #dadada!important;background:#f9f9f9;display:block!important; }
	#profile-menu li.current a { color:#fff!important;border-color:transparent!important;background:url("http://s.wordpress.org/style/images/download-tab-bg.png") repeat-x scroll left bottom #D54E21!important; }
	#profile-menu li:not(.current) a:hover { cursor:pointer!important }

	#useractivity { display:none!important; }

	.user-recent ol { 
	.user-recent > p { 
 		padding:0 1em!important;
	.user-recent h4 { 
		border-bottom:1px solid #ccc!important;
	.user-recent li { 
		line-height: 1.5em!important;
		list-style: none!important;
		text-align: right!important;
		padding:.2em 1em!important;
		border-top: .1em solid #fff!important;
		border-bottom: .1em solid #dfdfdf!important;
	.user-recent li:last-child { border-bottom:none!important; }
	.user-recent li a { display:block!important; }
	.user-recent li a, .user-recent li span {clear:both!important;float:left!important;text-align: left!important;}

	.user-recent .freshness { clear:both!important;font-weight:bold!important }
	#pagebody .user-recent li { font-size:.9em!important }
	#pagebody .user-recent li a { font-size:1.2em!important;line-height: 1.5em!important; }
	#userlogin { color:#888!important;background: #EAEAEA!important;line-height:2em!important;border:none!important;-moz-border-radius:0!important;border-bottom:1px solid #ccc!important; }
	#theuser { padding:0!important;background:#fafafa!important;-moz-border-radius:0!important;width:100%!important }
	#theuser * { margin:0!important;padding:0!important; }
	dl#userinfo { position:relative!important;width:80%!important;padding:.6em 0!important; } 
	#useravatar { margin: 1em!important; }
	#useravatar .avatar { width:4.3em!important;height:auto!important;border:none!important }
	#userinfo dt { line-height:1.4em!important;font-weight:normal!important;width:8em!important;text-align:left!important;margin:0!important }
	#userinfo dd { line-height:1.4em!important;margin:0 0 2px 0!important;display:block!important;white-space:nowrap!important; }
	#profile-form {
		margin: 0!important;
		padding: 0!important;
	#profile-form h3 { margin: 0!important;line-height:2em!important; }
	#profile-form table { border:1px solid #ccc; width:100%!important; background: #fafafa!important;margin: 0 0 1em!important; }
	#profile-form table tr { background: transparent!important; } 
	#profile-form th { text-indent: 1em!important;color:#808080!important; } 
	#profile-form td * { font:inherit!important }
	#profile-form th, 
	#profile-form td { 
		border-bottom: .1em solid #ccc !important;
	#profile-form label { padding:0!important; } 
	#profile-form table input { padding:0 0 0 .5em!important; } 
	#profile-form select, 
	#profile-form table input,
	#profile-form option,
	#profile-form ul,
	#profile-form li, 
	#profile-form label { 
	#profile-form p { line-height:1.5em!important; margin:0 0 1em!important; }
	#profile-form ul { margin:0 0 1em 1em!important; }
	#profile-form ul,
	#profile-form li { list-style: none inside!important; }
	p.submit { border:none!important; }
	th sup.required { float:right!important; margin-right: 1em!important; }

@-moz-document url-prefix("http://wordpress.org/support/topic") { 

	p.submit { padding:1em 0!important; }
	p.submit #postformsub { padding:1em!important }
	#topic-paging { margin:0!important }
	#pagebody { width:960px!important;margin:2.4em auto!important; }	
	#pagebody * { font-size:100%!important }
	#pagebody h2 { font-size:2em!important }

	#pagebody .wrapper { width:auto!important;margin:0!important; }
	#pagebody .col-10 { width:76%!important;margin:0 1.2%!important; }
	#pagebody .col-10 input { 		
		line-height: 2em!important;
		margin:0 .5em 0 0!important;
		padding:0 .5em!important;
		text-shadow:0 1px 0 #FFFFFF;
		background:url('http://s.wordpress.org/style/images/white-grad.png') repeat-x scroll left top #F2F2F2!important;
		font-size:1em!important;border:1px solid #bbb; 
	#pagebody .col-10 input:not([class*="ed_button"]) { 
	#pagebody .col-2 { padding:0!important;margin:0!important;width:20%!important; }

	#pagebody .post p {margin-bottom:1.5em!important}
	#pagebody .post p:last-child {margin-bottom:0!important}
	li.postitem { float:none!important;border:none!important;padding:0!important;width:100%!important;margin:0 0 1em!important }
	li.postitem.bozo { background-color:#ffffcc!important; }
	li.deleted { border-color:#f1aaaa!important;background-color:#FFE1E1!important; }

	.post { margin:0!important;padding:.6em 1em!important;font-size:inherit!important }
	.post p:last-child { margin-bottom:0!important; }
	.post pre , .post code { margin:0 0 10px!important;clear:both!important }
	.post blockquote { padding:7px 10px!important;border-left:10px solid #dadada!important; }

	.threadauthor { 
		margin: 0!important;
	.threadauthor {
	.threadpost { line-height:1.6em!important; }
	.deleted .threadauthor a { display:inline!important;color:#900!important; }
	.deleted .threadauthor p { border-bottom-color:#f1aaaa; }
	.bozo .threadauthor p { border-bottom-color:#f1f199; }
	.threadauthor br { display:none!important }

	.threadauthor p,
	.threadpost p { width:auto!important}

	.threadauthor p { margin:0!important;padding:.5em!important; }
	.deleted .threadauthor p { background:#f1aaaa; }
	.bozo .threadauthor p { background:#fafaa1}

	.threadauthor p small { float:right!important;margin: 0!important; }
	.threadauthor p > strong { /* author name*/ }		
	.threadauthor p small a + a + a { 		
		background:url('http://s.wordpress.org/style/images/white-grad.png') repeat-x scroll left top #F2F2F2!important;
		border:1px solid #bbb!important; 
	.threadauthor p small a:nth-child(7) { 
		background:url("http://s.wordpress.org/style/images/download-tab-bg.png") repeat-x scroll left bottom #D54E21!important;
	.threadauthor .avatar { 
		margin-right: .5em!important; 
	.threadauthor .authortitle { margin:0 1em 0 0!important; }
	.deleted .post p::after { content:""} /* Adds content, for testing whilst styling */
	ol#thread { margin:0 0 0 0!important; }
	ol#thread li:first-child { margin-top:0!important; }
	#resolvedformsel option { font-size:120% }

	.topicmeta * { margin:0!important;padding:0!important;font-size:inherit!important; }

	.topicmeta ul { margin:0 !important; }
	.topicmeta h4 { margin:0!important;border:0!important;line-height:2em;background-color:#f5f5f5!important; }
	.topicmeta .tags { margin-top:1em!important; }
	.topicmeta .tags p { background-color:#fafafa; }
	.topicmeta h4,
	.topicmeta ul, 
	.topicmeta fieldset { padding: .5em!important;background-color:#fafafa; }

	.topicmeta li { padding:0!important;line-height:2em!important; }
	.topicmeta li.resolution-flipper { padding:0!important }
	.topicmeta a.rsslink { background-position:right center!important;display:block!important }

	.topicmeta a[href$="/favorites"]::before {content:"View "}

	.topicmeta #tag,
	.topicmeta #tagformsub,
	.topicmeta #resolvedformsub { padding:0.5em!important;-moz-border-radius:4px!important; }
	.topicmeta #resolvedformsel { margin:0 0 .5em!important }
	.topicmeta #resolved select { line-height:1.7em!important;padding-left:0.2em!important }
	.topicmeta #resolved option { line-height:1em!important;padding:0.5em!important; }
	.topicmeta .tags p { padding:.5em 1em!important;margin-top:.5em!important; }

	.post-form { clear:both!important }
	.post-form tr {background:none!important}
	.post-form th {display:none!important}
	.post-form td {padding:10px 0!important;text-align:center!important}
	.post-form p {margin:0!important;border:0!important;text-align:center!important}
	.post-form textarea { background:#fff!important;width:90%!important;margin:10px 0!important;border-width:5px!important;border-color:#e7e7e7!important;padding:.5em!important; }
	.post-form textarea:focus { }

	form.postform table { background:#fafafa!important; }
	form.postform table p { line-height:3em!important;margin:0 28px!important;text-align:left!important }
	form.postform table p code { background:#fff!important;line-height:1.3em!important;padding:.6em .5em!important;-moz-border-radius:8px!important;margin-right:-5px!important }
	#pages { text-align:right!important;display:block!important;float:none!important;margin:0 auto!important;width:100%!important }
	div.col-4 { margin:0!important;padding:0!important; }
	p#pages { margin:0!important;display:block!important; }
	div.col-3 { margin-left:0!important;width:100%!important; }
	div.col-3 p#pages { text-align:center!important; }
	.admin { clear:both!important;color:#fafafa!important;text-align:center;padding:10px!important;margin:0 0 10px 0!important;background:#fafafa!important; }
	.admin a { 
		padding:.5em .7em!important;
		text-shadow:0 1px 0 #FFFFFF;
		background:url('http://s.wordpress.org/style/images/white-grad.png') repeat-x scroll left top #F2F2F2;
		font-size:1em!important;border:1px solid #bbb;
	.admin #topic-move { padding-top:10px!important;color:#000!important; }
	.admin #topic-move select { margin-right:10px!important}

	#ed_strong { margin-left:10px!important}
	.ed_button { font-variant:small-caps!important}

@-moz-document url-prefix("http://wordpress.org/support/view/no-replies") {
	tr.closed { display:none!important }

Try it out, let me know what you think. Feel free to post a comment, no registration needed, and it’s always nice to hear back from visitors.

QuickPress Category – Extending Functionality

Following on from a discussion on the WordPress.org support forums, and posts to both the ideas section, and trac which deals with bugs, features, enhancements and the general WordPress source code, i’m turning my efforts to blogging about changes i’d like to see in the QuickPress widget shown in the WordPress dashboard.

For reference, the relevant tickets and discussion.
Trac ticket: http://core.trac.wordpress.org/ticket/13038
Ideas ticket: http://wordpress.org/extend/ideas/topic/quickpress-category-need-hooks
Forum topic: http://wordpress.org/support/topic/317373

Seems WordPress.com users also want categories in QuickPress.

In my comments on the trac ticket i mentioned a proof of concept(PoC) for using arrays to sort and filter the form elements in the QuickPress widget. Now bear in mind this is still a concept and by no means is intended as a proper patch, but below you will find a rewrite of the QuickPress widget function that i spent some time working on and testing (it’s also in patch form for Trac if anyone wants a patch/diff to test against).

Click here to skip past the function code (it’s 205 lines).

function wp_dashboard_quick_press() {
	$drafts = false;
	if ( 'post' === strtolower( $_SERVER['REQUEST_METHOD'] ) && isset( $_POST['action'] ) && 0 === strpos( $_POST['action'], 'post-quickpress' ) && (int) $_POST['post_ID'] ) {
		$view = get_permalink( $_POST['post_ID'] );
		$edit = esc_url( get_edit_post_link( $_POST['post_ID'] ) );
		if ( 'post-quickpress-publish' == $_POST['action'] ) {
			if ( current_user_can('publish_posts') )
				printf( '<div class="message"><p>' . __( 'Post Published. <a href="%s">View post</a> | <a href="%s">Edit post</a>' ) . '</p></div>', esc_url( $view ), $edit );
				printf( '<div class="message"><p>' . __( 'Post submitted. <a href="%s">Preview post</a> | <a href="%s">Edit post</a>' ) . '</p></div>', esc_url( add_query_arg( 'preview', 1, $view ) ), $edit );
		} else {
			printf( '<div class="message"><p>' . __( 'Draft Saved. <a href="%s">Preview post</a> | <a href="%s">Edit post</a>' ) . '</p></div>', esc_url( add_query_arg( 'preview', 1, $view ) ), $edit );
			$drafts_query = new WP_Query( array(
				'post_type' => 'post',
				'post_status' => 'draft',
				'author' => $GLOBALS['current_user']->ID,
				'posts_per_page' => 1,
				'orderby' => 'modified',
				'order' => 'DESC'
			) );

			if ( $drafts_query->posts )
				$drafts =& $drafts_query->posts;
		printf('<p class="textright">' . __('You can also try %s, easy blogging from anywhere on the Web.') . '</p>', '<a href="tools.php">' . __('Press This') . '</a>' );
		$_REQUEST = array(); // hack for get_default_post_to_edit()
	$post = get_default_post_to_edit();
	$form_el = array();
	// Indexes - Visual order of fields
	$indexes = array( 'title' => 1, 'media' => 2, 'content' => 3, 'tags' => 4 );
	// Tab Indexes - Tabbing order of fields that have a tab index
	$tab_indexes = array( 'title' => 1, 'content' => 2, 'tags' => 3 );
	$form_el = array( 'indexes' => $indexes, 'tab_indexes' => $tab_indexes );
	$form_el = apply_filters( 'quickpress_fields', $form_el );
	// Check indexes if filter is attached
	if( has_filter( 'quickpress_fields' ) ) {
		if( !isset( $form_el['indexes'] ) || !is_array( $form_el['indexes'] ) || empty( $form_el['indexes'] ) )
			$form_el['indexes'] = &$indexes;
			$form_el['indexes'] = array_map( 'absint', &$form_el['indexes'] );
		if( isset( $form_el['tab_indexes'] ) ) {
			if( !is_array( $form_el['tab_indexes'] ) || empty( $form_el['tab_indexes'] ) )
				$form_el['tab_indexes'] = &$tab_indexes;
				$form_el['tab_indexes'] = array_map( 'absint', &$form_el['tab_indexes'] );
		else {
			// If the tab index array is unset, create as empty array
			$form_el['tab_indexes'] = array();
		$form_el['indexes'] = array_filter( $form_el['indexes'] );
		foreach( $form_el as $el => $data ) {
			$el = esc_attr( $el );
			switch( $el ) {
				case 'tab_indexes':
				case 'indexes':
				case 0:
					unset( $form_el[0] );
					$form_el[$el] = $data;
	$title_tab = $content_tab = $tags_tab = $draft_tab = $save_tab = '';
	if( isset( $form_el['indexes']['title'] ) ) {
		if( isset( $form_el['tab_indexes']['title'] ) ) 
			$title_tab = 'tabindex="' . $form_el['tab_indexes']['title'] . '" ';

		$title_field = array(
			'label'        => '<label for="title">' . __('Title') . '</label>',
			'label_before' => '<h4>',
			'label_after'  => '</h4>',
			'input'        => '<input type="text" name="post_title" id="title" '.$title_tab.'autocomplete="off" value="' . esc_attr( $post->post_title ) . '" />',
			'input_before' => '<div class="input-text-wrap">',
			'input_after'  => '</div>'
		$form_el['title']   = $title_field;
	if( isset( $form_el['indexes']['content'] ) ) {
		if( isset( $form_el['tab_indexes']['content'] ) )
			$content_tab = 'tabindex="' . $form_el['tab_indexes']['content'] . '" ';
		$content_field = array(
			'label'        => '<label for="content">' . __('Content') . '</label>',
			'label_before' => '<h4>',
			'label_after'  => '</h4>',
			'input'        => '<textarea name="content" id="content" class="mceEditor" ' .$content_tab . 'rows="3" cols="15">' . $post->post_content . '</textarea>',
			'input_before' => '<div class="textarea-wrap">',
			'input_after'  => '</div>'."\n\t".'<script type="text/javascript">edCanvas = document.getElementById(\'content\');edInsertContent = null;</script>'
		$form_el['content'] = $content_field;
	if( isset( $form_el['indexes']['tags'] ) ) {
		if( isset( $form_el['tab_indexes']['tags'] ) )
			$tags_tab = 'tabindex="' . $form_el['tab_indexes']['tags'] . '" ';
		$tags_field = array(
			'label'        => '<label for="tags-input">' . __('Tags') . '</label>',
			'label_before' => '<h4>',
			'label_after'  => '</h4>',
			'input'        => '<input type="text" name="tax_input[post_tag]" '.$tags_tab.'value="' . get_terms_to_edit( $post->ID , 'post_tag' ) . '" />',
			'input_before' => '<div class="input-text-wrap">',
			'input_after'  => '</div>'
		$form_el['tags']    = $tags_field;
	// Sort index by array values
	asort( $form_el['indexes'] );
	if( isset( $form_el['tab_indexes'] ) && !empty( $form_el['tab_indexes'] ) )
		$tab_current = max( $form_el['tab_indexes'] );
		$tab_current = 0;

	if( !0 == $tab_current ) {
		$draft_tab = 'tabindex="' . $tab_current . '" ';
		$reset_tab = 'tabindex="' . $tab_current . '" ';
		$save_tab  = 'tabindex="' . $tab_current . '" ';
	echo '<form name="post" action="' . esc_url( admin_url( 'post.php' ) ) . '" method="post" id="quick-press">';
	do_action( 'quickpress_before_fields' );
	// Fields to be output are required to have a place in the index
	foreach( $form_el['indexes'] as $field => $index ) {
		// If no data for this index item, carry onto next item
		if( !isset( $form_el[$field] ) )
		// If data but missing required input, carry onto next item
		if( !isset( $form_el[$field]['input'] ) ) {
			unset( $form_el[$field] );
		if( 'content' == $field ) {
			if ( current_user_can( 'upload_files' ) ) {
				echo "\n\t";
				echo '<div id="media-buttons" class="hide-if-no-js">';
				do_action( 'media_buttons' );
				echo "\n\t";
				echo '</div>';
		echo "\n\t";
		// Before label
		if( isset( $form_el[$field]['label_before'] ) )
			echo $form_el[$field]['label_before'];		
		// Label
		if( isset( $form_el[$field]['label'] ) )
			echo $form_el[$field]['label'];
		// After label
		if( isset( $form_el[$field]['label_after'] ) )
			echo $form_el[$field]['label_after'];		
		echo "\n\t";
		// Before input
		if( isset( $form_el[$field]['input_before'] ) )
			echo $form_el[$field]['input_before'];
		// Input
		echo $form_el[$field]['input']; // Required toward the top of the loop
		// After input
		if( isset( $form_el[$field]['input_after'] ) )
			echo $form_el[$field]['input_after'];
	do_action( 'quickpress_after_fields' );
	echo "\n\t" . '<p class="submit">';
	echo "\n\t\t" . '<input type="hidden" name="action" id="quickpost-action" value="post-quickpress-save" />';
	echo "\n\t\t" . '<input type="hidden" name="quickpress_post_ID" value="' . (int) $post->ID . '" />';
	echo "\n\t\t" . '<input type="hidden" name="post_type" value="post" />';
	wp_nonce_field( 'add-post' ); 
	echo "\n\t\t" . '<input type="submit" name="save" id="save-post" class="button" ' . $draft_tab .'value="' . esc_attr('Save Draft') . '" />';
	echo "\n\t\t" . '<input type="reset" value="' . esc_attr( 'Reset' ) . '" class="button" ' . $reset_tab .'/>';
	echo "\n\t\t" . '<span id="publishing-action">';
	echo "\n\t\t\t" . '<input type="submit" name="publish" id="publish" accesskey="p" ' . $save_tab . 'class="button-primary" value="' . 
	( current_user_can( 'publish_posts' ) ? esc_attr( 'Publish' ) : esc_attr( 'Submit for Review' ) ) . '" />';
	echo "\n\t\t\t" .'<img class="waiting" src="' . esc_url( admin_url( 'images/wpspin_light.gif' ) ) . '" />';
	echo "\n\t\t" . '</span>';
	echo "\n\t\t" . '<br class="clear" />';
	echo "\n\t" . '</p>';
	echo "\n" . '</form>';
	echo "\n";
	if ( $drafts )
		wp_dashboard_recent_drafts( $drafts );

If you’d like to test out the code, simply overwrite the original function in wp-admin/includes/dashboard.php with the one provided above.

Do be aware, by replacing this function, nothing will visually change in the widget yet, all this code does is provide a rewrite of the existing function, making it pluggable. The new hooks allow resorting of the form fields, resorting the tab index or removing it all together, aswell as allowing the removal of unwanted form fields, eg. tags.

The rewritten function provides one new filter and two new actions. Here they are for reference to save you having to look over all the code.

// Contains the fields indexes(positions) and tab indexes
// Also supports adding new fields
apply_filters( 'quickpress_fields', $form_el );

// Runs before the quickpress form fields have been output
do_action( 'quickpress_before_fields' );

// Runs after the quickpress form fields have been output
do_action( 'quickpress_after_fields' );

The quickpress_fields filter available contains only two keys, indexes and tab_indexes. Indexes is an array of key => value pairs representing input positions(key is the field, value the position), this in the most simple terms determines the order in which the various inputs display in the widget, but it’s also an integral part of the new filtering system, only items with a key and value in this array are displayed, if a filter were to remove that index it would in effect remove that field from the widget.

Tab indexes are simply for accessibilty and simply exist to aid users adding additional fields, and therefore can be adjusted or removed totally, the fields will render appropriately regardless of whether they are given a tab index. Tab indexes are the order in which the tab key on your keyboard moves through the various items of a webpage or form. By default the tax indexes in the widget as they exist in WordPress right now are…

Name – Tab-index
Title – 1
Content – 2
Tags – 3
Save Draft – 4
Publish – 5

The tab index array, like the main index array is comprised of key => value pairs, again key is the field name and value is the tab index. By passing these into the filter, they can be adjusted by users(you/me/anyone) alongside any extra fields we choose to add in via hook.

The new actions like any other in WordPress are just a quick shortcut to inserting some extra content into an area, in this case a dashboard widget, either before or after the existing fields (it didn’t make sense for me to place these in static positions amongst the existing fields since i’ve provided a method for sorting – so before and after fields were my preference).

Code speaks louder than my blabbering though, so here’s a basic example filter that adds a new text field below the existing ones. Please see the notes that follow the example.

add_filter( 'quickpress_fields' , 'my_quickpress_fields' , 10 , 1 );

function my_quickpress_fields( $form_array ) {
	// Add new field into index *required when adding new fields via this filter*
	$form_array['indexes']['myfield'] = 6;
	// Invalid (tab index is simply for re-sorting, if required)
	//$form_array[tab_'indexes']['myfield'] = 6;
	// Register a new element into the form array 
	// Required, array - key is the new field name, value should be an array of key -> value pairs for the supported fields
	$form_array['myfield'] = array(
		// Not required
		'label'        => '<label for="myfield">' . __('My field') . '</label>',
		// Not required
		'label_before' => '<h4>',
		// Not required
		'label_after'  => '</h4>',
		// Required
		'input'        => '<input tabindex="6" type="text" name="myfield" value="" />',
		// Not required
		'input_before' => '<div class="input-text-wrap">',
		// Not required
		'input_after'  => '</div>'
	// Return the data
	return $form_array;

Looks simple enough right? Do bear in mind, this doesn’t mean you can just go plonking in any form elements you want and expect them to save data, post.php does have limitations on what $_POST data it expects and will save.

The good news is, custom taxonomies are already supported, which whilst exciting is still held back by restrictions that leave us stuck using the “post” post_type. I’d have liked to have been able to add an additional filter to allow switching the post type(tried already), but due to restrictions in how post.php deals with the data sent from quickpress unfortunately that’s something that will have to be given much further consideration and more brainstorming as it would require patching post.php to accept something other than posts from QuickPress.

The above code example is a working example, in so much that it will appear as an element in the QuickPress widget, it won’t however save data(and only an illustration to show the available and required items), because as said above, post.php must receive one of the supported $_POST fields.

However!! below are some working examples filters and actions you can use alongside the new function.

Category Dropdown

add_filter( 'quickpress_fields' , 'my_quickpress_fields' , 10 , 1 );

function my_quickpress_fields( $form_array ) {
	$form_array['indexes']['category'] = 2;
	$form_array['category'] = array(
		'label'        => '<label for="post_category">Category</label>',
		'label_before' => '<h4>',
		'label_after'  => '</h4>',
		'input'        => '<p>'. wp_dropdown_categories( array( 'echo' => 0 , 'hide_empty' => 0 , 'name' => 'post_category[]' ) ) . '</p>'
	return $form_array;

Hidden Category Selection

add_action( 'quickpress_after_fields' , 'my_quickpress_fields' );

function my_quickpress_fields( ) {
	echo '<input type="hidden" name="post_category[]" value="23" />';

NOTE: 23 is an example category ID (change accordingly)

Remove Post Tags & Media Uploads from QuickPress

add_filter( 'quickpress_fields' , 'my_quickpress_fields' , 10 , 1 );

function my_quickpress_fields( $form_array ) {
	unset( $form_array['indexes']['tags'] , $form_array['indexes']['media'] );
	return $form_array;

If you like the function replacement, please show your support (pingback, trackback, etc), hopefully someone on the dev team will spot the idea and be able to create something more inline with WordPress coding practices as part of future functionality for the widget (or i can always refine my code, if someone wants to pick it apart, kindly).

Comments welcome…. 🙂