Automated Link Building Tool

June 25th, 2009

A buddy of mine wrote an automated link building and indexing tool that he calls Autopilot Linker. I’ve been a beta tester on it for a couple months and it works really well! Basically it uses your visitor’s IP address to build links and get your sites indexed faster.

I used it on some newer unindexed sites and it worked well for me. Basically I copy-pasted the little JavaScript code (just like installing adsense or analytics) into my site’s footer template and sent a little bit of traffic to each page using a twitter account. Each person who visited helped build links to those pages plus got them indexed faster! Once these pages were indexed and ranking for their terms, I added links to my more “white” sites. This is an amazing tool for building linking power for a site, since not only does it build links from high PR domains but it gets more pages on your sites indexed faster!

Anyway, I recommend checking it out. There are other tools like this, but his is quite inexpensive compared to them and a lot slicker and easier to use! Seriously, if you can copy-paste you can use this tool! Autopilot Linker

Tools , ,

External DNS at 1and1

June 10th, 2009

If you have your DNS hosted in one place (say DNS Made Easy) but host your content on 1and1.com. 1and1 allows you to do this without transferring the domain to them. The problem is they appear to not set the DirectoryIndex directive for hosts using external DNS. This can be overcome with either creating or adding to the .htaccess file in your site’s root directory:

DirectoryIndex index.php

That’ll work for WordPress, or most other PHP-based sites. You can always change it to index.html if that’s what your site’s index page is.

System Administration , ,

How to Start sshd On Plesk

June 9th, 2009

I had a client machine reboot today and ssh wasn’t configured to come up on boot. This was a CentOS Plesk machine at my least favorite hosting provider, Media Temple. Secure shell isn’t mentioned in the services section in Plesk, and Media Temple doesn’t have a remote console feature. So in order to avoid submitting a ticket and waiting for Media Temple’s slow support, I managed to restart sshd using Plesk’s web interface to cron (Scheduled Tasks) to start it. Here’s what I did:

  • Got the current server time (it might not be in your timezone) by going to Server > Time
  • Went to Server > Scheduled Tasks and created a new scheduled task 2 minutes ahead of the current server time. The task should run /etc/init.d/sshd start.
  • Wait.

Hopefully within 2 minutes sshd is started. Don’t forget to remove the scheduled task, and ensure that sshd is configured to start on boot (chkconfig –levels 345 sshd start or whatever your distro uses.) Honestly I think Media Temple should have a backend console similar to how other VPS providers such as Slice and Linode do it. You’re paying enough. :)

System Administration , ,

Link Log Matcher Plugin

May 12th, 2009

Introduction

Originally described in Eli’s bluehatseo.com post Link Saturation w/ Log Link Matching this plugin allows you to use this technique on your blog. I recommend reading the original article, but this technique gets links made to you indexed faster. The result is more efficient link building, and it’s all automated.

When a user visits your site, the plugin checks to see if they are coming from a SERP - if not, either an iframe or a piece of JavaScript (which creates the iframe dynamically) hits pingomatic.com with the referral URL, hopefully getting it indexed faster.

Here’s the link for the Link Log Matcher Plugin.

Installation

Installation is the same as most plugins, and if you’ve used widgets before you’ll know what to do. Please contact me if you’re having difficulty or you think something is broken. Here’s a step-by-step:

  1. Download the Link Log Matcher Plugin and extract link_log_matcher.php
  2. Copy the file into your wp-content/plugins/ folder
  3. Go to the Plugins menu in WordPress and activate the Link Log Matcher plugin
  4. By default the plugin inserts an iframe. If you’d rather use JavaScript go to Settings > Link Log Matcher and choose JavaScript.

Using the Plugin

It’s all automated once installed. If a user visits your site through a non-local link that is not a search engine results page, ping-o-matic will be used to ping that referring page and hopefully get it indexed faster.


Wordpress ,

Affiliate Link Cloaker Plugin

May 8th, 2009

Introduction

This plugin will cloak an affiliate link in a similar fashion to a product called GC Affiliate Cloaker. It essentially spits out some JavaScript that opens the cloaked link in an iframe. Cloaking in this case is not for SEO purposes - it is to shorten URLs so they look prettier and also to prevent other affiliates from stealing your commissions. You can use a redirect plugin to accomplish the same thing, but this is easier as you make the “redirect” just like writing a post.

Installation

Installation is the same as most plugins, and if you’ve used widgets before you’ll know what to do. Please contact me if you’re having difficulty or you think something is broken. Here’s a step-by-step:

  1. Download the Affiliate Cloaker Plugin and extract link_cloaker.php
  2. Copy the file into your wp-content/plugins/ folder
  3. Go to the Plugins menu in WordPress and activate the Affiliate Cloaker plugin

Using the Affiliate Cloaker plugin

When you’re writing a post you’ll see additional options below the posting window. Click the “Cloaked?” checkbox and fill in the URL field with the destination URL (your affiliate link.) If you want to stop cloaking, just uncheck the box.

Screenshots


Wordpress ,

Apache restarts with vlad

April 30th, 2009

Often when deploying a new web application you need to restart the apache process. If you’re deploying the application with vlad the deployer as a non-root user (which you should be doing) and you need to restart apache, this can be a little tricky. Luckily there’s a Linux command called sudo which allows you to run commands from a non-root user’s account as though you were root. We’ll limit the commands to just controlling the apache process. It’s a bad idea to give open access with sudo since the user can simply run “sudo su -” and become root.

In order to edit the configuration file that sudo uses (/etc/sudoers btw) we use the visudo command, which edits the file in a safe way by checking for simultaneous users editing the file, parse errors, etc. The idea being to minimize the number of mistakes that can be made. You’ll need to be root (or have sudo permission!) to edit the file:

  # visudo

This brings up an editor screen, whatever you have configured as your default editor. The example below is for Ubuntu, so if you’re using a different distro or web server you’ll need to change the location of the init scripts accordingly (i.e. Ubuntu uses /etc/init.d/apache2 but your distro may use /etc/init.d/httpd or /etc/init.d/lighttpd if you’re running Lighttpd.) I’ll also assume you’re calling the deployment user deploy.

Here are the lines you want to add:

Cmnd_Alias     APACHE = /etc/init.d/apache2 start, /etc/init.d/apache2 stop, /etc/init.d/apache2 restart, /etc/init.d/apache2 reload
# Allow the deploy user to control apache
deploy ALL=NOPASSWD: APACHE

Now save the file, and log in as your deploy user. You should be able to restart apache with the above commands by running:

deploy@steve:~$ sudo /etc/init.d/apache2 restart

Now in your vlad tasks, use the sudo version above to kick the webserver over when you do a deploy:

         # desc "Starts Apache"
         remote_task :start => :settings do
             run 'sudo /etc/init.d/apache2 start'
         end

         # desc "Restarts Apache"
         remote_task :restart => :settings do
             run 'sudo /etc/init.d/apache2 restart'
         end

         # desc "Stop Apache"
         remote_task :stop => :settings do
             run 'sudo /etc/init.d/apache2 stop'
         end

System Administration ,

Updating WordPress XML Sitemaps Offline

April 24th, 2009

I personally love Arne Brachhold’s Google XML Sitemap plugin for WordPress. I personally use it on any WordPress install I do. On larger blogs, or blogs where you’re using automated content generators (i.e. posting content in an automated way through XML-RPC) the default build mode will slow down your blog because it rebuilds the entire XML sitemap from scratch every time you create or update a post or page.

There is a second build mode this plugin supports, which is to build via a GET request. For sites that have a lot of posts or do automated posting, this is a great option. It’s possible to schedule the XML sitemap updates to happen at specific times of the day with a simple script that uses an HTTP GET request to refresh them. This will speed up posting, especially for sites that use automatically generated content. Here’s a simple php script that you can schedule via cron to update your sitemaps and send you an email when it is done. Just update the $admin_email variable to where you want the email to go and the $sitemap_link variable to whatever the XML Sitemaps plugin tells you when you change the build mode. Notethat you may need to change the link to include the wp-admin especially if you’re on Wordpress mu - the link the plugin gives doesn’t work (i.e. http://myblog.com/?sm_command… to http://myblog.com/wp-admin/?sm_command…)

Here’s the script:

<?php

$admin_email='info@m7tech.net';
$sitemap_link = 'http://myblog.com/?sm_command=build&sm_key=90210';

function getURIContents( $uri ) {
    return file_get_contents( $uri );
}

function generateSitemap( $link ) {
    $ret = '';
    $result = getURIContents( $link );
    if( !preg_match( '/.*DONE.*/', $result ) ) {
        $ret = $result;
    }
    return $ret;
}

$result = generateSitemap( $sitemap_link );
if( $result != '' ) {
        mail( $admin_email, 'Sitemap Generator failed:$result" );
} else {
        mail( $admin_email, 'Sitemap Generator Complete',
                "Completed sitemap generation." );
}
?>

If you get errors related to file_get_contents not being able to load a remote URI, just replace the above function with this, which uses libcurl and you should be ok:

function getURIContents( $uri ) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $uri);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_USERAGENT, 'IE 6 - Mozilla/4.0' );
    ret = curl_exec( $ch );
    if( curl_errno( $ch ) ) {
        $ret = '';
    } else {
        curl_close( $ch );
    }
    return $ret;
}

Search Engine Optimization ,

Clickbank Ad Feed WordPress Plugin

April 15th, 2009

Introduction

Clickbank does not have an RSS feed, but they do have an XML version of their products called the Clickbank Marketplace. I wrote a simple backend for importing this XML file and producing RSS feeds, and then wrote a WordPress plugin that will show these feeds on your blog as a widget. Affiliate ID, number of items, campaign tracking tag and keywords are all available. You’re also able to put multiple ad feeds on your blog in different places, provided your WordPress theme has more than one sidebar.

Installation

Installation is the same as most plugins, and if you’ve used widgets before you’ll know what to do. Please contact me if you’re having difficulty or you think something is broken. Here’s a step-by-step:

  1. Download the Clickbank Ad Feed plugin and extract clickbank_adfeed.php
  2. Copy the file into your wp-content/plugins/ folder
  3. Go to the Plugins menu in WordPress and activate the Clickbank Ad Feed plugin
  4. Go to Design > Widgets
  5. Add the Clickbank Ad Feed widget by clicking “Add”
  6. Click “Edit” and fill out the fields, or leave them as the defaults (you will want to fill in your affiliate ID!)
  7. Click “Change” then “Save Changes” and view your site

Screenshots

Additional Information

  • Keywords are specified one per line and can include spaces. The software will use these keywords to search the title and description fields of the Clickbank product database. If not enough ads are found, it will fill the remaining slots with the most popular ads based on Clickbank’s popularity score.
  • Ordering is done by Clickbank’s popularity score. There are plans to add the ability to sort based on commission amount, whether a product is recurring, or gravity.
  • Affiliate ID is set in the widget itself. You can have multiple instances of the widget, so you can use different IDs on different sidebars. Note that my ID is rotated through roughly 20% of the time.
  • Campaign tracking tags are part of the Clickbank hoplink and can be specified in the widget options. I highly recommend using different values on different sidebars so you can track which ones are more profitable.
  • Data updates are done once a day. Clickbank updates their marketplace XML file once a day, so there’s no point in doing it more frequently.
  • Clickbank RSS feed access is coming shortly - this will allow you to use any RSS feed reader to include a Clickbank Ad Feed on your site.


Wordpress , ,

Writing A Web Analytics Engine From Scratch

April 2nd, 2009

If you’re building a system that needs to track affiliate sales, you’ll need to integrate some form of analytics into your software. Your affiliates will want to see how many total visitors hit their link, total uniques, how they got there (search terms or referrer URL) and if they made a product purchase.

There are a few ways of doing web analytics - writing tracking code right into your application (affiliate landing page), using JavaScript, or looking at web logs. I’ll be focusing on the JavaScript version, which works similarly to Google analytics and Mint. I won’t even go into web log processing here, since although there is interesting information there, it’s not real time enough for our use but is a powerful way to “check” the other methods or even gather information on spider visits (frequency, times of day, etc.)

Our tracking code will be fairly simple:

  1. Grab any information from the URL (GET parameters) and server data (user agent, remote IP, referer [sic] URL)
  2. Record the information in a database
  3. Continue processing the page

Decide on a Tracking Method

If you’re embedding the tracking code directly into your application, it’s a matter of adding some code to your controller and creating a model (and associated tables) to store the visitor data. The reporting backend will work exactly the same. The pros here are you don’t have to deal with JavaScript and/or cross-browser problems, and there may be a performance benefit since there are fewer HTTP requests being made to your server. The cons are that any time you want to change the tracking code, you need to change the controller, and you lose the ability to use the same tracking code on different sites, or sites that aren’t yours. Typically you set up one application (and domain) for doing analytics and reporting, and you have multiple websites. If you only have one website, and don’t mind running your analytics and reporting there, I’d recommend embedding the tracking code in your controller.

Using JavaScript to record visitor information is relatively simple. We need to write a controller to handle the requests to record visitor information, and a model to do the actual recording. The client side is a small JavaScript snippet, which will extract some variables and make a GET request to our controller. We won’t be using any AJAX here, since we need to deploy this code to multiple sites and have only one analytics site (i.e. we run the code on www.domain1.com but have our analytics requests hitting analytics.anotherdomain.com) - this is cross-site scripting (XSS), and although we want to allow it in this case, your browser won’t! Pros of this method are the ability to deploy to multiple sites and consolidate analytics/reporting to one server, and the ability to change tracking code without re-deploying your application. Cons are JavaScript browser incompatibility and increased complexity and load due to many (small) requests.

My Analytics Solution

We’ll be writing a controller and model using the Kohana PHP framework, and the client-side JavaScript without a framework, since all it does is generate a request for a 1×1 pixel GIF. This is the same way Google analytics and Mint do it. So, on to the code.


Web Analytics Model

Our model will store time, IP, request and referer [sic] URL information. Here is the MySQL table:

CREATE TABLE IF NOT EXISTS Hits (
    id                  INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
    recorded_time        TIMESTAMP NOT NULL, -- Time the record was created
    ip                  INTEGER UNSIGNED NOT NULL,
    ua          VARCHAR( 200 ) NOT NULL DEFAULT '-', -- User agent
    request         VARCHAR( 200 ) NOT NULL DEFAULT '/',
    referer        VARCHAR( 200 ) NOT NULL DEFAULT '-', -- Referer
    is_unique           BOOLEAN NOT NULL DEFAULT TRUE
);

Hopefully there isn’t anything unclear there. I’ve created fields to record the time at which the hit happened, the IP (stored as an integer for compactness), the user agent string, the original request, the referer URL and whether this is a unique or not (has this person already visited our site.)

The model is equally simple:

class Click_Model extends Model {

    function __construct() {
        parent::__construct();
    }

    function create( $ip, $ua, $request, $referer, $is_unique=1) {
        $ret = false;
        $row = array();

        // Convert IP to integer
        $ip = $this->_ip_to_integer( $ip );

        // Either 0 or 1
        if( $is_unique > 0 ) {
            $is_unique = 1;
        } else {
            $is_unique = 0;
        }
        if( $ip > 0  ) {
            $row[ 'ip' ] = (int)$ip;
            $row[ 'ua' ] = $ua;
            $row[ 'request' ] = $request;
            $row[ 'referer' ] = $referer;
            $row[ 'is_unique' ] = $is_unique;
            $ret = $this->_create_if_not_exists( 'Clicks', $row );
        }
        return $ret;
    }

    /**
     * Converts a text IP address to an integer.
     **/

    function _ip_to_integer( $ip ) {
        $octets = split( '\.', $ip );
        return (int)( $octets[ 3 ] + $octets[2]*256 +
                      $octets[1]*256*256 + $octets[0]*256*256*256 );
    }

    /**
     * Inserts the row if it's new and returns the ID, or just returns the
     * ID if it already exists. The table must have a column called 'id'
     * that is the INTEGER AUTO_INCREMENT PRIMARY KEY style.
     **/

    function _create_if_not_exists( $table, $row ) {
        // Try to insert - if it doesn't exist we'll get an ID of zero
        $columns = join( ',', array_keys( $row ) );
        $placeholders = join( ',', array_fill( 0, count( $row ), '?' ) );
        $q = $this->db->query( "INSERT IGNORE INTO $table ($columns) ".
                               "VALUES ($placeholders)", array_values( $row ) );
        $ret = $q->insert_id();
        if( $ret == 0 ) {
            $q = $this->db->getwhere( $table, $row );
            if( $q->count() > 0 ) {
                $result = $q->result_array( false );
                $ret = $result[ 0 ][ 'id' ];
            }
        }
        return $ret;
    }
}

The model class is pretty straightforward. Since Kohana doesn’t support “INSERT IGNORE”, I had to roll my own version. The model only handles inserts - actual reporting and such are left out.


Web Analytics Controller

The controller only does one thing - validate and record the data passed to it, then return a 1×1 pixel GIF:

class Hit_Controller extends Controller {

    private $gif_data = "\x47\x49\x46\x38\x39\x61\x01\x00\x01".
                                "\x00\x80\xFF\x00\xFF\xFF\xFF\x00\x00".
                                "\x00\x2C\x00\x00\x00\x00\x01\x00\x01".
                                "\x00\x00\x02\x02\x44\x01\x00\x3B\x00";

    function __construct() {
        parent::__construct();
    }

    /**
     * Basically grab all the parameters, record in the database and return
     * some content.
     **/

    function index() {
        if( isset( $_GET[ 'ru' ] ) ) {

            $h_model = new Hit_Model();
            $ip = $this->input->server( 'REMOTE_ADDR' );
            $ua = $this->input->server( 'HTTP_USER_AGENT' );
            $h_model->record_click( $ip,
                                    $ua,
                                    $this->_get_elem( $_GET, 'ru' ),
                                    $this->_get_elem( $_GET, 'rf' ),
                                    $this->_get_elem( $_GET, 'u' ) );
        }

        // Return a 1x1 pixel transparent gif
        header( 'Content-Type: image/gif' );
        echo( $this->gif_data );
    }

    function _get_elem( $a, $k ) {
        $ret = '';
        if( isset( $a[ $k ] ) ) {
            $ret = $a[ $k ];
        }
        return $ret;
    }
}

The only validation we do here is check that the referer URL was passed (the ru variable in the GET string.)


Client-side JavaScript

The JavaScript that acts as our view (although nothing is displayed) and executes in the user’s browser is quite simple. It marshals the require parameters, then munges this into a request for a GIF. In order to tell the difference between a unique visitor and a pageview, we set a cookie upon first visit, which is then checked upon subsequent pageviews. Here’s our JavaScript:

function track() {
    var days = 7; // Number of days to keep cookie alive
    var ru = document.location.href;
    var rf = document.referrer;

    var rest = '';
    if( ru.length > 0 ) {
        if( rf == '' ) {
            rf = '-';
        } else {
            rf = urlencode( rf );
        }

        // If there's a query string, grab it and stick all the parameters on the
        // end.
        var qstring = ru.split( '?' );
        if( qstring.length > 1 ) {
            rest = qstring[ 1 ];
        }

        ru = urlencode( ru );
        rf = urlencode( rf );
        var clicked_time = new Date();
        clicked_time = Math.round(clicked_time.getTime()/1000);

        // Build data.
        var d = 'rf=' + rf;
        if( ru.length > 0 ) {
            d += '&ru=' + ru;

       }
        if( rest.length > 0 ) {
            d += '&' + rest;
        }
        d += '&ct=' + clicked_time;

        // If the cookie already exists for this bonus code, this isn't a unique hit
        var unique = 1;
        old_cookie = readCookie( 'analytics_unique' );
        if( old_cookie != null && old_cookie != "" ) {
            unique = 0;
        }

        // Set cookie.
        setCookie( 'analytics_unique', 'visited', days, '/' ); // For uniqueness

        d += '&u=' + unique;
        // Now request the 1x1 pixel gif to record the click.
        (new Image()).src =  'http://your.analytics.site.com/click.gif?' + d;
    }
    return true;
}

function setCookie( name, value, days, path ) {
    var date = new Date();
    date.setTime( date.getTime() + ( days*24*60*60*1000 ) );
    var expires = "; expires=" + date.toGMTString();
    document.cookie = name + '=' + value + expires + '; path=' + path;
}

function readCookie(cookieName) {
    var theCookie=""+document.cookie;
    var ind=theCookie.indexOf(cookieName);
    if (ind==-1 || cookieName=="") return "";
    var ind1=theCookie.indexOf(';',ind);
    if (ind1==-1) ind1=theCookie.length;
    return unescape(theCookie.substring(ind+cookieName.length+1,ind1));
}

function deleteCookie( cookieName ) {
    if( readCookie( cookieName ) ) {
        setCookie( cookieName, '', 0, '/' );
    }
}

function urlencode(str) {
    str = escape(str);
    str = str.replace(/\+/g, '%2B');
    str = str.replace(/%20/g, '+');
    str = str.replace(/\*/g, '%2A');
    str = str.replace(/\//g, '%2F');
    str = str.replace(/@/g, '%40');
    return str;
}

There are a few convenience methods for reading/writing cookies and encoding the data so things don’t get screwed up when we request the image. The final piece is to add a rewrite rule so our controller gets hit with any requests to click.gif:

RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_URI} ^/click\.gif\?
RewriteRule ^/click\.gif\?(.*) /hit?$1 [L]

The above just strips off all our GET parameters and feeds them to our hit controller, which we know returns a 1×1 pixel gif.


Extensions

You could extend the above to include more information about the user’s browser such as platform, Java-enabled, Flash version, JavaScript version or screen resolution. With some post-processing you’d be able to do geolocation on the user’s IP, and strip out keywords from search engines or PPC campaign variables. If you added a little more information to the uniqueness cookie, you’d be able to record bounce rate and time on page.

I’ve completely glossed over how the data should be presented to the users (your affiliates.) Most affiliate systems show total clicks, uniques and sales grouped by date, time of day or campaign ID. Of course, the main benefit of writing your own engine from scratch is you can offer affiliates things that other programs don’t show them such as referrer URL, search terms, PPC campaign variables and geographic location.

Programming ,

cPanel Upload Only FTP

April 2nd, 2009

In some cases you want to set up an “upload only” FTP account, which allows a user to upload files, but not list directories, download or delete files. This is typically used when you want to give out the credentials for an account to multiple people so they can upload content (mp3s, videos, resumes, etc.) but not affect what others are doing.

cPanel gives you two choices of FTP server - PureFTP or ProFTP. PureFTP is simpler and smaller if you just want a quick and dirty FTP site. For more advanced configuration, ProFTP is recommended. ProFTP has a feature that is a lot like Apache’s .htaccess file, allowing you do make per-directory configurations without modifying the main config file. Any directives that can appear in a ProFTP <Directory> stanza can appear in an .ftpaccess file and will be applied to the directory in which the file resides.

First make sure you’re running ProFTP and not PureFTP - you can change this setting with no loss of information from within WHM. If you don’t have WHM access, you’ll need to ask your provider to switch FTP servers. Here’s the config for an upload-only FTP account called ‘uploadonly’ - the account will need to have been created from cPanel first. Name this file .ftpaccess and put it in the directory where you want the uploads to appear. I usually create a separate directory and apply quotas.

    <Limit ALL>
        DenyUser uploadonly
    </Limit>

     <Limit CDUP CWD XCWD XCUP>
        AllowUser uploadonly
    </Limit>

    <Limit STOR STOU>
        AllowUser uploadonly
    </Limit>

This type of configuration where we deny everything, then allow only what we want is more secure than allowing everything and denying specific operations. More configuration options are shown on ProFTP’s configuration page - anything that can belong in a <Directory> stanza can go in the .ftpaccess file and avoids having to edit cPanel’s ProFTP configuration file.

System Administration , ,