Build a Short URL Service with WordPress Custom Post Types

WordPress is normally used as a blog engine or as a content management system (CMS), but those are not the only things it can be used for. With a little imagination, you can do virtually anything you want with it! In this tutorial I will teach you how to build a shortened URL service, write some plugin code using object oriented techniques, and deal with WordPress routers and custom error messages.

Note that this is an advanced tutorial - we'll be using some relatively advanced PHP techniques, but I'll make sure to link you over to any resources articles that you might need to understand along the way. Just remember, the point of this tutorial is to push the limits of WordPress, and that's going to take some real thought!


Overview

You should be familiar with the idea of what a "short URL" service is nowadays. If not, check out Goo.gl, bit.ly, or any of the others out there. Those are all fine and well, but what if you want your own? There a few reasons that you might want this (not just for vanity purposes), and this is an excellent chance to look at some of the more advanced areas of WordPress that you might not be familiar with.

The Goal: We will create a Custom Post Type called "url", its post title will be used as the original url. For each post (actually each url now), we generate a key for it, or the user enters his/her own key, which we will call the "vanity key" from now on. This vanity key will be appended to site's url and we have a short url.

Here's the breakdown of what we'll be doing in layman's terms:

  1. Assume your site has domain: http://wp.tutsplus.net.
  2. We have a very long url: http://net.tutsplus.com/tutorials/wordpress/create-a-multi-layout-portfolio-with-wordpress/.
  3. We will assign it a "vanity key" of "1A", so short url will be: http://wp.tutsplus.net/1A.
  4. Anyone who hits it gets redirected to: http://net.tutsplus.com/tutorials/wordpress/create-a-multi-layout-portfolio-with-wordpress/.

If you are not very familiar with plugin development yet, you should take a look at these useful tutorials before going further:

Also make sure you have mod_rewrite installed, and enable the Pretty Permalink. We will use a custom field called _vanity_key to store this vanity key for each post.

Enough chatting, let's get started!


Step 1 Setting up the plugin class

We will use OOP here.
Let's call this plugin 'wp-vanity'. We create a folder wp-vanity in wp-content/plugins. Then we create a main file call vanity.php and put it in folder wp-vanity.

File vanity.php

We organize code into a class called "Vanity". We also define a class constant POST_TYPE for the name of our post type. Then we use the singleton() pattern, so that in the future you can make the plugin bigger without needing to deal with global variables since we always get the same instance of the "Vanity" class when we use method singleton().

You see the method bootstrap() will be automatically called after creating object in method singleton(). Thus, everything related to add_action, add_filter should be put here.

If you are not familiar with Singleton pattern, then read A beginners guide to design patterns.

Experienced PHP devs may wonder why I didn't put that code in __construct() method. This is for reasons of safety. If you have too much long-execution code in method __construct(), then what might happen if one of those lines calls to method singleton() again. Well, at that time, the executing of __construct() has not finished yet, thus, the object is not yet returned; Therefore Vanity::$_self is not assigned. As the result method singleton() will create an object one more time. In order to be safe, we should not put code which call method singleton() in constructing method.

We manually call bootstrap() in method singleton() after object is created. Of course, if you make sure nothing goes wrong you can just put it in _construct() straight away.
For now, we will put every code related to add_action, add_filter in method bootstrap().


Step 2 Dealing with custom post type

In this step, we will register our custom post type, add a meta box to display our vanity key and short link.

Registering Our Custom Post Type

We register post type whose name is stored in self::POST_TYPE. I didn't use hard-coding so you can easily change post type name to anything. Also, we just need WordPress to show a title field, and an author field for our post type. We just need title field to enter original URL. A custom field is used for the vanity key. You will handle it later. Now let's create method init() to register the post type:

There's not that I can add to the code above. We register with register_post_type and set a label and text for it. Now we'll let WordPress know we want to hook into it with the init hook! We use modified method _bootstrap():

Adding Custom Meta Box

To store the vanity key, we use a custom field called _vanity_key. As editing/adding post, we display a form or in other words, custom meta box, with information about short link (via appending _vanity_key to the site's url), and a text box to let the user enter their own vanity key instead of generating key automatically.

You can use get_post_meta to get the value of any custom field of a post. If you are still not familiar with meta box and custom field, let read this amazing tutorial again.

We use action add_meta_boxes to register our new meta box, then we use the method, meta_box_content(), to render its inside content! When displaying meta box, we try to get the value of custom field _vanity_key. If we got a non empty value, we display the whole short url with that vanity key and a "Try it" link so that the user can click on it to try short url in a new window!

At this point, if you try to add a new URL you have form like this:

If you edit an URL, you have form like this:

Saving the custom field

When we save the post, WordPress just saves title of post, we must handle our custom field in meta box ourselves. When any post is saved, action save_post is called, so we'll hook into this action:

If we are saving the post automatically, there is no point to save our custom field. We also checked to make sure form has a valid nonce field to avoid double submit and to make sure data came from the right place.

If the users entered a value in the vanity field, then value of $_POST['_vanity_key'] in PHP is not empty, let's use it, otherwise we automatically generate a key by converting the id of post into base 36 number. Then we use update_post_meta to save it. Before saving, we get the current value of custom field _vanity_key and compare it with our new key which is entered by the user or generated by our code to see if we really need to save it. If the old value and new value are the same there is no point to save it again.

Error handling

Everything is looking pretty good at this point, but maybe you're wondering what happens if the user enters a vanity key which has been used before? Or what if the user enters an invalid url? We need some sort of error handling to help the users along the way here.

Firstly, let's create a method called _key2url. As its name says about it, it will receive a key, and try to find if we already have an URL corresponding to this key.

If a vanity key has not already been used, then a false value will be returned. Otherwise, the URL which matches with that key in database will be returned. We also prepend 'http://' to URL if needed. We must do this because of missing leading 'http://' can make WordPress redirect to ourdomain.com/original.com instead http://original.com.

WordPress stores custom fields in the table "wp_postmeta", and posts are stored in the table "wp_posts". Here "wp_" is a prefix which we can access via $wpdb->prefix. We use MySql JOIN clause to match data. Below is a figure on how WordPress store our custom field (our vanity key in this case)

Okay, let's change our method save_url for some error handling. Note that I added two new methods: invalid_key and invalid_url. We will detail this later.

We use preg_match('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $post->post_title) to check if we have a valid URL. preg_match returns the number of times pattern matches.

In this case, |^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i is a regular expression pattern for an URL which start with http or https. URL is $post->post_title (remember we use title of the post as original url).

If URL is not valid, we will call add_filter to warning error. Don't worry about what it means now, I will cover it later. Also, once we got the new vanity key which we assigned to $_vanity_key, we call method _key2url in while loop to make sure no post is used that vanity key before.

If the vanity key is already used, we generate a new key. We get the current time by using function time() which returns an int number then plus with a random number and convert total result to a base 36 number .

So, how do we notify the user of these on WordPress back-end? The mechanism for solving this inside WordPress looks like this: after saving post, WordPress redirects the user to the post editing page, and appends some parameters to URL as flags to display messages. Try to look at these to figure out and notice the parameter "message" in URL and the actual message in yellow.

If you look at the URL of WordPress after saving a post, you see something like this: http://house.axcoto.com/vanity/wp-admin/post.php?post=13&action=edit&message=1 and a message is shown as in the picture below:

If you try to modify the "message" parameter on URL you have:

Unfortunately, WordPress doesn't have a document for filter redirect_post_location now but you can understand simply that this hook gives us a simple way to modify the URL which WordPress will redirect to after saving a post.

Well, you should understand now how WordPress shows notifications to the user via parameters on URL. So when saving our post type in method save_url, if any error happens, we will alter the URL which WordPress will redirect to and append our custom parameter. WordPress provides filter redirect_post_location to do this. This is an extract from above code for you see it more clearly:

In each case, we append a custom parameter vanity_message with a value: 1 means invalid URL, 2 means the key is already use. Next. we must show our custom message with this vanity_message. Let's modify our method meta_box_content:

We can output the error message in our meta box. But the good thing is as long as you set the class of any element on the page to "updated" then WordPress automatically grabs it and moves it to right place like this:

You maybe say "wow" after reading this! But as I told you, WordPress is really clever and does many things for you, just they cannot document everything.


Step 3 Detecting url and redirecting

At this moment, you were able to add a new URL and save the URL. It's now time to try shorten the URL now!

What happens if an user hits short url? Well, WordPress loads up, and will process URL into "query" property of class wp_query.

This class has a global instance: $wp_query. We will hook into one of the WordPress hooks before header is printed out to redirect the users to the original url. If the header is printed out, how come we can make redirect, right? To make it easier to understand, let's hook into action 'get_header'.

So, when you go to url domain.com/foo WordPress will store "foo" as "pagename" of $wp_query->query if it cannot detect any permalink (post slug, category name,..) which matched this URL. Once we got the key, we call method _key2url to get URL of that key. If it found one, we redirect to that original URL. Alternatively, we just call _key2url if WordPress output did not find a page! There is no point to call _key2url every time because it needs query database and this can be a performance issue if your site has huge traffic. Finally, you have done it! That's all that you need to do to have a shorten url service with WordPress.

Step 4 Making it even better

At this point, you can add a url and have a list of url posts in WordPress dashboard! But to see the short url and vanity key, you must edit a post to see it... That's really annoying! So, let's put this vanity key on post listing page. We can achieve this with filter manage_edit-{post_type}_columns and action manage_{post_type}_custom_column

Filter allows us add more columns when listing our custom post type besides normal columns such as: title, author,...etc. Action lets us really build content for that column. As always, you must inject add_action and add_filter to the _bootstrap method:

Columns are stored in an array which is passed to our filter method. We add a new column via appending a new element to that array. Method "custom_column" takes care of this. The modified array is returned, WordPress grabs returned value and recognizes the new column. The name of column is _vanity_key. We use it to reference our column later. The title of column is "Vanity Key" - this is the text which appears on table header.

We use method "column_content" to output content of this column. WordPress passes two parameters to functions which hooked action manage_{post
type name}_posts_custom_column
: The first one is name of column, the second one is the id of the post which is rendering.

Based on this, we check to make sure value of variable $column_name is _vanity_key, the name of our column. Then we use get_post_meta to read the custom field _vanity_key. Finally, we print out an "a" element with target="_blank" to open it in a new window. If you had other columns, you could continue with other "case" statements for those columns.

Now you can take a look at two pictures: before and after using above filter, action. The first one doesn't have a vanity column while second one has a vanity column with each post's vanity key.

Conclusion

Finally, you now have your own shorten url service with just around 60-70 minutes of coding and can use your current WordPress site with current domain. Hopefully, you've found this tutorial to be of help. Feel free to reuse this code elsewhere in your projects. If you have something to say or share, or even teach me as well, then please leave me a comment!

Tags:

Comments

Related Articles