Made of Everything You're Not

Writing code... well, forever really. Sigh...
  • Home
  • Projects
  • Portfolio
  • Resume

Archive for January, 2009

WordPress Plugin in 12 hours

Posted in Code, Programming on January 3rd, 2009 by Eric Lamb – 5 Comments

In “Why I’m writing my own blog system” I announced my plans to build a custom blog platform. Seeing as I wrote about it before I’d actually written the script I had to publish the announcement on a different blog system; WordPress. Something happened while I was using WordPress though; I actually started enjoying it. The sophistication behind the scenes really started to pique my interest and get me excited. I started looking at plugins and getting into how themes are put together; it’s a really a well thought out and executed product.

Now, with the above realization came another; I shouldn’t write a blog system anymore, WordPress is just too damn good at it. Doing so would be the picture of reinventing the wheel; it would just be a waste of my time. That’s not to say it’s perfect. There are definitely a few areas that WordPress is lacking in, for my needs especially. It’s not really an issue though because the developers thought of this and implemented a very well thought out plugin system.

One of the more obvious features I wanted, that WordPress is lacking, is some sort of tracking mechanism for links in posts. Basically, I want to know how many times links in my posts have been clicked so I decided to write a plugin to handle this. Surprisingly, I was able to get a working version up and going in about 12 hours of total work.

The Plan

Well, as I stated above the plugin is going to allow the tracking of link clicks in posts and pages. I want this to be as painless as possible to manage so there won’t be any CRUD portion. Initially, the plugin will extract all links out of the post on the user side and modify them to allow for tracking. The tracking should delineate between unique and repeat clicks and stats should be displayed on the edit page. The plugin should be installable through the WordPress plugin manager

The Basics

A plugin is just a php script that follows some formatting and business rules so it’s pretty straight forward once you get the requirements out of the way. Here are the basics:

Plugins should all be placed in the /wp-content/plugins directory. It’s also a good idea to place your plugin in it’s own sub directory; this way all your files are segregated into a single place.

All plugins require a specific header comment format:

1
2
3
4
5
6
7
8
9
10
<?php
/*
Plugin Name: Name Of The Plugin
Plugin URI: http://URI_Of_Page_Describing_Plugin_and_Updates
Description: A brief description of the Plugin.
Version: The Plugin's Version Number, e.g.: 1.0
Author: Name Of The Plugin Author
Author URI: http://URI_Of_The_Plugin_Author
*/
?>

Right away I noticed how the above comment isn’t in phpdoc but it turns out that’s ok; you can still put that above anything else.

After that is where you can put the license:

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
/*  Copyright YEAR  PLUGIN_AUTHOR_NAME  (email : PLUGIN AUTHOR EMAIL)
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
?>

Having the above in a file placed in the plugin directory is enough to have the plugin displayed in the plugin manager.

The functionality of the plugin comes from custom functions that are placed in hook wrappers. The hook wrappers work through various actions and filters which are accessed through add_action() and add_filter() like the below:

29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
function plugin_filter_function($content){
	//do something to the post content
	return $content;
}
 
function plugin_action_function(){
	//execute some code...
 
}
add_filter('the_content', 'plugin_filter_function', 1);
add_action('wp_head', 'plugin_action_function');
?>

To activate a plugin there might be some preparation needed for the plugin; the same for deactivating. To execute functionality during both processes:

40
41
42
43
44
45
46
47
48
49
50
51
<?php
function plugin_install_function(){
	//execute some code...
}
 
function plugin_deactivate_function(){
	//execute some code...
 
}
register_activation_hook(__FILE__,'plugin_install_function');
register_deactivation_hook(__FILE__, 'plugin_deactivate_function');
?>

It’s also possible to disable some built in actions and functions, though I didn’t need to do this on my plugin.

50
51
52
53
<?php
remove_filter('filter_hook','filter_function')
remove_action('action_hook','action_function')
?>

The Program

With the above out of they way all that’s left is the actual plugin code. Here’s the basic outline:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<?php
/**
 * Wordpress Click Tracker
 *
 * Contains all the click tracker functions.
 *
 * @author Eric Lamb <eric@ericlamb.net>
 * @package    wp-click-tracker
 * @version 0.1
 * @filesource
 * @copyright 2009 Eric Lamb.
 */
/*
Plugin Name: Click Tracker
Plugin URI: http://blog.ericlamb.net/
Description: Tracks all links in posts.
Version: 0.1
Author: Eric Lamb
Author URI: http://blog.ericlamb.net
*/
 
/*  Copyright 2009  Eric Lamb  (email : eric@ericlamb.net)
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
 
/**
 * Version of wp-click-tracker code
 * @global string	$click_tracker_version 
 */
$click_tracker_version = "0.1";
 
/**
 * Version of wp-click-tracker database
 * @global string	$click_tracker_db_version 
 */
$click_tracker_db_version = "0.1";
 
/**
 * Wrapper for click track filter
 *
 * @param   string  $content	string to parse
 * @return  string				The completed 
 */
function wp_click_tracker_filter($content)
{
	return wp_click_tracker_replace_uris($content);
}
 
/**
 * Takes a string and extracts all the links (URIs)
 *
 * @param   string  $content     string to parse
 * @return  array	The links
 */
function wp_click_tracker_extract_urls($content){
 
}
 
/**
 * Replaces all the URLs in a string with newly created URLs for tracking
 *
 * @param   string  $content	string to parse
 * @return  string	$content	The filtered content
 */
function wp_click_tracker_replace_uris($content){
 
}
 
/**
 * Installs the database tables and settings
 *
 * @return  void
 */
function wp_click_tracker_install () {
	global $wpdb;
	global $click_tracker_db_version;
	global $click_tracker_version;
 
	add_option("click_tracker_version", $click_tracker_version);
	add_option("click_tracker_db_version", $click_tracker_db_version);
}
 
/**
 * Handles the removal of a plugin
 *
 * @return  void
 */
function wp_click_tracker_deactivate(){
 
}
 
/**
 * Adds the javascript embed to the header
 *
 * @return  void
 */
function wp_click_tracker_head()
{
 
}
 
/**
 * Execution of click tracker 
 *
 * @return  void
 */
function wp_click_tracker_go(){
 
}
 
/**
 * Displays administration quick view 
 *
 * @return  void
 */
function wp_click_tracker_link_data(){
 
}
 
/**
 * Adds the click tracker options page to the administration module
 *
 * @return	void
 */
function wp_click_tracker_menu() {
  add_options_page('Click Tracker Options', 'Click Tracker', 8, __FILE__, 'wp_click_tracker_options');
}
 
/**
 * Displays the click tracker administration module
 *
 * @return  void
 */
function wp_click_tracker_options() {
 
}
 
register_activation_hook(__FILE__,'wp_click_tracker_install');
register_deactivation_hook(__FILE__, 'wp_click_tracker_deactivate');
 
add_action('edit_form_advanced', 'wp_click_tracker_link_data');
add_action('edit_page_form', 'wp_click_tracker_link_data');
 
add_action('wp_head', 'wp_click_tracker_head');
add_action('init','wp_click_tracker_go');
 
add_filter('the_content', 'wp_click_tracker_filter', 1);
add_filter('the_excerpt', 'wp_click_tracker_filter', 1);
?>

Granted, the above doesn’t really do anything; it’s only the framework. Still, I was able to get the research and above code done in a little under 6 hours. After that it’s just like any other project (write function, test, fix bugs, write new funtion, etc…) which only took an additional 6 hours.

Download the complete plugin.
Click Tracker

Resources

Writing a Plugin
Plugin API
WordPress Hooks
WordPress Code Documentation

Bookmark and Share

A journey into php-cli and scraping

Posted in Code, Programming on January 1st, 2009 by Eric Lamb – Be the first to comment

I recently had a couple days to myself and I wanted to experiment more with this php-cli thing I’d been thinking about. To help the process (and feed my guitar addiction; I have a serious problem) I decided to write a script to hit up the Stupid Deal page for Musicians Friend and send me an email if the deal of the day matched a given term list.

Prep

I’m pretty sure all Windows installs of php include php-cli but to check execute this in the cmd:
Download

php -v

You should see something like the below; note (cli):

PHP 5.2.6 (cli) (built: May  2 2008 18:02:07)
Copyright (c) 1997-2008 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2008 Zend Technologies
with Xdebug v2.0.3, Copyright (c) 2002-2007, by Derick Rethans

Assuming it’s all worked out here are some additional requirements:
1. Must work like *nix cli program; it’s just going to make things easier for me. For example the program should be executed like:

C:\ProjectFiles\php_cli>php check_for_guitars.php --search="guitar,amp,tablature" --email="foo@bar.com"

2. Must have error checking and validation.
3. Must prevent duplicate notifications.
4. Provide a “help” mode (–help, -help, -h, -?).
5. Ability to be set as Automated Task (Windows Cron equivalent)

Argument Handling

To begin, I needed to change the way passed parameters are interpreted. Before version 5.3, php handled parameters passed to scripts in a pretty messed up way; but there’s a function available in the notes of the php manual that helps a lot.
inc.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function arguments($argv) {
   $_ARG = array();
   foreach ($argv as $arg) {
       if (preg_match('#^-{1,2}([a-zA-Z0-9]*)=?(.*)$#', $arg, $matches)) {
           $key = $matches[1];
           switch ($matches[2]) {
               case '':
               case 'true':
               $arg = true;
               break;
               case 'false':
               $arg = false;
               break;
               default:
               $arg = $matches[2];
           }
 
           /* make unix like -afd == -a -f -d */            
           if(preg_match("/^-([a-zA-Z0-9]+)/", $matches[0], $match)) {
               $string = $match[1];
               for($i=0; strlen($string) > $i; $i++) {
                $_ARG[$string[$i]] = true;
               }
           } else {
               $_ARG[$key] = $arg; 
           }            
       } else {
           $_ARG['input'][] = $arg;
       }        
   }
   return $_ARG; 
}

Using the above function works like so:

C:\ProjectFiles\php_cli>php check_for_guitars.php --search="guitar,amp,tablature" --email="foo@bar.com"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$input = arguments($argv);
print_r($input);
/*
Array
(
    [input] => Array
        (
            [0] => get_music.php
        )
 
    [search] => guitar,amp,tablature
    [email] => foo@bar.com
)
*/

Now that we can access the passed variables we need to validate and verify them like any other script. The code below checks if a key is present in the $input array and if not goes into a loop sending a request to STDIN and validates the returned value; if TRUE it breaks out of the loop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//make sure we have a value for "search"
$validate_search = FALSE;
if(!array_key_exists('search',$input)){
	$validate_search = TRUE;
} else {
	if(strlen($input['search']) <= 2){
		$validate_search = TRUE;
	}
}
 
if($validate_search){
	echo "Please enter what to search for:\n"; 
	while(1){
 
		$input['search'] = trim(fgets(STDIN)); // reads one line from STDIN
		if(strlen($input['search']) <= 2){//it's a valid string
			break;
		}
		echo "Please enter a something to search for ";
		echo "(at least 2 charachters:\n";
		echo "Example: \"guitar,bass,dvd\"\n";
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//make sure we have a valid email address
$validate_email = FALSE;
if(!array_key_exists('email',$input)){
	$validate_email = TRUE;
} else {
	if(!checkEmail_basic($input['email'])){
		$validate_email = TRUE;
	}
}
 
if($validate_email){
	echo "Please enter an email to send the alert to:\n"; 
	while(1){
 
		$input['email'] = trim(fgets(STDIN)); // reads one line from STDIN
		if(checkEmail_basic($input['email'])){//it's a valid email
			break;
		}
		echo "Please enter a valid email address:\n";
	}
}

Help

To access the help mode there’s an example there that maintains the *nix tradition of “–help, -h or -?” like the below:

C:\ProjectFiles\php_cli>php check_for_guitars.php --help
 
Takes a given string (--search) and searches the
Stupid Deal of the Day for a match. If a match is
found an email is sent to (--email)
 
 Usage:
 check_for_guitars.php <option>
 
 <option> With the --help, -help, -h,
 or -? options, you can get this help.
 
 Example:
 check_for_guitars.php --search="term1" --email="foo@bar.com"

The accompanying php code works like the below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
/**
 * Check if we're dealing with 0 paramaters or help
 */
if(isset($argv[1]) && in_array($argv[1], array('--help', '-h', '-?'))) {
?>
Takes a given string (--search) and searches the 
Stupid Deal of the Day for a match. If a match is 
found an email is sent to (--email)
 
 Usage: 
 <?php echo $argv[0]; ?> <option>
 
 <option> With the --help, -help, -h,
 or -? options, you can get this help.
 
 Example:
 <?php echo $argv[0]; ?> --search="term1" --email="foo@bar.com"
<?php } ?>

Now that the above is done things are starting to work just like a traditional web app.

Grab and Parse Page

The first thing we need to do is get the actual page. To do this I used Snoopy.

1
2
3
4
5
6
$uri_to_check = 'http://www.musiciansfriend.com/stupid';
$snoopy = new Snoopy;
$snoopy->agent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)";
$snoopy->referer = "http://www.yahoo.com/";
$snoopy->fetch($uri_to_check);
$results = $snoopy->results;

The above returns the entire contents of $uri_to_check into a string in $results. Now we need to parse $results and find all the values we need. Here’s how to get the page title:

1
2
3
$pattern = "'<[^>]*h1[^>]*>(.*?)<[^>]*/h1[^>]*>'";
preg_match($pattern, $results, $match);
$page_title = $match['1'];

Next, find out if there is a match in $input['search'] and create an array of the values:

1
2
3
4
5
6
7
8
9
10
//check if there's a match in the passed $input['search'] array
$total = count($input['search']);
$match_for = array();
$FOUND = FALSE;
for($i=0;$i<$total;$i++){
	if(stristr($page_title, trim($input['search'][$i])) !== FALSE) {
		$match_for[] = trim($input['search'][$i]);
		$FOUND = TRUE;
	} 
}

Basically, if $FOUND is TRUE than check if an alert has already been sent and send a new alert if not:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$htmlmessage = <<<HTML
Match found for <a href="$uri_to_check">%%search%%</a><br>
Title: %%title%% <br>
Sale Price: %%sale_price%%<br>
Original Price: %%og_price%%<br>
HTML;
if($FOUND){
 
	//check if the search was done today...
	$sql = "SELECT * FROM mf_checks WHERE title = '".$DB->es($page_title)."' AND DATE_FORMAT(`date_checked`,'%m') = '".date('m')."' AND DATE_FORMAT(`date_checked`,'%d') = '".date('d')."' AND DATE_FORMAT(`date_checked`,'%Y') = '".date('Y')."' LIMIT 1";
	$DB->query($sql);
	if($DB->getNumRows() == '1'){ //alert has already been sent so break out...
		echo "Already sent today... exiting...";
		exit;
	}
 
	//match was found so get the price now
	$price_arr = explode('<div style="font-size:3em;color:#FF0000;font-weight:normal;padding:20px 0;">',$results);
	$price_arr = explode("\n",$price_arr['1']);
	$sale_price = strip_tags($price_arr['0']);
	$og_price = str_replace('Reg ','',strip_tags($price_arr['1']));
 
	$htmlmessage = str_replace(array('%%search%%','%%title%%','%%sale_price%%','%%og_price%%'),array('"'.implode(', ',$match_for).'"',$page_title,$sale_price,$og_price),$htmlmessage);
 
	$mail = new Mailer();
	$mail->From = $input['email'];
	$mail->FromName = $input['email'];
	$mail->Subject = 'Found: '.$page_title;
	$mail->AltBody = strip_tags($htmlmessage);
	$mail->MsgHTML($htmlmessage);
	$mail->AddAddress($input['email']);
	if($mail->Send()){
		echo "Mail Sent";
	} else {
		echo "Mail Not Sent";
	}
 
	//add to the db 
	$sql = "INSERT INTO mf_checks SET term = '".$DB->es(implode(', ',$match_for))."', title = '".$DB->es($page_title)."', sale_price = '".$DB->es($sale_price)."', og_price = '".$DB->es($og_price)."', date_checked = now(), alert_sent = '1'";
	$DB->query($sql);
}

Automating

To set the script to automatically check on a regular interval you have to setup an Automatic Task in Start->Programs->Accessories->System Tools->Task Scheduler and add something like the below to the Triggers tab of a new task:

C:\php\php-win.exe C:\ProjectFiles\php_cli>php check_for_guitars.php --search="guitar,amp,tablature" --email="foo@bar.com"

Note the full path to php-win.exe. If you use “php” by itself you’ll get an annoying dos box popping up every time the script executes; use the full path to your php-win.exe program.

Code

Download Check Guitar

Bookmark and Share
Newer Entries »
  • Subscribe: Entries | Comments
  • About Me

    Email Email
    Twitter Twitter
    310.739.3322
  • Categories

    • Brain Dump
    • Business
    • Code
    • IT
    • Programming
    • Rant
    • Servers
  • Archives

    • October 2011
    • August 2011
    • July 2011
    • June 2011
    • May 2011
    • April 2011
    • March 2011
    • February 2011
    • January 2011
    • December 2010
    • November 2010
    • October 2010
    • September 2010
    • August 2010
    • July 2010
    • June 2010
    • May 2010
    • April 2010
    • March 2010
    • February 2010
    • January 2010
    • December 2009
    • November 2009
    • October 2009
    • September 2009
    • August 2009
    • July 2009
    • June 2009
    • May 2009
    • April 2009
    • March 2009
    • February 2009
    • January 2009
    • December 2008
    • November 2008
    • October 2008

Copyright © 2008 - 2012 Eric Lamb - All rights reserved