Url Shortener for same domain in Wordpress

Url Shortener services have increased their presence a lot. And their importance can never be underestimated. There can be many reasons why you wish to use Url Shortener for your wordpress site.

  1. To really shorten your long URLs so people can easily type it on browser
  2. To hide their bad intentions behind a short URL
  3. To look cool :)

In this article, I'll be showcasing a new plugin that I developed to have my own URL Shortener for my wordpress website. Before we begin, let me show you how your this shortener is going to work.

Original Url: http://yourdomain.com/2016/01/01/this-is-very-very-long-url-that-contains-many-keywords

gets another url

Shortened Url: http://yourdomain.com/yXppz

Looks good? No third party services like http://bit.ly, goo.gl, http://wp.me or anything else. It's you, your domain, your posts, your articles and your own url shortener.

Advantages

Because you are not depending on any third party URL shortening service, and your own wordpress website is your shortening service, nobody else is tracking your visits or link usage. You'll also be free from any dead links born out of a dead third party service server.

How it works

All we going to do is intercept every incoming request, extract the short slug form the url, check if that slug exist in our database and if it does, perform a 301 permanent redirection to the actual post.

Database

The database structure required by the plugin is really simple. We don't really need more fields for our data. This is how it looks.table structure We just need a single table with 3 columns. All three explain themselves very well, don't they?

Umm, wordpress hooks right?

Oh yes, we are using three wordpress hooks, but you may wish to use more depending on the features you wish to implement.

  1. wp_loaded: This is called right after wordpress load is complete. It's at this point, we can safely intercept the incoming request and perform the required redirection.
  2. save_post: This is called whenever the post is saved. It's here we check whether a given post has a short url assigned or not. And if it's not, we assign it a new short slug.
  3. edit_form_before_permalink: I used this hook to display the short url on the post edit screen in wordpress administration. Depending on where you wish to show this url, you may need to look for another hook. (custom boxes may be?)

Code Please?

Sure, The plugin consist of two files only. Here's the code.

<?php
/*
Plugin Name: Roasted Url Shortner
Plugin URI: http://www.roastedbytes.com
Description: Simple URL shortner plugin for your wordpress site
Version: 1.0
Author: Roasted Bytes Team
Author URI: https://commitpad.com
License: GPLv2 or later
*/
require_once("urlshortenerdb.php");
class RoastedUrlShortener
{
    const SLUG_LENGTH = 5;
    private $_db;
    function __construct(){
        $this->_db = new UrlShortnerDb();

        add_action("wp_loaded", array($this, "redirect_if_found"));
        add_action( 'save_post', array($this, "save_slug") );
        add_action( 'edit_form_before_permalink', array($this, "show_shorturl") );

    }

    function redirect_if_found(){
        $url = wp_parse_url($_SERVER['REQUEST_URI']);
        $path = $url['path'];
        if($path == "/"){
            //do nothing
           return;
        }
        $path = substr($path, 1);
        $path_parts =  explode("/", $path);

        if(count($path_parts) == 0 || empty($path_parts[0])){
                return;
        }

        //check if the slug is in db
        $slug = $path_parts[0];

        $permalink = $this->_db->get_post_link($slug);
        if(!empty($permalink)){
            //we'll do a permanent redirection. Good for SEO
            $query = $url['query'];

            if(!empty($query)){
                $queryArr = array();
                parse_str($query, $queryArr);
                $permalink = add_query_arg($queryArr, $permalink);
            }
            header("HTTP/1.1 301 Moved Permanently");
            header("Location: $permalink");
            exit;
        }
    }

    function install(){
        $this->_db->install();
    }

    function save_slug($post_id){
        // verify if this is an auto save routine.
        // If it is our form has not been submitted, so we dont want to do anything
        if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
            return null;

        //is it already saved?
        $slug = $this->_db->get_short_slug_by_id($post_id);
        if($slug != null)
            return $slug; //do nothing, we already have saved it's permalink

        //create a new slug
        $slug = $this->generate_random_slug();

        //does this slug already exist? chances are rare but better safe than sorry
        while(!$this->_db->save_slug($slug, $post_id)){
            $slug = $this->generate_random_slug();//another slug
        }

        return $slug;
    }

    function show_shorturl($post, $force_generate = true){
        //not for pages
        if($post->post_type === "page" || $post->ID == 0 || $post->post_status=="auto-draft"){
            return;
        }
        //first check if the $post is saved or not
        //is it already saved?
        $slug = $this->_db->get_short_slug_by_id($post->ID);
        if($slug == null){
            if(!$force_generate)
                return;
            //save a slug
            $slug = $this->save_slug($post->ID);
        }
        $url = get_bloginfo("url") . "/" . $slug;
        ?>
        <div class="inside" style="padding: 0 10px;margin: 15px 0">
            <strong>Short Url: </strong>
            <a href="<?php echo $url; ?>" target="_blank"><?php echo $url; ?></a>
            <button type="button" onclick="prompt('Short Url for your post', '<?php echo $url; ?>')" class="button button-small">Copy</button>
        </div>
        <?php
    }
    function generate_random_slug(){
        $characters = '23456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ-_';
        $charactersLength = strlen($characters);
        $randomString = '';
        for ($i = 0; $i < self::SLUG_LENGTH; $i++) {
            $randomString .= $characters[rand(0, $charactersLength - 1)];
        }
        return $randomString;
    }
}

$urlShortner = new RoastedUrlShortner();
//register activation hook
register_activation_hook(__FILE__, array($urlShortner, "install"));
<?php
class UrlShortenerDb
{
    function _get_table_name(){
        global $wpdb;
        $table_name = $wpdb->prefix . "shortslugs";
        return $table_name;
    }
    function install(){
        global $wpdb;
        $charset_collate = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE IF NOT EXISTS ". $this->_get_table_name() ."(
              id mediumint(9) NOT NULL AUTO_INCREMENT,
              slug text NOT NULL,
              post_id mediumint(9) NOT NULL,
              UNIQUE KEY id (id)
            ) $charset_collate;";

        require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
        dbDelta($sql);
    }

    function save_slug($slug, $post_id){
        //check if this slug exist?
        $permalink = $this->get_post_link($slug);
        if($permalink == null){
            global $wpdb;
            $data = array(
                "slug" => $slug,
                "post_id" => $post_id
            );
            $wpdb->insert($this->_get_table_name(), $data);
            return true;
        }
        return false;
    }

    function get_post_link($slug){
        global $wpdb;
        $sql = "SELECT post_id FROM " . $this->_get_table_name() . " WHERE slug=%s";

        $post_id = $wpdb->get_var($wpdb->prepare($sql, $slug));
        if(empty($post_id))
            return null;

        return get_permalink($post_id);
    }

    function get_short_slug_by_id($post_id){
        global $wpdb;
        $sql = "SELECT slug FROM " . $this->_get_table_name() . " WHERE post_id=%d";

        $slug = $wpdb->get_var($wpdb->prepare($sql, $post_id));
        if(empty($slug))
            return null;

        return $slug;
    }
}

Would you mind explaining the code?

Sure, these are the two files for the plugin.

  1. urlshortener.php: This is the main plugin file. As you can see, inside constructor we register the above mentioned hooks.
  2. urlshortenerdb.php: The file for the database CRUD operations. (Not CRUD in it's real sense, but you get the point right?). If you have written some wordpress plugins, you'd find it family.

Why have you excluded certain characters while generating slug?

Good question. I've actually excluded those characters which may cause ambiguity with shared urls because they look same for some fonts. e.g. [1,l,I] look similar, so do [0,o,O] So it's better to exclude those characters from URLs, isn't it? Share in comments how you think it is and what would you like to improve. So where does https://commitpad.com/5ZEqW go?