Object Oriented Hook Management in PHP5

One of the nice features of WordPress is the plugin system, where ‘hooks’ allow custom functionality to be added without monkeying around with the core functionality.

I haven’t played around with this myself yet, but from what I could gather from the documentation, it does this using functions which are all defined in the global scope.

I was wondering today whether similar functionality could be achieved using a more object-oriented approach.

I wanted to be able to extend core functionality of one object by registering ‘hook’ methods on other objects at specific execution points. So I threw together the following code as a demo:


/**
* Binding class - represents a single event binding
*/
class Binding {
var $oObj;
var $sMethod;

/**
* Constructor
* @param object &$oObj     the object on which we wish to call a method, passed by referenct
* @param string $sMethod the method to call
*/
function __construct(&$oObj, $sMethod) {
$this->oObj         = $oObj;
$this->sMethod     = $sMethod;
}

/**
* Trigger the event
* @param $aArgs an array of arguments for the bound function
*/
function trigger($aArgs = array()) {
call_user_func_array(array($this->oObj, $this->sMethod), $aArgs);
}
}

/**
* Singleton class to hold all event bindings
*/
class Hook {
/**
* Stores the static instance of this object - singleton design pattern
* @param object
*/
private static $instance;

/**
* Array of hooks
*/
private $aHooks = array();

/**
* Constructor
*/
private function __construct() {

}

/**
* get instance of this object
*/
public static function getInstance() {
if (!self::$instance)
{
self::$instance = new Hook();
}

return self::$instance;
}

/**
* Bind functionality
* @param string $sEvent     the name of the event to bind
* @param object &$oObj         the object whose method we wish to call
* @param string $sMethod the method to call
*/
function bind($sEvent, &$oObj, $sMethod) {
$this->aHooks[$sEvent][] = new Binding($oObj, $sMethod);
}

/**
* Trigger a bound event
* @param string $sEvent    the event to trigger
* @param array  $aArgs        an array of arguments to pass to the bound function
*/
function trigger($sEvent, $aArgs = array()) {
if(array_key_exists($sEvent, $this->aHooks)) {
foreach($this->aHooks[$sEvent] as $key => $Binding) {
$Binding->trigger($aArgs);
}
}
}
}

class Extend {

function __construct() {
$Hook = Hook::getInstance();
$Hook->bind('pre', $this, 'beforeDoSomething');
$Hook->bind('post',$this,    'afterDoSomething');

}

function beforeDoSomething($arg) {
echo '<li>This before... the calling function wanted to say"' . $arg . '"';
}

function afterDoSomething($arg) {
echo '<li>The calling function said "' . $arg . '"';
}
}

class Extend2 {
function __construct() {
$Hook = Hook::getInstance();
$Hook->bind('post', $this, 'beforeDoSomething');
}

function beforeDoSomething() {
echo '<li>This is done in a second extended object';
}
}

/**
* Class providing base functionality
*/
class Base {

function __construct() {

}

function doSomething() {
$Hook = Hook::getInstance();

$Hook->trigger('pre',    array("I'm about to start doing something..."));
echo '<li>doing the core functionality...';
$Hook->trigger('post', array("Ok I'm all done now!"));
}

}

$Base = new Base();
$Extend = new Extend();
$Extend2 = new Extend2();
$Base->doSomething();

Hopefully someone will find it useful :)

JQuery AJAX using XML in Internet Explorer

I spent a while this morning trying to figure out why an JQuery AJAX call wasn’t working in Internet Explorer 8. The call was being made to a PHP script which was outputting XML.

I had checked the XML was valid and (no surprises) it was all working fine in Firefox.

Eventually I found this useful discussion. It turns out you need to have the following header in your PHP script:

header("content-type:application/xml;charset=utf-8");

I was using ‘text/xml’ which is perfectly valid but hey. That’s IE for you.

While I’m doing the usual IE bashing, it should be said that version 8 of the browser is much better both from the point of view of standards support and debugging, so at least progress is being made. Only took 8 versions eh?

Loading XML with JQuery – an XCAL feed

XCAL is an XML implementation of the iCal format, and as such it can be easily read with JQuery.

So, if you’re implementing some kind of calendaring system online, one way to accomplish it could be to read the events from your database, output them in XCAL format, and display them online using our old friend JQuery.

I’m not sure how widespread XCAL is, but at least it’s a standard of some sort and that could be useful, for example if you wanted to subscribe to the calendar using an external program.

Anyway – on with the example:


/**
 * A function to parse the XCAL format date into an object
 */
function parseDate(xCalDate) {

 // Array of abbreviated month names
 var months = new Array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');

 return {
 'year'            :    xCalDate.substr(0,4),
 'month'            :    xCalDate.substr(4,2),
 'monthname'    : months[xCalDate.substr(4,2)-1],
 'day'                :    xCalDate.substr(6,2),
 'hour'            :    xCalDate.substr(9,2),
 'minute'        :    xCalDate.substr(11,2)
 }
}

/**
 * Do this when document is ready
 */
$(document).ready(function() {
 // Load the XML file
 $.get('xcal.xml', null, function(data) {
 // Find each 'vevent' node
 $(data).find('vevent').each(function() {
 // Parse the text string into an object using the above function
 var dateObj = parseDate($(this).children('dtstart').text());
 // Display the event info in the unordered list
 $('#content').append('
	<li><b>' + dateObj.day + ' ' + dateObj.monthname + ' ' + dateObj.year + '</b> ' + dateObj.hour + ':' + dateObj.minute + ':</li>
');
 $('#content li:last').append($(this).children('summary').text());
 $('#content li:last').append(' at ' + $(this).children('location').text());
 })
 }, 'xml');
})

If you want to see it all in action, you can download the whole project, including the XCAL document.

Mosso Cloud Files API with Windows

I spent a while tearing my hair out today trying to connect to the Mosso Cloud Files API with PHP. It seemed to be authenticating OK, but I couldn’t create a container.

The error I got was “Failed to obtain http response”.

It appears as though this has to do with CURL and SSL on Windows machines. For a quick and dirty fix (not for production use) add:

curl_setopt($this->connections[$conn_type], CURLOPT_SSL_VERIFYPEER, 0);

To line 1210 of cloudfiles_http.php

To fully solve the problem, see this great blog post.

For more info on Mosso Cloud Files, soon to become “The Rackspace Cloud”, see the link below:
Cloud Hosting - Formerly Mosso

Cloud Hosting

Loading XML with JQuery

Thankfully JQuery makes it really easy to load a variety of data formats with AJAX:

  • Text
  • HTML
  • JSON (JavaScript Object Notation)
  • XML

Within applications I generally use JSON, as it is less verbose and easier to process than XML. Check out the native PHP5 function or the PEAR JSON package for a way to easily expose data from PHP as JSON.

I occasionally find it useful to read XML data instead, and JQuery lets us interact with it in exactly the same way as we interact with HTML elements.

Here’s a rough and ready example to show how easy it is to read an RSS feed (rss.xml) in, and then display the list of article titles as links in a div with id of ‘content’ in just a few lines of code:


$(document).ready(function() {
$.get('rss.xml', null, function(data) {
$(data).find('channel item').each(function() {
$('div#content').append('<a href="' + $(this).children('link').text() + '">' + $(this).children('title').text() + '</a>
');
})
}, 'xml');
})

Here I’ve use the text() function to read the text value of a node in the XML, you may sometimes also need to use the attr() function to access a particular attribute.

A word of warning: you can’t read data using AJAX from domains other than the current domain for security reasons. To get around this problem, you will need to write a server-side script (eg PHP or ASP) to read the XML feed from the remote server and output it, so it can be accessible by JQuery on your own server.

Updated: you can now download a complete working example here.

JQuery Search Suggester Plugin

While writing a simple knowledgebase application for my web company I wanted to create the facility to automatically suggest articles as the user typed in a question.

So I came up with the following ‘suggester’ plugin. To get it working, you need to:

  • Attach it to an input field
  • Pass in the backend URL to load via AJAX
  • Supply a callback function called results_cb where the data gets sent.

Typically your results_cb function will be responsible for displaying the returned options to the user.

Your backend script needs to respond to the POST variable unimaginatively named ‘search’.

The plugin doesn’t impose any ideas about what sort of data you’re receiving back, or how to display it, but this makes it more flexible and resusable in my opinion.

Here’s an example of how it can be used:


$('input').suggester(

{ url : 'search.php',

results_cb : function(data) { alert(data); },

mode : 'text'}

)

And here’s the plugin itself:


/**
 * JQuery suggester plugin 0.1
 * J A Carmichael
 * www.siteclick.co.cuk
 *
 * License: MIT
 *
 * If you use this, please consider linking to www.siteclick.co.uk :)
 *
 * 12/5/2009
 * attach this to a field, eg $(input).suggester()
 * options:
 * url                :        The URL to call with the search term
 * results_cb    :        A callback to call when data is loaded via AJAX
 * mode                :        the type of data to send to the callback - default JSON
 */

jQuery.fn.suggester = function(settings) {

 // Set defaults
 var settings = jQuery.extend({
 'url'                    :    '',
 'results_cb'    :    function() {},
 'on_search'        :    function() {},
 'mode'                :    'json'
 }, settings);

 // For each item
 return this.each(function(){

 // The field which the user is typing in
 var inputField = this;

 // A flag to indicate whether typing currently
 var typing = false;

 // Current search term
 var searchTerm = '';

 // The last search
 var currentSearch = '';

 /**
 * Function to do the searching every so often
 */
 var searchFunc = function() {
 if(!typing && (searchTerm != currentSearch)) {
 currentSearch = searchTerm;
 // Call the function to indicate we're now searching
 settings.on_search(searchTerm);
 // Send post request
 $.post(settings.url, 'search=' + encodeURIComponent(searchTerm), settings.results_cb, settings.mode);
 }
 // Keep calling the search function
 window.setTimeout(searchFunc, 500);
 }

 /**
 * Handler for when a key is depressed on this field
 */
 jQuery(inputField).keyup(function() {
 typing = true;

 // Update the search term
 searchTerm = $(this).val();

 // Reset the typing flag after a certain time
 window.setTimeout(function() {
 typing = false;
 }, 500);
 })

 // Start searching
 searchFunc();

 })
}

Simplifying HTML Form CSS with JQuery

Thanks to CSS, it’s possible to make nicely styled web forms without a table in site. Often we wish to add CSS styling to our input fields – but there’s a snag which is any checkbox or radio field ends up with the same styling. We end up with ugly borders and wierd gaps everywhere around checkboxes and radio buttons.

In the future, CSS will provide a solution with “attribute selectors” as so:


input {

  width:  100px;

  border:  1px solid #cde;

}

input[type="radio"], input[type="checkbox"] {

  width:  auto;

  border: 0;

}

But, as with most things in life, this has not been supported by Internet Explorer until recently. So, JQuery to the rescue. We can change our CSS to:


input {

  width:  100px;

  border:  1px solid #cde;

}

input[type="radio"], input.radio, input[type="checkbox"], input.checkbox {

  width:  auto;

  border: 0;

}

And add a little JQuery:


$(document).ready(function() {

  $('input[type=radio]').addClass('radio');

  $('input[type=checkbox]').addClass('checkbox');

})

We can take advantage of JQuery’s support for attribute selectors to apply a CSS class to the checkbox and radio button elements, applying the same CSS rules as if attribute selectors had been supported by the browser.

A JPEG to PNG Processor in PHP

So for our new CMS project I needed a way to turn a logo as a JPEG on a white background, into a transparent PNG.

This didn’t turn out to be particularly straightforward, as due the JPEG compression, the white part of the image is noisy. So it was quite tricky to cut out the logo and have a smooth edge.

So I came up with the following algorithm, mostly by trial and error. It works by calculating a cost function, based on the brightness and greyness of a particular pixel. If a pixel is both very bright, and doesn’t have much of a hue, it will be made more transparent. This gives a smooth edge to the cut out logo.

This could be useful for example in an online shop where you might have product images on a white background that you wish were transparent.

Among other things, you will need PHP5 and the GD libraries to get this working.

You use it something like so:


$Im = new AutoTransparency();

$Im->loadFromFile('sourcefile.jpg');

$Im->process();

$Im->toFile('destinationfile.png');

There’s also a handy ‘resample’ function built in so you can apply that method if you’d like the PNG to be a different size aswell.

<?php

/**
 * A class designed to load a JPEG image on a white background
 * and automatically convert to a PNG with a transparent background
 * using an alpha transparency algorithm
 *
 * License: MIT
 *
 * Please link to www.siteclick.co.uk if you use this code in your project - thanks :)
 *
 * (c)    13/5/2009    J A Carmichael
 */

class AutoTransparency {

 /**
 * The maximum possible value for alpha transparency
 * @param int
 */
 var $max_alpha    =    127;

 /**
 * The maximum possible value for RGB values
 * @param unt
 */
 var $max_rgb        = 255;

 /**
 * The image resource loaded
 * @param int
 */
 var    $im;

 /**
 * The newly created image resource
 * @param int
 */
 var $newim;

 /**
 * Original width &height of image
 * @param int
 */
 var $w_orig;
 var $h_orig;

 /**
 * Constructor
 */
 function __construct () {

 }

 /**
 * load source image from JPEG
 */
 function loadFromFile($file) {
 $this->im = imagecreatefromjpeg($file);
 }

 /**
 * Do the process
 */
 function process() {

 // Get image dimensions
 $this->w_orig = imagesx($this->im);
 $this->h_orig = imagesy($this->im);

 // Create new image
 $new = imagecreatetruecolor( $this->w_orig, $this->h_orig );

 // Turn off alpha blending and set alpha flag
 imagealphablending($new, false);
 imagesavealpha($new, true);

 // Set a White & Transparent Background Color
 $bg = imagecolorallocatealpha($new, 255, 255, 255, 127);
 imagecolortransparent($new, $bg);

 // Go over each pixel
 for($x =0; $x < $this->w_orig; $x++) {
 for($y = 0; $y < $this->h_orig; $y++) {

 // Get RGB values of colour at current pixel
 $rgb = imagecolorat($this->im, $x, $y);
 $r = ($rgb >> 16) & 0xFF;
 $g = ($rgb >> 8) & 0xFF;
 $b = $rgb & 0xFF;

 // Brightness defined 0-1
 $brightness = ($r + $g + $b) / (3 * $this->max_rgb);

 // Greyness by measuring difference between channels defined 0-1
 $rg = 255 - abs($r - $g);        //     Range 0-255
 $rb = 255 - abs($r - $b);     //     Range 0-255
 $gb = 255 - abs($g - $b);     //     Range 0-255
 $greyness   = ($rg + $rb + $gb) / (3 * $this->max_rgb);

 // Alpha value - combines the brightness and greyness of the image using power laws so they drop off rapidly
 $alpha = $this->max_alpha * pow($brightness, 2) * pow($greyness, 3);

 // Create new partially transparent colour
 $new_colour = imagecolorallocatealpha( $new , $r , $g  , $b  , $alpha);

 // Set pixel with new partially transparent colour in new image
 imagesetpixel ($new  , $x, $y , $new_colour );

 }
 }

 $this->newim = $new;

 }

 /**
 * Save the new PNG to the specified file
 */
 function toFile($file) {
 imagepng($this->newim, $file);
 }

 /**
 * Resample
 */
 function resample($iNewWidth = 100) {
 settype($iNewWidth, 'int');

 // Aspect ratio
 $r  = $this->w_orig / $this->h_orig;           // Aspect ratio

 // Calculate new height
 $iNewHeight = floor($iNewWidth/$r);

 $newim           = imagecreatetruecolor($iNewWidth, $iNewHeight);        // Create new image

 // Turn off alpha blending and set alpha flag
 imagealphablending($newim, false);
 imagesavealpha($newim, true);

 $success         = ImageCopyResampled($newim, $this->newim, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $this->w_orig, $this->h_orig);

 $this->newim = $newim;
 }

 /**
 * Stream to output
 */
 function stream() {
 header('content-type: image/png');
 imagepng($this->newim, null);
 }

}

?>

5 Awesome JQuery Plugins

A bit like the film “High Fidelity”, I thought I’d do a top 5 of awesome JQuery plugins!

So here goes:

  • JQuery UI – all sorts of goodies here – make items on pages draggable, droppable, sortable and more. There’s also handy widgets such as a great date picker, accordion lists, tabs and more – superb!
  • Colour Picker – a great colour picker which I found easy to implement, and which works on the HSB model so is intuitive to use.
  • Livequery - allows you to automatically attach behaviours to elements, even if they’re not yet on the page! Magic.
  • Uploadify - only discovered this yesterday, but it lets you easily select and upload multiple files at once thanks to using a Flash SWF to perform the uploading.
  • JQuery Lightbox – a JQuery implementation of the popular lightbox gallery effect.

Houmous Recipe

I know this isn’t strictly code-related (OK it’s not even remotely code related), but whenever we have visitors I make this houmous recipe and people seem to enjoy it so I thought I’d spread it around a bit:

  • 1 can of chickpeas drained and washed (also called garbonzo beans)
  • juice of half a lemon
  • 1 clove of garlic
  • 1/4 cup (60 ml) dark tahini (you can use normal light tahini to)
  • 1/4 cup (60 ml) extra virgin olive oil
  • 1/4 cup (60 ml) cold water
  • 3/4 teaspoon salt
  • Smoked paprika for sprinkling

Method:

  1. Put everything except the paprika in the food processor and blend until smooth
  2. Put in a bowl and sprinkle with the paprika
  3. Munch with pitta breads toasted and cut into slices

Bon appetit!