Changed to TwentyTen


http://en.blog.wordpress.com/2010/04/26/new-theme-twenty-ten/

Unfortunately it doesn’t look like nav menu functionality is here yet, so whilst i do have a nice flashy theme to show off (i’ve been using it in WP 3.0 anyway), i can’t use the nav menus here, because the nav menu page just says “No peeking”. Oh well, given time i’ll have some menus… until then you can use the tags to the right to browse around (there’s not much here to see yet anyway).

You can expect to see Twenty Ten as the new default theme shipping with WordPress 3.0 on it’s release. I’m going to reserve any criticisms i have for the theme until release time (i’m not hugely bothered in total honesty, it’s just a theme, blogging is about content isn’t it?).

Advertisements

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 );
			else
				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;
		else
			$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;
			else
				$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':
					continue;
				case 0:
					unset( $form_el[0] );
					continue;
				default:
					$form_el[$el] = $data;
				break;
			}
		}
	}
	$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'] );
	else
		$tab_current = 0;

	if( !0 == $tab_current ) {
		$tab_current++;
		$draft_tab = 'tabindex="' . $tab_current . '" ';
		$tab_current++;
		$reset_tab = 'tabindex="' . $tab_current . '" ';
		$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] ) )
			continue;
		// If data but missing required input, carry onto next item
		if( !isset( $form_el[$field]['input'] ) ) {
			unset( $form_el[$field] );
			continue;
		}
		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…. 🙂

Manage dashboard widgets


Ever wanted a little more control in terms of controlling widgets shown in the dashboard area of your installation.

As of yesterday, i posted up a small plugin for managing widgets via google code. It’s my first shot at actually putting plugin code into a repository for consumption. As always the code i provide is free, holds very minor author attribution because i really don’t feel a need to own code i write and is provided “as is” with no guarantees.

This is a follow up blog to the thread i started here on the WordPress forums.

For those of you wish to simply skip straight to the code, you can view the repository or download a zip archive of the plugin from the following URLs.

Source code.
Download plugin.

Requirements:
WordPress version – 2.9 – 3.0
PHP Version – 5.x
MySQL Version – not applicable (whatever WordPress requires)

Support: (will it be available)
In a limited capacity yes, if you have problems with the code and want help making adjustments or fixing a bug please use the WordPress support forums, since i primarily focus my time in helping others on there, making it the most ideal place to catch me.

NOTES:
Utilises the settings api to register settings.
Stores all options in one row of the options table, not several.
Uses jQuery to allow smart setting of capabilites across all widgets.
This is an unfinished plugin, this is not considered a stable version, changes/fixes/additions are subject to feedback.

All that said and done, enjoy, and please do feel free share your thoughts..

Exploring dual WordPress installations locally


I took to spending some time attempting to install a series of virtual servers on the local PC today, after 4-5 hours of playing around last night with various httpd.conf settings i was really getting nowhere.

So i took backups of the important data, themes and plugins naminlgy, and set about reinstalling Uniform Server (it’s basically a WAMP app), and used the built in virtual host creation tool (i had tried this previously). For some reason the gods were in my favour this morning as nothing has gone wrong thus far and i now have 3 virtual servers available for whatever testing i like, from a volunteer perspective this is great because i can run the latest trunk code and the current stable version in 2 installs without the need to use a sub-directory structure.

http://trunk.localhost/ – 3.0 currently
http://current.localhost/ – 2.9.2

To aid myself in keeping the trunk version current i have tortoise svn installed and occasionally (when i think to do it), hit the update button and get a copy of the latest files direct from the SVN (how i got that all working is quite a surpise to me in honesty).

Once both installs were running (install, DB and user creation aside, zzzZZZZ), i wondered how i was going to manage code across two installs, ideally i don’t want to be editting two copies of every file i decide to fiddle with, well at least not at a theme or plugin level.

So onto Google, “Windows sync two folders” were my search terms. This lead me to find a free application from Microsoft called SyncToy. I now have my plugins and theme’s a click away from being in sync with one another. I could delve into the details of SyncToy, but i won’t… suffice to say the trunk install folder is where i do my coding, i then simply sync the folders at the end by running a routine in SyncToy.

I thought the whole process was quite an archievement, going from a working install, then stripping it down to nothing, then building it all up again, and adding a bunch of cool stuff. I’m sure there are plenty of people that do these kind of things on a regular, perhaps even daily basis, but for me some the procedure is still a little alien, so i’m quite happy with myself today…

WordPress pages, don’t trash, delete!


There is one downside to keeping your pages in the trash when using WordPress, and it’s something i only came to realise when i was looking over the rewrite rules array recently. After reading a series of posts to the hackers mailing list regarding permalinks i was curious to actually take another look at the array of rules, upon doing so i came to notice something, that i had never considered before.

Pages specifically will each at a minimum take up 5 rules in the array, usually 11, and more depending on whether you have sub-pages and attachments with any of those pages (the top level one, and the sub-pages). With a site driven in pages your rewrite array is likely to be huge simply as a consequence of using alot of pages.

UPDATE: Above is only true if running verbose rules (see further down)


The issue..

Pages in the trash will still remain in the rewrite array. Each page can take up quite alot of rules, if you leave old pages in the trash you’re actually losing out on a potential boost to load times.

Here’s what a very basic rewrite array could look like, look closely at how many rules the pages alone take up compared to say, … category rules, comment rules, or feed rules.

UPDATE: The below rules, are actually what’s known as verbose rules if i’m not mistaken, which was a result of me using /%post_name%/ for my permalink structure.

There are clear disadvantages to using certain permalink structures, and without me trying to babble my way through, i’d rather refer you here to Otto’s post regarding permalinks and the rewrite rules, which certainly has taken my understanding that little bit futher, thanks Otto.

<?php
$example = array
(
    [robots\.txt$] => index.php?robots=1
    [.*wp-atom.php$] => index.php?feed=atom
    [.*wp-rdf.php$] => index.php?feed=rdf
    [.*wp-rss.php$] => index.php?feed=rss
    [.*wp-rss2.php$] => index.php?feed=rss2
    [.*wp-feed.php$] => index.php?feed=feed
    [.*wp-commentsrss2.php$] => index.php?feed=rss2&withcomments=1
    [top-page-4/attachment/([^/]+)/?$] => index.php?attachment=$matches[1]
    [top-page-4/attachment/([^/]+)/trackback/?$] => index.php?attachment=$matches[1]&tb=1
    [top-page-4/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
    [top-page-4/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
    [top-page-4/attachment/([^/]+)/comment-page-([0-9]{1,})/?$] => index.php?attachment=$matches[1]&cpage=$matches[2]
    [(top-page-4)/trackback/?$] => index.php?pagename=$matches[1]&tb=1
    [(top-page-4)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?pagename=$matches[1]&feed=$matches[2]
    [(top-page-4)/(feed|rdf|rss|rss2|atom)/?$] => index.php?pagename=$matches[1]&feed=$matches[2]
    [(top-page-4)/page/?([0-9]{1,})/?$] => index.php?pagename=$matches[1]&paged=$matches[2]
    [(top-page-4)/comment-page-([0-9]{1,})/?$] => index.php?pagename=$matches[1]&cpage=$matches[2]
    [(top-page-4)(/[0-9]+)?/?$] => index.php?pagename=$matches[1]&page=$matches[2]
    [top-page-3/attachment/([^/]+)/?$] => index.php?attachment=$matches[1]
    [top-page-3/attachment/([^/]+)/trackback/?$] => index.php?attachment=$matches[1]&tb=1
    [top-page-3/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
    [top-page-3/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$] => index.php?attachment=$matches[1]&feed=$matches[2]
    [top-page-3/attachment/([^/]+)/comment-page-([0-9]{1,})/?$] => index.php?attachment=$matches[1]&cpage=$matches[2]
 --- *trimmed
)
?>


Now say i had decided i didn’t want to use pages on my site, so i moved them to the trash. The rewrite array above would hold onto over 50 rules for pages i was no longer using.

So is there ever a reason to flush out the trash, hell yes!… especially when it comes to pages.

There are good uses for the trash of course, i’m just pointing something out that may not otherwise be obvious for some users.

UDPATE: Having read Otto’s blog post about permalink structures i see there are clear disadvantages in particular custom structures, and that can be evidenced from looking at the sheer size of the rewrite array when using verbose rules, like those that were posted originally(above). I’ve trimmed the list as my original point doesn’t really matter too much now.. (the snippet still illustrates the issue with pages in verbose rules).

This does not negate the fact trashed pages are left in the rewrite array, if you’re using a permalink structure that uses verbose rules then you’ll want to keep the array as trim as possible, so ideally pages should never be trashed(delete), just to help clear them out the rewrite array.

I do see that this would be problematic to avoid, how could you deal with the rewrite rules, you’d probably need to regenerate them every time an item was trashed and/or restored (what a nightmare), at least once an item is intended to be deleted, as in gone forever, it makes sense to regenerate the rewrite array, you’re then essentially cleaning house and getting rid of old rules that no longer need apply.

It does make sense how the system currently works, and i take back what i said before. That will teach me for jumping the gun and assuming i know more then i do….hehe…