WishlistCast goes open source

Over 5 years ago, I released the first version of Wishlistcast – a plugin to link the Nanacast shopping cart and the popular Wishlist Member membership plugin for WordPress.

Since its release, countless folks have downloaded the free Wishlistcast plugin – and it has benefited greatly from the feedback provided.

In addition, it has connected me with clients, fellow WordPress enthusiasts, Nanacast specialists and opened doors to a wide range of diverse coding projects.

It’s been an absolute blast and I’ve thoroughly enjoyed the ride.

However… all things come to an end… and after turning down several WordPress membership projects this year, and too often finding “wishlistcast”-related queries at the bottom of my inbox – I’ve realised it’s time for me to open Wishlistcast up so others can work on it.

To that end I have open-sourced the code for Wishlistcast. Any WordPress-PHP developer can now access and feel free to modify the code here:


You can download the latest version of the plugin here:


You can get the “PRO” version (previously only available to my personal clients) here:


Note that these are offered completely on an “as-is” basis. (They DO work with latest WordPress and Wishlist Member and Nanacast). I can no longer offer any sort of support or installation service for these plugins. If you need technical help, then any competent WordPress developer will be able to get these up and running. Sites like UpWork.com or Fiverr.com are a good place to find skilled people for small tasks. If you need WordPress specialists for larger projects, then I HIGHLY recommend TopTal.

And if you’re wondering what I’m up to…

Nowadays I’m focussing on realtime and mobile app development using full-stack Javascript and MongoDB (Meteor) – which is quite different from WordPress/PHP and MySQL. If you’re interested, you’ll find me over at wellcraftedapps.com.

WishlistCast Plugin Security

A client of mine recently had some dramas with HostGator making an unannounced modification to her server’s mod_security which broke her site – specifically, it blocked Nanacast from talking to WishlistCast – so her new members that bought her online program were not getting added to her Wishlist membership. You can imagine the implications to her business. This is the exact issue I describe in more detail here, which is the most common cause of “Wishlistcast not working” emails and skype messages I receive. When speaking to Hostgator support, they provided my client with this information:

(11:03:30pm)HG Support.:I would highly recommend you contact that plugins developer. Any developer worth thier salt knows sending anything via GET is highly insecure.
(11:03:44pm)HG Support.:This is especially true when sending a security code, like was the case here.
(11:03:50pm)client:which developer
(11:03:57pm)HG Support.:The one for wishlist
(11:04:27pm)HG Support.:Sending a jumble of letters like that is seen by nearly any security software as a spam request, cause frankly it looks like one
(11:04:39pm)client:oh ok
(11:04:56pm)HG Support.:Combine this with WordPress’s javascript concatenation, and you have a spammy request URI and jumbled javascript being sent via an ajax call to another file.

Anyway, ignoring the complete FUD about javascript concatenation and Ajax requests (which have nothing to do with anything here), and the dig at my potential lack of developer salt-worthiness (really? I’m crushed!), I thought it would be constructive to transparently explain the design and security implications of using the Wishlistcast plugin. I am open to any constructive feedback and suggestions for improvement.

Background / Overview

The WishlistCast plugin adds an api end-point URL to your Wishlist site (http://www.yoursites.com/wp-content/plugins/wishlistcast/wishlistcast_api.php), allowing Nanacast (or technically another program) to add new users to your Wishlist membership.
When a new user is added to your Nanacast membership, Nanacast makes an Outgoing API call to the WishlistCast plugin API requesting it to “add a new user”. The Outgoing API call is a HTTP request – and the information WishlistCast needs to authorise and fulfill the request is sent as a combination of GET and POST parameters.
In order to authenticate the request, WishlistCast uses a “security_code” parameter, which is unique to your wishlist installation. You can think of this as an “API key”.
Because Nanacast can be configured to send multiple (up to 5) Outgoing API requests, and each of these can go to potentially different wishlist installations, the security_code parameter is sent as a GET parameter (so it can hold a different value for each Outgoing API Request).
In English what this means is – if you have multiple Wishlist membership sites, and you want to add someone to single Nanacast membership and have this add them to all of your wishlist sites, Nanacast needs to talk to each site individually. To do this within Nanacast’s design, you must pass the security_code as a GET parameter so it is unique to each outgoing call.

Security Implications of Using GET Parameters

The main security concerns around GET parameters, is that – because they form part of the URL – they are cached in a user’s browser, and are also open to being “packet sniffed” if sent over a WiFi network (e.g. browsing at your local coffee shop). They are also commonly recorded in your server’s history logs – where they are available if someone has access to (or hacks) your computer server.
However – in our case – it is not users who are browsing to the wishlistcast_api.php URL – so there are no concerns with browser history or WiFi packet sniffing.
The HTTP request is being sent from Nanacast directly to WishlistCast. It is a computer-to-computer call, with no humans or browsers in between.
So – the only real issue is that the URL with the security_code parameter might be visible in your server log. Of course, if someone has access to your server log, there are far easier ways for them to do damage than trying to figure out how to hack some obscure wishlistcast_api…

How WishlistCast Handles the Nanacast Request

Here is the code from the top of the WishlistCast plugin:
$f = array_merge( $_POST, $_GET );

// v1.4.6 introduce transaction logging with replay capability (for use by support)
$replaying = false;
if ( array_key_exists( 'replay', $f ) || !empty( $f['replay'] ) ) {
 // Note: The transaction replay functionality is not included in the public release
 if ( file_exists( dirname( __FILE__ ) . '/replay/replay_txns.php' ) ) {
 include_once(dirname( __FILE__ ) . '/replay/replay_txns.php');

if ( !array_key_exists( 'mode', $f ) || $f['mode'] == '' ) {
 return; // Not executing as a result of being called from Nanacast - probably plugin activation
As you can see – the very first line of code merges the GET and POST parameters into a single array of parameters (called “$f”) – with priority given to the GET parameters in case of duplicate parameter names.
Then there is a block of code that checks if this is a transaction replay. The code to support “replay” functionality  is only available in my personal copy of the plugin and is used for support purposes – so even if someone sent a “replay” parameter, the file_exists check would fail.
Finally, there is a “sanity check” test to see if the “mode” parameter is present. This early check essentially tests to see if this call most likely originated from Nanacast, which ALWAYS sends a “mode” parameter. If this “mode” parameter is not present, the code returns (ends) immediately. You can see this for yourself if you visit the wishlistcast_api.php URL in your browser – you will just get a blank screen.
After this, there is some code to hook WishlistCast to WordPress (so we can talk to Wishlist) as well as set up logging. Note that adding Wordpress happens AFTER we have already grabbed our GET and POST parameters into $f. WordPress is not processing our parameters or doing any kind of javascript concatenation or Ajax mumbo-jumbo.
Then, there is this code to authenticate the request:
if ( isset( $f['verify_field'] ) && isset( $f['verify_value'] ) && ($f[$f['verify_field']] != $f['verify_value']) ) {
 logmsgandClose( 'Request ignored. Verification failed: ' . $f['verify_field'] . ' value is [' . $f[$f['verify_field']] . ']. Verification value is [' . $f['verify_value'] . ']' );

// Check secret key matches Wishlist Generic secret key from Wishlist Integration panel
if ( class_exists( 'WLMAPI' ) ) {
 $wishlist_security_code = WLMAPI::GetOption( 'genericsecret' );
 if ( $wishlist_security_code != '' && $wishlist_security_code != $f['security_code'] ) {
 logmsg( "Incoming Secret Key '" . $f['security_code'] . "' does not match the Secret Key in WishList Generic Integration settings", PEAR_LOG_NOTICE );
 die( "Incoming Secret Key '" . $f['security_code'] . "' does not match the Secret Key in WishList Generic Integration settings" );
 unset( $wishlist_security_code );
The first block is an undocumented feature that allows you to add a verification field and value to the request – which allows you to control whether a particular request is handled or not. The specific use case for this is a client I had who wanted to manually verify payments before adding users to a membership. So I created a hidden “is_paid” field inside their nanacast membership with a default value of “N”, and on receipt of payment, he would edit the users nanacast membership and change the field to a “Y”. Ugly but effective. The verify field name was set to is_paid and the verify value was set to “Y”.
The second block compares the security_code parameter with the Wishlist Generic Secret code from your Wishlist installation.
You will notice that (if logging is enabled) I do log the (incorrect) security code that was passed in, however I do NOT log the expected value. So someone browsing the wishlistcast log file would not be able to use this to uncover the real security code.

TL; DR – Short Version

The wishlistcast_api code is designed with a set of sensible security checks at the start of the code to ensure that only valid, authenticated requests are processed. All of the code in the wishlistcast plugin follows best coding security practises to the best of my knowledge.

What About Using HTTPS?

You could install a SSL certificate on your site and send the request using an encrypted HTTPS call. Because the call is secured at the network level, this would make the call unreadable to an attacker who is able to monitor the network traffic between nanacast and your wishlist server. However, the URL (with parameters) would probably still appear unencrypted in your server logs, so this would result in NO improvement in security.

What Do You Think?

I am open to comments, constructive feedback and suggestions for improvement. Please comment below!

Wishlistcast Plugin Not Working?? Read This…

The WishlistCast plugin is used by hundreds of sites worldwide, and is tested against latest WordPress (3.8.1) and latest Wishlist (2.80.2002 at time of writing). It will generally work with older versions of both WordPress and Wishlist too, though I recommend staying up-to-date wherever possible.

So what happens if you’ve configured it all and it still isn’t working for you?

Or worse – it was working fine, and suddenly stopped working.

Well, there are really only two things that can be wrong:
1) Something is amiss in your configuration, or,
2) Something is blocking the call from getting from Nanacast to the plugin

Checking your configuration
From helping a number of people debug issues in their configuration, the most common mistake I see is spaces being added to the front, end or even in the middle of the Outgoing API when it is copied into the Nanacast setup. There should be no spaces anywhere in this URL. Or quotation marks for that matter! You should be able to copy and paste the URL into your browser. You should see a plain white screen. If you see an error message, then that’s probably the clue you are looking for.


Is the call being blocked?
Another common issue is when the call from nanacast is not reaching the wishlistcast API. In every case I’ve investigated so far, this has been caused by over-zealous mod_security rules put in place by the hosting company. In this situation, you need to contact your hosting company and get them to identify and white-list the mod-security rule.

You can check whether the call is getting through by examining the Outgoing API Call Log inside your Nanacast account. It’s a little hidden away in the menu – here is the direct URL (you need to be logged in to your account):

http://nanacast.com/api/outboundlogging.php (Outbound API Call Log)

See the notes below on how to check the Outgoing Call Log for issues.

It was working – and it has suddenly stopped working
Ahhh.. my favorite kind of bug. The mysterious “it stopped working” bug.

After over 30 years of creating software, I can say with some degree of certainty, that if a piece of software has been working fine and then suddenly stops working, then 99.9% of the time, something has changed to cause it to stop working. (The 0.1% allows for unanticipated conditions in the code itself – remember the “Y2K bug” that was going to bring an end to civilisation as we know it… but I digress… back to Wishlistcast…)

If you have just upgraded WordPress, or Wishlist, or changed your membership levels, or upgraded to a new version of the Wishlistcast plugin, then it is possible that one of these upgrades is the culprit.

If you’ve just installed a new plugin (for example a security plugin for WordPress – particularly if you’ve used that security plugin and/or manually managed to change the location of your wp-content directory), then the new plugin might be causing havoc.

If nothing has been changed in your WordPress setup – then quite probably it is not the software that has stopped working that is at fault.

Like when I upgraded my Mac earlier this year and suddenly my scanner stopped scanning. The scanner had not changed. But the software that talks to it (Mac OSX Mavericks in this case) certainly had.

In this situation – chances are you will find the culprit by examining the Outgoing API Call Log in Nanacast.

Checking Outbound API Log – What You SHOULD See


This is what the Outgoing Call Log should look like on a successful call.

If you click on the “Data” area, you will see all of the parameters being passed by nanacast to the plugin. These should include the members email address and password.

When you click on the “Server Response” area you will see what Nanacast received as a response to the call.
Note that you have to click on the Server Response to see the error in the response – an empty value in the column does not indicate a blank response!

If everything is working, this will usually be empty – which shows up as a small white box when you click on it.

Outbound API Log – What An Error Response Looks Like


This is an example of what the Outgoing Call Log will look like if the server response is NOT what we want to see.

This example is the dreaded Mod_Security issue – which means that your server host is blocking the call. (You need to contact your hosting support to help resolve this).

You may also see “403 – Forbidden” errors – for example if a WordPress security plugin is preventing the call getting through.

Or “404 – Not Found” errors if you have a bad URL in the Outgoing API.

A side note on Mod_Security:
Be aware that Mod_security issues are more difficult to debug than other server issues – and my experience has been that the front-line support person won’t be able to spot the issue.

As an example – here is a direct quote from a hosting support person in a situation where I KNEW the call was getting blocked by mod_security (because I could see the message in my Nanacast Outgoing API Log):
“Adam R.:We are not seeing any mod_sec rules being hit by that URL.”

Given that the server is very clearly responding with a message telling me that mod_sec rules ARE being hit… well, let’s just say I tend to believe the computer in this case – they aren’t good at making stuff up… 🙂

Keep in mind that these poor over-worked and under-appreciated folks are usually following a standard trouble-shooting checklist, and are likely incented to close calls as quickly as possible. If they can tell you “no problem here” and move on to the next call in the queue, then that is a good outcome for them.

Unfortunately – the checklist has them looking in the wrong place. Typically they look at the server error logs – which don’t show mod-security issues.

To see the mod_security issue and figure out which rule is being triggered, they will need to get a server admin to use ‘apachegrep’ to specifically look for your domain while you are triggering the rule (for example, by manually adding a member through Nanacast).

Once they know which rule is being tripped, they can whitelist that rule for your site – preferrably JUST for the wishlistcast_api.php URL being called from nanacast.

If the calls are being blocked by Mod_Security, then the only people that can help you unblock it are your server admins – i.e. your hosting company. Unfortunately – there is nothing Nanacast support or I can do to “work around” the problem. The only way this can get resolved is for your hosting company to take ownership of and resolve the issue for you.

If you are really stuck, then go to Fiverr.com or UpWork.com and hire a WordPress expert for a couple of hours. And if it takes them more than a couple of hours, then trust me, they aren’t an expert!

Need more than a quick fix?
If you need a full custom membership site built from scratch, then get in touch with Jon Hollenberg at FiveByFive. Jon literally wrote the book on WordPress – the Five-By-Five team do awesome work!


How To Link Nanacast.com and WishList Member

Having problems?

Want to use Nanacast for Affiliate Tracking and payment handling and still deliver secured, drip-fed membership content through WordPress using Wishlist Member?

With WishListCast you can do exactly that. Here’s how…

Grab Nanacast

Grab Nanacast through http://nanacast.co (yes, that’s my link) 🙂

Grab and Install WishList Member

Refer to Wishlist documentation for this

Grab and Install WishListCast plugin


Grab WishListCast plugin at http://nanacast.com/wishlist-plugin
Upload through standard WordPress plugin install process

Create a Membership Level in WishList


Refer to Wishlist documentation for how to do this

Collect integration details from the Integration tab in Wishlist


You need to know the "Secret Word" and the SKU for the membership you want to link

Create a Membership in Nanacast

This is the membership we will link to your Wishlist Membership Level
(i.e. signing up to this Nanacast membership will add you to Wishlist)
Refer to Nanacast documentation for how to do this.

Add "Password" as a custom field


Click on Notifications / Custom fields in your Nanacast membership and add password from the list of pre-defined custom fields.

This is the same as if you were using MemberLock

Tick the box to "not show password field and Auto-Generate Password Instead"

Link to WishListCast from your Nanacast Membership Custom Fields/Notifications


This is where you will link Nanacast to Wishlist through the WishListCast plugin


If you have multiple membership levels you want to join you can use a "pipe" (|) to separate them, eg.

Note: Line breaks here are for readability – the command goes all in one long line.

New in v1.1.0 – Set a Default User Role
You can now set a default WordPress role for the new user by adding &role=roleName (e.g. &role=subscriber to the link command e.g.

If you do not specify a role, then the "New User Default Role" from your WordPress Dashboard Settings | General will be used.
If you want the new user to have NO WordPress role use &role=none

Test by creating a New member


Add a new member in Nanacast.

The member will appear in your Wishlist Members

Check member appears in WishList Members


At this point, integration is working.

If you Unsubscribe the member in Nanacast, they will be Cancelled in Wishlist (simulate PayPal / credit card recurring payment stopped).
If you Resubscribe the member in Nanacast, they are Re-Activated in Wishlist (simulate PayPal / credit card recurring payment resuming)

Set Thank You Email


Make sure that the email you send to new members directs them to your Wishlist membership site (default is Nanacast).
To do this, just change the Website URL.

Bulk uploading existing members from Nanacast to Wishlist


If you have existing Nanacast members, you can easily bulk upload them to your Wishlist site by going to your Nanacast membership list (click View Active)

Upgrading existing members – make Nanacast members "Pending"


Hit Check/Uncheck All and change status to "Pending" – this will send an unsubscribe message to Wishlist (as the members don’t exist in Wishlist yet, this will have no effect).

Upgrading existing members – find "Pending" members


Next we use the Advanced Search functionality to find the existing Pending members

Upgrade existing members – find "Pending" members cont’d


Search for subscriber’s with "Pending" status

Upgrade existing members – set status to Subscribed


You may optionally Resend email receipt – this will send your subscribers the "thank you" email with their Wishlist login details.

Disable the WordPress Upgrade Nag

I love WordPress – it’s awesome and it let’s me build fantastic and functional sites for my clients.

But I HATE that upgrade nag you get whenever a new release comes out.

Because invariably, one of my clients will log in, see the “WordPress 3.0 is available! Please update now.” message, and think that they are supposed to click the link. And you can guess the rest of the story… we’ll spend the next 2 hours restoring the site from backup because two of their critical plugins aren’t yet compatible with the latest version of WordPress.

As an aside, I’ve lately been using iTheme’s brilliant BackupBuddy plugin to copy client’s entire sites to a test domain to trial new plugins and generally test upgrades. Highly recommended – really makes transferring sites between domains a snap!

Anyway, I finally decided to bite the bullet and hide the upgrade nag message for everyone who is logged in to the site except for me.

Here’s the code I used – it goes in your theme’s functions.php file – or custom-functions.php if you’re using Thesis. Obvioulsy change the user name from “steve” to YOUR user name (check top-right corner of Admin dashboard – Howdy, xxxxx).

function hide_update_notice() {
 global $user_login , $user_email;
 if ($user_login != "steve") {
 remove_action( 'admin_notices', 'update_nag', 3 );
add_action( 'admin_notices', 'hide_update_notice', 1 );

I borrowed heavily from http://gunnerpress.com/wordpress/disable-non-admins-from-seeing-the-wp-version-update-notification and Yoast’s http://yoast.com/disable-update-nag/ in pulling this together. Thanks!

Effective Problem Solving

Do you get frustrated by technology? When things go wrong – as they inevitably do – how do you go about solving them? Do you even know where to turn for help?

I was recently asked how it was that I seemed to just “know” how to fix stuff. My initial response was that it   comes naturally – but then I wondered if I could somehow break down my thinking process and give you a way to be a better problem-solver…

It’s kinda weird to think about my own thinking, but here goes…

0. Mindset – Assume that the Problem is ME until proven otherwise

I start with the assumption that the problem is caused completely by me or my own faulty equipment. This way, I allow myself to take full responsibility for finding and fixing the problem – rather than assuming it is caused by someone else which dis-empowers me from being able to fix the problem.

A lot of people hit a technical problem and immediately jump to the conclusion that “oh, that software doesn’t work” – in spite of the fact that hundreds or even thousands of other people are successfully using it to do whatever it is that supposedly “doesn’t work”.

Accept that the problem may very well be you or your machine or your particular configuration, and it enables you to tackle the problem with the mindset that it CAN BE FOUND and FIXED!

1. Define the problem and the desired outcome

Start by identifying and defining the problem as well as the desired outcome (which may be as simple as “problem no longer exists!”). You want to make sure you are focused on the right issue – don’t spend a bunch of time fixing the car engine when the real reason it won’t go is four flat tyres!

2. Identify all possible contributors

With technology, there are many, many moving parts at work. I brainstorm all the likely culprits.

For example, let’s say that I am having trouble accessing a particular website.

My list might look like this:

a. Target website server might be “down”

b. Trouble with my Internet Service Provider

c. Trouble with my local network (in my home/office)

d. Trouble with my browser

I don’t drill into esoteric possibilities on the first pass (like faulty hardware) in the early stage – just hit the “big ticket” possibilities.

3. Eliminate Possible Causes

I then use a process of elimination to remove culprits from my list.

Taking the list above, I would for example:

a. Try to access another common website – e.g. Amazon.com

If I can get through to Amazon.com, it pretty much eliminates the network (both local and my ISP), and also demonstrates that my browser is working to some degree (there may be issues with a particular plugin that affects the target website though).

b. Try to access the target website from a different machine or device (e.g. my iPhone)

If I can get to the target machine from a different device, then the problem is not with the target website being “down” in any way. It is starting to look more and more like a problem on my machine or possibly my browser settings.

c. Clear browser cache completely. Stop and restart browser. Try target website again

Chances are this will fix the issue, but if not…

d. Try accessing from a different browser (e.g. Safari)

If this works, it means that the problem is in my normal browser – probably a plugin

e. Check for updates on all software I am using in the process

There may be a critical update that solves my exact problem

4. Repeat Steps 2 and 3 Until Problem Solved or Out of Ideas

Each iteration through the steps may uncover fresh paths to explore or new details that lead me closer to a solution. I will keep brainstorming possible causes and ways to test each theory until I’ve either fixed the problem, run out of ideas, OR I’ve reached the limit of what I am able to do on my own and need help from a specialist.

The good thing about going through the brainstorming and elimination steps is that I have probably identified the right person or company I need to speak with to move forward – whether that is my internet service provider, my software manufacturer or the hardware manufacturer.

5. Document What I Have Found and Ask For Help

If I haven’t solved the problem, I will quickly outline the tests I have run and the outcomes and take it to the RELEVANT specialist who can help me.

If I’m not sure which specialist to ask, I ask my network to help me identify them. e.g. “I’m having a problem displaying XYZ site in Safari on my Mac – but it works OK in FireFox. Where do you think would be the best place to get help?”

Hope this helps!


Fun with Regular Expressions (RegEx)

I’ve been having fun with Regular Expressions today!

I was after a solution for one of my clients who posed this challenge…

When someone clicks on my ads, I want to send them to my index page (index.php), and I want to pass in information about the campaign as well as the keyword.

At the moment, these are parameters, so I do this with (for example) “http://www.mydomain.com/index.php?campaign=EDUC1&keyword=mind-mapping“, but it looks ugly in their browser URL bar.

I’d really rather the whole thing was a url, but I don’t want to set up copies of my index page for every campaign and keyword combination.

Is there a way to do this?

I suggested that we could send them through to a url that looked more like this:

Which I accomplished with some regular expressions in the .htaccess file.

While I was doing this I found a really useful (and free!) online Regular Expression testing tool – saved me a bunch of time with uploading .htaccess files and testing them.

Here’s how I did it…

.htaccess changes

First, we need to ensure that their web-server’s rewrite engine is switched on:

RewriteEngine on

Then we need to replace anything that looks like this:

with this:

Regular Expressions (RegEx) to the rescue:

RewriteRule ^([^/\.]+)/([^/\.]+)/?$ /index.php?campaign=$1&keyword=$2 [L]

A RewriteRule takes the general form of:

RewriteRule <search-string> <replace-string> [FLAGS]

Let’s take our solution apart one piece at a time…

[^/\.]+ - matches any string of characters ([...]) EXCEPT (^) a forward-slash (/) or a full-stop (.)

(…)  – round brackets identify a “group” and saves it with a name for use later on. The first group is called $1, the second is called $2 and so on.

^  – matches from the start of the line

/? $ – matches an OPTIONAL (?) forward-slash(/) at the end of the line ($)

putting it all back together, this will match (campaign)/(keyword-phrase) as well as (campaign)/(keyword-phrase)/. The campaign and keyword-phrase are saved as groups $1 and $2 respectively, ready to be used in the replacement url: /index.php?campaign=$1&keyword=$2

Now, the only problem is that relative references to images and other files from within the index.php will be pointing to the wrong place. This is because the user’s browser thinks it is pointing to /EDUC1/some-keyword so references to images/some-pic.jpeg would be interpreted as /EDUC1/images/some-pic.jpeg. Of course, they are really in simply /images/some-pic.jpeg.

We fix this with another bit of RegEx magic:

RewriteRule .+/images/(.*) /images/$1 [L]

So all together the changes look like this:

RewriteEngine on
RewriteRule .+/images/(.*) /images/$1 [L] RewriteRule ^([^/\.]+)/([^/\.]+)/?$ /index.php?campaign=$1&keyword=$2 [L]

Check out this webmasterworld page for more information on using mod-rewrite, regular expressions and .htaccess.