<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	>

<channel>
	<title>Stut.net</title>
	<atom:link href="http://stut.net/blog/feed/" rel="self" type="application/rss+xml" />
	<link>http://stut.net/blog</link>
	<description>Ramblings of a random software engineer</description>
	<pubDate>Sat, 26 Jul 2008 19:37:15 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.6.1</generator>
	<language>en</language>
			<item>
		<title>Sessionless Sessions</title>
		<link>http://stut.net/blog/2008/07/26/sessionless-sessions-2/</link>
		<comments>http://stut.net/blog/2008/07/26/sessionless-sessions-2/#comments</comments>
		<pubDate>Sat, 26 Jul 2008 19:11:47 +0000</pubDate>
		<dc:creator>Stut</dc:creator>
		
		<category><![CDATA[Coding]]></category>

		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://stut.net/blog/?p=227</guid>
		<description><![CDATA[
Ok, so the title is a little misleading, but hopefully only until I explain what I mean. The first session refers to PHP sessions, and the second refers to the concept of sessions. Maybe it needs a little more explanation than that.

Do what?

There aren&#8217;t many PHP applications (or indeed web applications) out there that don&#8217;t [...]]]></description>
			<content:encoded><![CDATA[<p>
Ok, so the title is a little misleading, but hopefully only until I explain what I mean. The first <i>session</i> refers to PHP sessions, and the second refers to the concept of sessions. Maybe it needs a little more explanation than that.
</p>
<h2>Do what?</h2>
<p>
There aren&#8217;t many PHP applications (or indeed web applications) out there that don&#8217;t need to maintain some form of user state between page requests. This functionality is commonly known as sessions, and most web development languages provide a built-in mechanism for managing them, and PHP is no exception. It&#8217;s probably safe to assume that most people reading this probably know what sessions are and how they work, but I think it&#8217;s worth taking a moment to spell it out.
</p>
<p>
The basic idea of sessions is to retain data between page requests without exposing that data to the client. The common way to do this is to store an ID in a cookie that gets passed between the browser and the server as part of every request. If cookies are unavailable then the ID can be added to all internal URLs as GET or POST variables. On the server site this ID refers to some sort of data storage, whether it be a file, a row in a database or an item in Memcache. To restore the session the application simply reads the data corresponding to the ID it&#8217;s been passed, and when the request ends it writes it back with any changes.
</p>
<p>
Sessions time out in two ways. The cookie will usually live until the user closes their browser. Most implementations will also clean sessions from the server if they are not used for a period of time.
</p>
<h2>The issues</h2>
<p>
The default implementation of sessions in PHP (file-based) is perfectly adequate for most applications. However, as an application scales you will almost certainly need to spread the load across multiple servers which creates issues for session management.
</p>
<p>
One solution is known as sticky sessions. This is where the load balancer keeps track of which session ID&#8217;s are on which server and routes subsequent requests accordingly. This creates a shedload more work for the load balancer and depending on usage patterns can lead to an uneven distribution of load.
</p>
<p>
Another solution is to store the session data in a shared resource such as a database or Memcache. This can create excessive load on that shared resource since sessions will generally be accessed on every page request. If you&#8217;re lucky enough to need to scale beyond the point where a single database server can handle that load then you&#8217;re looking at solutions such as sharding the sessions across multiple database servers and things start to look decidedly over-complicated. That&#8217;s the problem that the architecture this post describes aims to solve.
</p>
<p><span id="more-227"></span></p>
<h2>Motivation</h2>
<p>
Last year (mid-late 2007) we were lucky enough to be faced these issues at <a href="http://uk.freeads.net/">Freeads Classifieds</a> so I set about finding a solution that would scale without causing further complications. My motivation was simple&#8230; I wanted a system that required near-zero maintenance, and a complex session management system was not going to help achieve that.
</p>
<h2>Analysis</h2>
<p>
I started with an analysis of how our application was using sessions. I should note that I inherited this site in early 2007. While it was pretty solid it was clear it had evolved rather than being designed and the way it was using sessions reflected this. I should also note that it&#8217;s no longer like that!
</p>
<p>
The main points I emerged with were&#8230;
</p>
<ul>
<li>A session is created whenever a user logs in.</li>
<li>Most of the time there is very little data stored in the session.</li>
<li>A lot of the data that&#8217;s stored in the session is the same for all users.</li>
<li>A lot of the data that&#8217;s stored in the session is easily (and efficiently) obtained from other places.</li>
</ul>
<p>
It occurred to me that fundamentally the only thing the session actually needs to contain is the user ID. Everything else can be obtained from the database as needed. However, effective scalability requires that database usage is kept to a minimum, and this led me on to consider how user data is utilised while the user is browsing around the site.
</p>
<p>
As with most sites it has two distinct parts, the public site and the users area.
</p>
<p>
The public site has few user-specific data requirements.
</p>
<ul>
<li>We show a small menu on every page when a user is logged in that includes the users name.</li>
<li>The menu also states how many messages the user has and how many of those are unread.</li>
<li>Various forms around the site are pre-filled with the users name, email address and phone number.</li>
</ul>
<p>
So that gives us a need for the users name, email address, phone number and message count for the majority of the site.
</p>
<p>
In the users area practically every page needs to access the database to provide CRUD functionality. The only thing that ties all that together is the user ID, so let&#8217;s add that to our list.
</p>
<p>
I thought for a long time about whether there was any point in caching more in the session when in the users area, but I came to the conclusion that it wasn&#8217;t worth it. If the session becomes too large then it has to be stored server-side rather than being passed with each request, and that takes us back to the issues discussed above. If we&#8217;d need to resort to storing the data in a database why not just get the data from where it normally lives rather than duplicating it?!
</p>
<h2>Implementation</h2>
<p>
So I now had a limited amount of data I wanted to store between page requests, all I had to do was figure out where. It didn&#8217;t take long to realise that I could easily put it where PHP would normally put the session ID - a cookie. Note that you could also pass it as a GET or POST variable if required, but Freeads has always required cookies and it&#8217;s never been a problem in the past.
</p>
<p>
Clearly the cookie needs to be encrypted - we don&#8217;t want malicious users to be able to change its contents. For this I turned to the <a href="http://php.net/mcrypt">Mcrypt</a> extension. I&#8217;d used it before for server-side encryption and have found it to be reliable and performant so it ticked all the boxes.
</p>
<p>
My user implementation is a class, but I only intend to show some extracts. Hopefully it will be clear where stuff like <i>self::LoginTokenKey</i> and <i>self::TokenCookieName</i> are coming from.
</p>
<pre name="code" class="php">
public function CreateLoginToken()
{
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    $token = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, self::LoginTokenKey, serialize($GLOBALS['__USER__']), MCRYPT_MODE_ECB, $iv);
    Cookies::Set(self::TokenCookieName, base64_encode($token));
}
</pre>
<p>
The encrypted value is not limited to printable characters so I base64_encode it before sticking it in the cookie. The global variable is an array containing 4 of the 5 data elements we need to store. The message count is the only part of the set that needs to be re-read on every page request so that&#8217;s done via an AJAX request so that database hit doesn&#8217;t delay the page; it&#8217;s not critical information, so if it doesn&#8217;t show it&#8217;s not the end of the world.
</p>
<p>
The second parameter to <i>mcrypt_encrypt</i> is the key. This is what <i>secures</i> the encrypted data and should be a string that&#8217;s non-trivial and ideally completely unrelated to your application. No I won&#8217;t tell you what we use, but I&#8217;m certain you&#8217;ll never guess it!
</p>
<p>
The other side of the equation is the decryption. If the cookie exists this method decrypts the data and stuffs it into the global var.
</p>
<pre name="code" class="php">
public function TokenLogin()
{
	$retval = false;
	$c = Cookies::Get(self::TokenCookieName);
	if (strlen($c) > 0)
	{
		$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
		$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
		$userdata = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, self::LoginTokenKey, base64_decode($c), MCRYPT_MODE_ECB, $iv);
		$GLOBALS['__USER__'] = unserialize($userdata);
		if ($GLOBALS['__USER__'])
		{
			$retval = true;
		}
		else
		{
			unset($GLOBALS['__USER__']);
		}
	}
	return $retval;
}
</pre>
<p>
Pretty simple stuff really.
</p>
<h2>The real world</h2>
<p>
Ok, so life&#8217;s never that simple. In the real implementation I also store the current time in the data and apply a separate timeout when doing a TokenLogin. Actually it&#8217;s not that simple either.
</p>
<p>
I&#8217;ve modified it to use two cookies. The first is basically the one shown above but if the user ticks the <i>Remember Me</i> option when logging in I set the expiry for this cookie to one year. This essentially means that if a user logs in, closes their browser and the returns to the site later it will appear as if they&#8217;re still logged in. We&#8217;ve <i>remembered</i> them.
</p>
<p>
That&#8217;s great but we don&#8217;t want a remembered user to have access to certain parts of the site without verifying their password, so there&#8217;s a second cookie that&#8217;s created when they actually log in. I call it the authenticated cookie and it expires when the browser is closed or when the timeout it contains passes. If the user has been remembered and tries to create or modify ads, edit their profile or read/send messages they are asked for their password.
</p>
<p>
This functionality is pretty simple to implement so I won&#8217;t bother posting the code here. The effect is to minimise the effort required from the user to make use of most of the site while still protecting the sensitive areas.
</p>
<h2>Summary</h2>
<p>
Personally I don&#8217;t think there&#8217;s anything ground-breaking here, it&#8217;s simply a case of thinking more carefully about how you use sessions and in particular whether you actually need to store that huge array between requests. In my experience traditional sessions can get very large very quickly which tends to slow everything down, especially when you start using a shared resource to store that data between requests.
</p>
<p>
This implementation (or rather a few iterations beyond it) has been running on <a href="http://uk.freeads.net/">Freeads Classifieds</a> since March 2008 and we&#8217;re yet to have any problems with it. Database utilisation is way down and the user experience is a lot slicker. All things considered I&#8217;m pretty happy with the way it works.
</p>
<p>
As always comments are appreciated. If anyone can see any holes in this or can think of ways to improve it <a href="http://stut.net/who#contact">I&#8217;d love to hear from you</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://stut.net/blog/2008/07/26/sessionless-sessions-2/feed/</wfw:commentRss>
		</item>
		<item>
		<title>MySQL Sessions</title>
		<link>http://stut.net/blog/2008/07/20/mysql-sessions/</link>
		<comments>http://stut.net/blog/2008/07/20/mysql-sessions/#comments</comments>
		<pubDate>Sun, 20 Jul 2008 10:18:28 +0000</pubDate>
		<dc:creator>Stut</dc:creator>
		
		<category><![CDATA[Coding]]></category>

		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://stut.net/blog/?p=225</guid>
		<description><![CDATA[
A share-nothing approach to web development is great for scalability, but there aren&#8217;t many web applications that don&#8217;t need to share anything between requests. The solution PHP (and most other web development lanugages) utilises is sessions. Sessions basically allow you to store some data between requests. That data is tied to an ID that gets [...]]]></description>
			<content:encoded><![CDATA[<p>
A share-nothing approach to web development is great for scalability, but there aren&#8217;t many web applications that don&#8217;t need to share anything between requests. The solution PHP (and most other web development lanugages) utilises is sessions. Sessions basically allow you to store some data between requests. That data is tied to an ID that gets passed between the browser and server in every request, using a cookie, in the URL or in GET/POST parameters.
</p>
<p>
The default data store for PHP sessions is files, and that&#8217;s fine so long as you only have one server, or you can tie each user to one server. When your app scales to the point where each request from a given user could go to one of any number of servers you need to replace this storage mechanism with something accessible from all of them. A database is the obvious choice.
</p>
<p><span id="more-225"></span></p>
<p>
I wrote the code below to solve this problem for a site that get &gt; 1 million unique users per month (at the time of writing). It&#8217;s designed for ease of use and maximum performance. The session table exists in its own database so it can be moved to a dedicated server if required. It would also be trivial to split the session data across several tables by hashing or modifying the session ID to indicate which shard it was on.
</p>
<p>
The code is liberally commented so I won&#8217;t waste electrons describing it separately. Hopefully the way it works is straightforward and easy to understand. Don&#8217;t forget to check out the <a href="http://php.net/session">session documentation on the PHP website</a> for full details about <a href="http://php.net/session-set-save-handler">putting in your own session handler</a>.
</p>
<pre name="code" class="php">
/***********************************************************************
	MySQL Session class

	This class encapsulates everything needed to store your PHP sessions
	in a MySQL database. To use it simply call Session::Start() instead
	of session_start().

	You'll need a table like this in your database. You can change the
	name but the fields should remain as they are defined here.

	CREATE TABLE `sessions` (
	  `id` varchar(50) NOT NULL,
	  `name` varchar(50) NOT NULL,
	  `expires` int(10) unsigned NOT NULL default '0',
	  `data` text,
	  PRIMARY KEY  (`id`, `name`)
	) TYPE=InnoDB;
***********************************************************************/
class Session
{
	private $lifetime = 900;
	private $db = false;
	private $table = 'sessions';
	private $name = 'phpsess';

	static public function Start($host = 'localhost', $username = 'root', $password = '', $db = 'sessionstore', $table = 'sessions', $lifetime = 0)
	{
		// Create the object
		$GLOBALS['_SESSION_OBJ_'] = new self($host, $username, $password, $db, $table, $lifetime);
		// Hook up the handler
		session_set_save_handler(
						array(&#038;$GLOBALS['_SESSION_OBJ_'], 'Open'),
						array(&#038;$GLOBALS['_SESSION_OBJ_'], 'Close'),
						array(&#038;$GLOBALS['_SESSION_OBJ_'], 'Read'),
						array(&#038;$GLOBALS['_SESSION_OBJ_'], 'Write'),
						array(&#038;$GLOBALS['_SESSION_OBJ_'], 'Destroy'),
						array(&#038;$GLOBALS['_SESSION_OBJ_'], 'GC')
					);
		// Start the session
		session_start();
	}

	private function __construct($host = 'localhost', $username = 'root', $password = '', $db = 'sessionstore', $table = 'sessions', $lifetime = 0)
	{
		// By default we use the session lifetime in php.ini, but this can be overridden in code
		$this->lifetime = ($lifetime == 0 ? get_cfg_var('session.gc_maxlifetime') : $lifetime);
		// This is the table where session data is to be stored
		$this->table = $table;
		// Now we connect to the database, throwing expections if anything fails
		$this->db = @mysql_connect($host, $username, $password);
		if ($this->db === false)
			throw new Exception('Failed to connect to the session store', 1);
		if (false === @mysql_select_db($db, $this->db))
			throw new Exception('Failed to select session store', 2);
	}

	public function Open($path, $name)
	{
		// Store the session name for future use, we don't have any use for the path
		$this->name = $name;
		// Everything is OK if we have a connection to the database
		return ($this->db !== false);
	}

	public function Close()
	{
		// Run the garbage collector 10% of the time
		if (rand(1, 10) == 5) $this->GC($this->lifetime);
		// Close the database connection
		return @mysql_close($this->db);
	}

	public function &#038; Read($id)
	{
		// By default we return nothing
		$retval = '';

		// Try to read an entry from the database
		$result = mysql_query('select data from `'.$this->table.'` where id = "'.mysql_real_escape_string($id, $this->db).'" and name = "'.mysql_real_escape_string($this->name, $this->db).'" and expires > '.time().' order by expires desc', $this->db);
		if ($result !== false and mysql_num_rows($result) > 0)
		{
			// Found one, get it
			$result = mysql_result($result, 0, 0);
		}
		@mysql_free_result($result);

		return $result;
	}

	public function Write($id, $data)
	{
		$retval = false;
		// Build the query. We use the MySQL ON DUPLICATE KEY feature to do an insert/update in one query.
		$sql = 'insert into `'.$this->table.'` set ';
		$sql.= 'id = "'.mysql_real_escape_string($id, $this->db).'", ';
		$sql.= 'name = "'.mysql_real_escape_string($this->name, $this->db).'", ';
		$sql.= 'expires = '.(time() + $this->lifetime).', ';
		$sql.= 'data = "'.mysql_real_escape_string($data, $this->db).'" ';
		$sql.= 'on duplicate key update expires = values(expires), data = values(data)';
		// Run it and return true if it was successful
		$result = mysql_query($sql, $this->db);
		if ($result !== false and mysql_affected_rows($this->db) > 0)
			$retval = true;
		@mysql_free_result($result);
		return $retval;
	}

	public function Destroy($id)
	{
		// Remove this session from the database
		$result = mysql_query('delete from `'.$this->table.'` where id = "'.mysql_real_escape_string($id, $this->db).'" and name = "'.mysql_real_escape_string($this->name, $this->db).'"', $this->db);
		if ($result !== false and mysql_affected_rows($this->db) > 0)
			return true;
		return false;
	}

	public function GC($lifetime)
	{
		// Remove any sessions that have expired
		$result = mysql_query('delete from `'.$this->table.'` where expires < '.time(), $this->db);
		return ($result === false ? 0 : mysql_affected_rows($this->db));
	}
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://stut.net/blog/2008/07/20/mysql-sessions/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Yet Another URL Shortener</title>
		<link>http://stut.net/blog/2008/06/21/yet-another-url-shortener/</link>
		<comments>http://stut.net/blog/2008/06/21/yet-another-url-shortener/#comments</comments>
		<pubDate>Sat, 21 Jun 2008 01:59:05 +0000</pubDate>
		<dc:creator>Stut</dc:creator>
		
		<category><![CDATA[Internet]]></category>

		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://stut.net/blog/?p=224</guid>
		<description><![CDATA[I wrote this a while ago for a different domain name but didn&#8217;t register it in time. I found the code a few days ago and have just finished putting the finishing touches on it. Nothing special, just a URL shortener with a base length of just 14 characters and a minimum ID length of [...]]]></description>
			<content:encoded><![CDATA[<p>I wrote this a while ago for a different domain name but didn&#8217;t register it in time. I found the code a few days ago and have just finished putting the finishing touches on it. Nothing special, just a URL shortener with a base length of just 14 characters and a minimum ID length of 1.</p>
<blockquote><p><a href="http://jmp.li/">http://jmp.li/</a></p></blockquote>
<p>For those who care&#8230; it&#8217;s written in PHP, uses MySQL for storage but the destinations are cached at runtime for efficiency.</p>
]]></content:encoded>
			<wfw:commentRss>http://stut.net/blog/2008/06/21/yet-another-url-shortener/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Sending email</title>
		<link>http://stut.net/blog/2008/06/18/sending-email/</link>
		<comments>http://stut.net/blog/2008/06/18/sending-email/#comments</comments>
		<pubDate>Wed, 18 Jun 2008 22:09:39 +0000</pubDate>
		<dc:creator>Stut</dc:creator>
		
		<category><![CDATA[Coding]]></category>

		<category><![CDATA[Internet]]></category>

		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://stut.net/blog/?p=223</guid>
		<description><![CDATA[&#8220;Spam with Cheese&#8221;, &#169;kris247

If you&#8217;re using PHP you want PHPMailer. If you&#8217;re using something else there&#8217;s probably an equivalent library (Google is your friend). However, regardless of how you do it you should always consider the impact of your email activities on your users. Unwanted email is one of the biggest problems on the Internet [...]]]></description>
			<content:encoded><![CDATA[<div style="float:right;margin-left:1em;margin-bottom:0.5em;font-size:small;font-style:italic;text-align:center;line-height:1.1em;"><a href="http://flickr.com/photos/kris247/754341/" style="text-decoration:none;"><img src="http://farm1.static.flickr.com/1/754341_57c393fee5_m.jpg" border="0" style="margin:0;" /><br />&#8220;Spam with Cheese&#8221;, &copy;kris247</a></div>
<p>
If you&#8217;re using PHP you want <a href="http://phpmailer.sf.net/">PHPMailer</a>. If you&#8217;re using something else there&#8217;s probably an equivalent library (<a href="http://www.google.com/">Google is your friend</a>). However, regardless of how you do it you should always consider the impact of your email activities on your users. Unwanted email is one of the biggest problems on the Internet today and the more you can do to present your website as a responsible sender of email the better.
</p>
<p><span id="more-223"></span></p>
<h2>Content</h2>
<p>
There are essentially two types of email that you might send out to the users of your website: operational and non-operational. The purpose and content of the two differ greatly and it&#8217;s important to understand the purpose of each to ensure your users understand why they&#8217;re getting them and what action (if any) they need to take when they do.
</p>
<h3>Operational Emails</h3>
<p>
The term <em>operational emails</em> refers to emails that directly relate to the service you are providing the user. For example this would include welcome messages, order acknowledgements and reminders that you may send to users in response to their actions or other events.
</p>
<p>
Successful operational emails are the ones that keep it simple. You&#8217;re sending them because they provide value for the user, so avoid the temptation to load them up with marketing information.
</p>
<ul>
<li>
<h4>Get straight to the point</h4>
<p>
			You&#8217;re sending this email because you need to tell the user something, so tell them. If you really need to add other stuff like marketing messages be sure to do it low key and after the primary content. The worst thing that can happen with your operational emails is that your users start ignoring them because the signal-to-noise ratio is too low.
		</p>
</li>
<li>
<h4>Avoid HTML</h4>
<p>
			Ask anyone in sales or marketing and they&#8217;ll tell you that to engage people you need flashy graphics and pleasing colours, and at times they&#8217;re right. However, when it comes to operational emails your users are already users - they&#8217;re already engaged. Plain text is your best option. You can get the point across quickly, the message is less likely to be marked as spam and users won&#8217;t get any security warnings when they open it.
		</p>
<p>
			One thing you do need to ensure when using plain text is that your links are short to avoid them wrapping and becoming unclickable.
		</p>
<p style="font-style:italic;">
			Note that not using HTML for these emails is probably more of a personal preference, but in my opinion while HTML email is a necessary evil for sales and marketing messages it has no place in operational emails.
		</p>
</li>
</ul>
<h3>Sales &amp; Marketing Emails</h3>
<p>
Promotional emails can be a very effective way of reminding your users that you&#8217;re there. I&#8217;m sure I have accounts on many websites I never visit and that haven&#8217;t even crossed my mind lately, but it&#8217;s likely if I received an occasional email from them I&#8217;d go visit them. I may just go back to delete my account, but surely that&#8217;s better than account rot. On the other hand it may remind me why I signed up in the first place and I could become an active user again.
</p>
<p>
Emails can also be a great medium through which to inform your users of new features on your site, or long-standing features that aren&#8217;t getting used. And don&#8217;t forget that they also provide the opportunity to push the features of your site that make you money.
</p>
<p>
Evil reasons aside (because we all know sales and marketing people are an evil bunch), regular contact with your users is more likely to engage them with your website. Nothing you do on the site itself can have the same effect, but you have to do it right to prevent getting bitten by consequences.
</p>
<p>
The keys to success with sales and marketing emails differ greatly depending on the subject and audience.
</p>
<ul>
<li>
<h4>Don&#8217;t let the message get lost in the pretty pictures</h4>
<p>
			While less pointed than operational emails promotional emails still carry a message to your users and it&#8217;s important that they get it. If you look at an email you want to send and you can&#8217;t immediately see what it&#8217;s telling you chances are it will be ignored by a fair proportion of recipients. Time is precious and people are rarely willing to give emails longer than a few seconds to assert their importance.
		</p>
</li>
<li>
<h4>Use HTML but make sure it degrades well</h4>
<p>
			You probably realise that by using HTML you can make your email more engaging, but it&#8217;s all too easy to ignore the realities of the security-concious email clients that are out there. Here are some pointers to ensuring maximum compatibility&#8230;
		</p>
<ul>
<li>Test it in as many clients as possible.</li>
<li>Check it with images turned off, with background images turned off, with CSS turned off and combinations of all three.</li>
<li>If you&#8217;re loading images off a remote server remember that by default most email clients will not show them without the user saying it can.</li>
<li>Ensure all images have alt tags - including transparent images where you should set it to a single space.</li>
<li>Make it a complete HTML page including a doctype.</li>
<li>Provide a link at the very top of the email that goes to the email on a web server. Some people will trust that link over clicking the big scary button in their email client that makes it look like your email is trying to take over their computer.</li>
</ul>
</li>
</ul>
<h2>Technicalities</h2>
<p>
More important (from a potential problems point of view) than the content of the emails you send is the process and procedures you use to actually send them. Most of this should sound like common sense but I think it&#8217;s important to state the obvious. The public view of commercial email, whether unsolicited or not is well-known, so unless you want to give your website a bad reputation you need to take care to do it properly.
</p>
<h3>Recipient management</h3>
<p>
Track your recipient list carefully. If someone tells you they don&#8217;t want your email, don&#8217;t send them email. There is no commercial value in sending email to someone who doesn&#8217;t want it, but the potential damage it can cause is extensive.
</p>
<h4>Unsubscribes</h4>
<p>
You&#8217;ll already know that you need to provide unsubscribe instructions in each email you send. For operational emails it may be as simple as &#8220;you have an account and to use it you need to get these emails&#8221;. For promotional emails you need to provide a way for people to unsubscribe from them but continue to receive operational emails.
</p>
<p>
When someone does unsubscribe, honour it. In my opinion it&#8217;s a good idea to keep a separate list of addresses that have unsubscribed as well as noting it against the user on your website. Whenever you send an email you should check to see if the recipient is in that separate unsubscribed list first.
</p>
<h4>Bounces</h4>
<p>
Track these too. Email can bounce for a variety of reasons. It could be that the address really doesn&#8217;t exist, or it could be because the mailbox is full. Either way you need to have a procedure in place to deal with addresses that bounce.
</p>
<p>
Given that not all bounces mean the address is not valid it&#8217;s a good idea to have a bounce threshold. Record bounces and when you get more than 1 (or more than 2 if you want to be more tolerant) for a given address mark them as bounced and stop sending email to it. This will prevent your mail server from having to do more work than it needs to.
</p>
<p>
I&#8217;ve heard several people suggest that it&#8217;s a good idea to periodically flush bounced addresses. The argument centres around the possibility that you might mark perfectly valid addresses as bounced in fairly rare circumstances. The idea is that you unmark bounced addresses after some random period of time, thereby getting back any that were incorrectly marked as bounced. Personally I don&#8217;t think it&#8217;s worth it, but if your bounce rate is low it can&#8217;t hurt. Just be sure not to apply the same logic to unsubscribed addresses!
</p>
<h3>Email structure</h3>
<p>
Well-behaved emails are those that have proper well-formed headers. The following list highlights the really important ones&#8230;
</p>
<ul>
<li>
<h4>Envelope sender</h4>
<p>
			A carefully crafted envelope sender is the best way to identify bounces when they come in. Take the following address&#8230;
		</p>
<p><tt>bounce+listname+messageid+john=doe.com@listserver.com</tt></p>
<p>
			Set up an alias called bounce that passes the email to a script. That script then does the following&#8230;</p>
<ul>
<li>Remove the @ and everything after it.</li>
<li>Split what remains by + with a maximum of 3 elements. Limiting it to 3 elements allows for + signs in the users email address.</li>
<li>The first element is the name of the list they&#8217;re bouncing from.</li>
<li>The second element is the message ID they&#8217;re bouncing from. This part can be removed if you have no need to tie bounces to individual messages.</li>
<li>The last element is the email address in question encoded by replacing the @ with an =. To decode it ensure that you only change the last = to an @ as an = is perfectly valid in the user part.</li>
<li>Take whatever steps are required to mark that email address as bounced from that list.</li>
</ul>
<p>
			Technically you can probably remove the need for the list name and message ID because if an address bounces for one list it&#8217;s no different to it bouncing from another list. The same goes for the message ID. I use this to record events against a given message, but that&#8217;s only for metrics - it serves no functional purpose.
		</p>
</li>
<li>
<h4>Content</h4>
<p>
			If you&#8217;re sending HTML be sure to send a plain text alternative along with it. It doesn&#8217;t need to be the same content, it can be as simple as&#8230;
		</p>
<p><tt>This email is in HTML format which your email client is not displaying. To view this email click on this link.</tt></pre>
<p><tt>http://url.to/web/based/version/of/the/email</tt></p>
<p>
			Keep the URL as short as possible to avoid wrapping. Make sure you also include the unsubscribe instructions in the plain text content.
		</p>
</li>
</ul>
<h2>Sending email with PHP</h2>
<p>
Yeah, I know, most of the rest of this article has had nothing to do with PHP, but here&#8217;s where it comes on-topic. First let&#8217;s get one thing straight&#8230; the built-in <a href="http://php.net/mail">mail</a> function sucks.
</p>
<p>
<a href="http://phpmailer.sf.net/">PHPMailer</a> is a great bit of code that simplifies creating and sending properly formed emails and I always recommend it unless you really understand email. If you know what you&#8217;re doing you can easily do everything yourself.
</p>
<p>
The one piece of advice I&#8217;d give is to make sure the SMTP server you&#8217;re dumping the messages to in the first instance will accept them with minimal delay. As long as you&#8217;ve set up your envelope sender as described above you&#8217;re far better off allowing the MTA to decide to bounce messages in its own time than you are waiting for it to do it as your script is sending them.
</p>
<h2>Other tips</h2>
<p>
Here are a few things to think about when sending bulk email.
</p>
<h3>Don&#8217;t over-complicate your unsubscribe process</h3>
<p>
If someone wants to unsubscribe from your list you need to make it as easy as possible. They don&#8217;t want your email - there&#8217;s no commercial value in keeping them subscribed. Your unsubscribe process should ideally take just one-click and certainly no more than two.
</p>
<p>
Don&#8217;t delay removing them from the list. Nothing grates me more in this area than a list that states it may take a few weeks for an unsubscribe request to be honoured. There&#8217;s never a valid reason why it would take that long.
</p>
<h3>Explain why you have their email address</h3>
<p>
You got their email address from somewhere. Hopefully they gave it to you willingly. In every email you send remind them why you have it. Put that right next to the unsubscribe instructions. It shows them that you&#8217;re not blindly flinging messages out without caring where they&#8217;re going.
</p>
<h3>Don&#8217;t ever use purchased lists</h3>
<p>
If you didn&#8217;t gather the addresses you&#8217;re sending to then you&#8217;re a spammer in my book regardless of the content of your emails. In addition you&#8217;ll find it difficult to justify why you sent them email if anyone complains (see the next point). If you have something to say that people want to hear you shouldn&#8217;t have any trouble gathering the email addresses of willing users.
</p>
<h3>Deal with spam complaints properly</h3>
<p>
If you get people complaining that you&#8217;re spamming them (and you will), make sure you respond to each one personally. If you can trace why their email address was used tell them. Provide details of the unsubscribe procedure and also offer to unsubscribe their address for them if they simply reply to you requesting that. If you&#8217;re doing everything else right these complaints should be quite rare, but I think it&#8217;s worth putting in the small amount of effort it takes to deal with them properly and personally. One of the lists I manage contains over 600,000 addresses and we&#8217;ve never had more than 4 spam complaints from any one message sent.
</p>
<p>
It&#8217;s important to note that complaints of this nature are usually sent through or copied to your hosting provider or ISP. Get too many and they&#8217;ll start to take notice and there may be damaging consequences so deal with them quickly and keep all parties informed.
</p>
<h2>That&#8217;s all folks</h2>
<p>
I hope this article has given you some pointers to help you on your way to being a good citizen in the world of bulk email. Feel free to leave a comment if you have any questions or you think I&#8217;ve missed something.</p>
]]></content:encoded>
			<wfw:commentRss>http://stut.net/blog/2008/06/18/sending-email/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Beginner&#8217;s guide to HDR photography</title>
		<link>http://stut.net/blog/2008/06/17/beginners-guide-to-hdr-photography/</link>
		<comments>http://stut.net/blog/2008/06/17/beginners-guide-to-hdr-photography/#comments</comments>
		<pubDate>Tue, 17 Jun 2008 10:26:52 +0000</pubDate>
		<dc:creator>Stut</dc:creator>
		
		<category><![CDATA[Cool Stuff]]></category>

		<category><![CDATA[Photography]]></category>

		<category><![CDATA[23x]]></category>

		<category><![CDATA[hdr]]></category>

		<category><![CDATA[jared earle]]></category>

		<guid isPermaLink="false">http://stut.net/blog/?p=221</guid>
		<description><![CDATA[My mate Jared has posted a most excellent tutorial on High Dynamic Range (HDR) photography. I haven&#8217;t tried this yet but I definitely will the next time I&#8217;m out with my camera - nice work Jared!

HDR example by Jared Earle, used with permission
]]></description>
			<content:encoded><![CDATA[<p>My mate Jared has posted a <a title="Beginner's guide to HDR" href="http://blog.23x.net/?p=7">most excellent tutorial on High Dynamic Range (HDR) photography</a>. I haven&#8217;t tried this yet but I definitely will the next time I&#8217;m out with my camera - nice work Jared!</p>
<p style="text-align: center;"><span style="color: #0000ee; text-decoration: underline;"><a title="HDR tutorial by Jared Earle" href="http://blog.23x.net/?p=7"><img class="aligncenter size-full wp-image-222" title="HDR example" src="http://stut.net/blog/wp-content/uploads/2008/06/hdr-027-640.jpg" alt="copyright Jared Earle, used with permission" width="480" height="768" /></a></span></p>
<p style="text-align: center;"><em>HDR example by Jared Earle, used with permission</em></p>
]]></content:encoded>
			<wfw:commentRss>http://stut.net/blog/2008/06/17/beginners-guide-to-hdr-photography/feed/</wfw:commentRss>
		</item>
		<item>
		<title>PHP Models</title>
		<link>http://stut.net/blog/2008/06/08/php-models-2/</link>
		<comments>http://stut.net/blog/2008/06/08/php-models-2/#comments</comments>
		<pubDate>Sun, 08 Jun 2008 20:42:27 +0000</pubDate>
		<dc:creator>Stut</dc:creator>
		
		<category><![CDATA[Coding]]></category>

		<category><![CDATA[PHP]]></category>

		<category><![CDATA[activerecord]]></category>

		<guid isPermaLink="false">http://stut.net/blog/?p=220</guid>
		<description><![CDATA[I wanted an object-oriented way of accessing a database that strikes a good balance between abstracting the details of SQL escaping, insert or update, etc and going too far to the point where the benefits are drowned out by the abstraction. In this article I present the system I am currently using. It does most [...]]]></description>
			<content:encoded><![CDATA[<p>I wanted an object-oriented way of accessing a database that strikes a good balance between abstracting the details of SQL escaping, insert or update, etc and going too far to the point where the benefits are drowned out by the abstraction. In this article I present the system I am currently using. It does most of what I wanted but certainly has potential for further improvement.</p>
<p>The main reason this post exists is due to a request from someone on the PHP-General list. This code is not intended to be bug-free or extensively tested or indeed anything. Treat it as you would any other experimental code.<span id="more-220"></span></p>
<h2>Design</h2>
<p>The basic design of this system is based around a base class called <em>Table</em>. Fundamentally the <em>Table</em> class wraps a database table row. When using a system like this it is important to keep in mind what the memory implications are, but more on that later.</p>
<p>When an instance of a subclass of the <em>Table</em> class is created, it gets the table definition from either the database or a definition cache. The cache is file-based and is considerably quicker than getting the definition from the database every time. The definition for any given table is loaded only once per request. The only important implication of this is that if the table definition changes you need to delete the cache file to force it to be loaded from the database again.</p>
<p>One last thing&#8230; the class relies upon the primary key to perform updates, so it&#8217;s vital that any tables you want to use with this system have one.</p>
<p>Ok, nuff talk, let&#8217;s get to the code!</p>
<h2>Implementation</h2>
<p>Probably the easiest thing to do is to go through the two classes piece by piece. Rather than describe the code and force you to match up my ramblings with a separate file of code, I&#8217;ve commented the source liberally. Let&#8217;s start with the <em>Table</em> class.</p>
<pre name="code" class="php">&lt;?php
 // Table: An ActiveRecord-style DB abstraction class
 // Copyright (c) 2005-2006 Stuart Dallas
 // Released into the public domain with absolutely no warranty, explicit, implied or otherwise
 // Use at your own risk!! 

// If you do use this code please drop an email to code@stut.net and let me know why <img src='http://stut.net/blog/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' />
 // Patches, comments, suggestions, questions, etc are welcomed

// The MODEL_CACHE_DIR is where the table definitions get cached. This can be anywhere you like, defaults to cache/
 // in the same location as this file. Obviously the web user (usually nobody or www) needs to have write access to
 // this directory.
 define('MODEL_CACHE_DIR', dirname(__FILE__).'/cache/');
 // Make sure the directory exists, or try to create it if not
 if (!file_exists(MODEL_CACHE_DIR))
 	mkdir(MODEL_CACHE_DIR) or die('Failed to create model cache directory on line '.__LINE__.' of '.__FILE__);

class Table
 {
 	protected $table = '';					// What table does this map to
 	protected $idfield = 'id';				// If there is an ID field, but it's not called id, override this
 	protected $readonly = false;			// Access to this table is read only
 	protected $data = false;				// Internal array of row data
 	protected $dirty = false;				// Is this row dirty?
 	protected $disregarddirty = false;		// Do we care that it's dirty?
 	protected $isnew = true;				// Is it a new row?
 	protected $customfieldtypes = array();	// Overrides of the default field types (for automatic form creation)

protected static $fields = array();		// Per-request cache of the table definitions

// Init is called by the constructors of subclasses
 	// It is responsible for loading the table definition
 	public function Init($classname, $conditions = '', $order = '')
 	{
 		// Have we been called statically or dynamically?
 		// Either way we need an instance to work with
 		if (isset($this))
 			$obj = &amp;$this;
 		else
 			$obj = new $classname();

// The table variable should be set by the subclass, if not use the class name
 		if ($obj-&gt;table == '')
 			$obj-&gt;table = strtolower($classname);

// Only load the field defs if this model has not been used yet in this request
 		if (!isset(self::$fields[$obj-&gt;table]))
 		{
 			// Build the base filename of the cache files for this table
 			$cachefilename = MODEL_CACHE_DIR.$obj-&gt;table;

// Does the field definition cache file exist? If not we need to build it
 			if (!file_exists($cachefilename.'.fields'))
 			{
 				// Initialise the cache (stored statically in the Table class)
 				self::$fields[$obj-&gt;table]['fields'] = array();
 				self::$fields[$obj-&gt;table]['autonumber'] = array();
 				self::$fields[$obj-&gt;table]['primarykey'] = array();

// Get the field definitions
 				$query = mysql_query('show columns from '.$obj-&gt;table);
 				while ($row = mysql_fetch_assoc($query))
 				{
 					// We store auto_number fields separately for use when creating new rows
 					if (strpos($row['Extra'], 'auto_increment') !== false)
 						self::$fields[$obj-&gt;table]['autonumber'][] = $row['Field'];
 					else
 						self::$fields[$obj-&gt;table]['fields'][$row['Field']] = $row;
 				}

// Get the primary key fields
 				$query = mysql_query('show indexes from '.$obj-&gt;table);
 				while ($row = mysql_fetch_assoc($query))
 				{
 					if ($row['Key_name'] == 'PRIMARY')
 						self::$fields[$obj-&gt;table]['primarykey'][] = $row['Column_name'];
 				}

// Now save the definition in files in the model cache directory
 				file_put_contents($cachefilename.'.fields', serialize(self::$fields[$obj-&gt;table]['fields']));
 				file_put_contents($cachefilename.'.autonumber', serialize(self::$fields[$obj-&gt;table]['autonumber']));
 				file_put_contents($cachefilename.'.primarykey', serialize(self::$fields[$obj-&gt;table]['primarykey']));
 			}
 			else
 			{
 				// The cache files exist, load them
 				self::$fields[$obj-&gt;table]['fields'] = unserialize(file_get_contents($cachefilename.'.fields'));
 				self::$fields[$obj-&gt;table]['autonumber'] = unserialize(file_get_contents($cachefilename.'.autonumber'));
 				self::$fields[$obj-&gt;table]['primarykey'] = unserialize(file_get_contents($cachefilename.'.primarykey'));
 			}
 		}

// Did we get given conditions?
 		if (strlen($conditions) &gt; 0)
 		{
 			// If it's numeric, use it as an ID and build a where clause. This relies upon the idfield member variable that
 			// should be overridden in subclasses if the ID field is not named 'id'
 			if (is_numeric($conditions))
 			{
 				$conditions = $this-&gt;idfield.' = '.Table::Escape($conditions);
 			}

// Try to find a row matching the conditions
 			if ($obj-&gt;FindFirst($conditions, $order) === false)
 			{
 				// Couldn't find one, reset this object so it's an unsaved new row
 				$obj-&gt;Clear();
 			}
 		}
 		else
 		{
 			// No conditions, it's an unsaved new row
 			if($obj-&gt;data === false) $obj-&gt;Clear();
 		}
 	}

// The destructor does nothing more than a sanity check to see if this row is dirty (i.e. contains unsaved data), in which
 	// case it raises an error. The disregarddirty flag can be used to disable this where destroying a dirty object is expected
 	public function __destruct()
 	{
 		if ($this-&gt;dirty &amp;&amp; !$this-&gt;disregarddirty)
 			trigger_error(get_class($this).'.__destruct: Dirty object being released');
 	}

// Ahh, magic functions!!
 	// This one allows you to use OO syntax to access fields, e.g. $obj-&gt;id will get you the id field
 	public function __get($field)
 	{
 		return (isset($this-&gt;data[$field]) ? $this-&gt;data[$field] : '');
 	}

// The other half of __get is __set. This method is called if you do something like $obj-&gt;id = 100
 	public function __set($field, $value)
 	{
 		// If we're read only then changing the data is not allowed
 		if ($this-&gt;readonly)
 		{
 			trigger_error(get_class($this).'.__set: Attempt to modify a readonly object');
 		}
 		// If the row is not new then the primary key is read only
 		elseif (!$this-&gt;isnew and in_array($field, self::$fields[$this-&gt;table]['primarykey']))
 		{
 			trigger_error(get_class($this).'.__set: Attempt to set primary key field "'.$field.'"');
 		}
 		// Now we check to make sure the attribute being set actually exists (i.e. is in the table fields or is already in the
 		// data array)
 		elseif (isset(self::$fields[$this-&gt;table]['fields'][$field]) or isset($this-&gt;data[$field]))
 		{
 			if (!isset($this-&gt;data[$field]) or $this-&gt;data[$field] != $value)
 				$this-&gt;dirty = true;
 			$this-&gt;data[$field] = $value;
 		}
 		// If we get here then we don't know anything about the field being set - that's an error that is!
 		else
 		{
 			trigger_error(get_class($this).'.__set: Unknown field "'.$field.'"');
 		}
 	}

// LoadFromArray does what it says on the tin. It fills in the data for this record from an array
 	private function LoadFromArray($arr)
 	{
 		$this-&gt;data = array();

// Only allow fields we know about, ignore anything else
 		foreach (array_keys(self::$fields[$this-&gt;table]['fields']) as $field)
 			if (isset($arr[$field]))
 				$this-&gt;data[$field] = $arr[$field];
 		foreach (self::$fields[$this-&gt;table]['autonumber'] as $field)
 			if (isset($arr[$field]))
 				$this-&gt;data[$field] = $arr[$field];

// At first glance these might seem wrong, but this method is used when reading &gt; 1 row (see the FindAll method) and since it's
 		// a private method we know the data source will always be the database, so it's not new and it's not dirty
 		$this-&gt;dirty = false;
 		$this-&gt;isnew = false;

// Call table-specific translation
 		$this-&gt;AfterLoad();
 	}

// Call this function to disable the error generated when a dirty object is destructed
 	public function DisregardDirty()
 	{
 		$this-&gt;disregarddirty = true;
 	}

// Clear the object of data.
 	protected function Clear()
 	{
 		$this-&gt;data = array();
 	}

// Stubs for table-specific stuff
 	protected function AfterLoad() { }
 	protected function BeforeSave() { }
 	protected function AfterDelete($result) { }
 	protected function BeforeDelete() { }

// GetFieldType first looks in the customfieldtypes array to see if the subclass has overridden it before returning the
 	// field type from the table definition. If we don't know anything about the field, assume it's a string
 	public function GetFieldType($field)
 	{
 		if (isset($this-&gt;customfieldtypes[$field]))
 			return $this-&gt;customfieldtypes[$field];
 		if (isset(self::$fields[$this-&gt;table]['fields'][$field]['Type']))
 			return self::$fields[$this-&gt;table]['fields'][$field]['Type'];
 		return 'string';
 	}

// GetValueFromDB will re-fetch a single value from the DB - useful for flags, locks, etc
 	public function GetValueFromDB($field)
 	{
 		$retval = $this-&gt;GetValuesFromDB(array($field));
 		return $retval[$field];
 	}

// GetValuesFromDB will get a given set of fields from this row in the table
 	// Note that it does not update the internal data
 	public function GetValuesFromDB($fieldlist = array())
 	{
 		$retval = false;
 		if (!$this-&gt;isnew and count($fieldlist) &gt; 0)
 		{
 			$sql = 'select '.implode(',', $fieldlist).' from '.$this-&gt;table.' where '.$this-&gt;PrimaryKeyWhere();
 			$query = mysql_query($sql);
 			if ($query !== false and mysql_num_rows($query) == 1)
 			{
 				$row = mysql_fetch_assoc($query);
 				$retval = array();
 				foreach ($row as $key =&gt; $val)
 					$retval[$key] = $val;
 			}
 		}
 		return $retval;
 	}

// GetFieldList will produce an array containing the table definition
 	public function GetFieldList($pkey = true, $normal = true)
 	{
 		$retval = array();
 		if ($pkey) $retval['primarykey'] = self::$fields[$this-&gt;table]['primarykey'];
 		if ($normal) $retval['normal'] = self::$fields[$this-&gt;table]['fields'];
 		return $retval;
 	}

// Reload will update this object from the table
 	// Reloading a new row (duh!!) or reloading a dirty object will raise errors
 	public function Reload($ignoredirty = false)
 	{
 		if ($this-&gt;isnew)
 		{
 			trigger_error(get_class($this).'.Reload: Attempted to reload a new object');
 		}
 		else
 		{
 			if (!$ignoredirty and !$this-&gt;disregarddirty and $this-&gt;dirty)
 				trigger_error(get_class($this).'.Reload: Reloading a dirty object - changes lost');
 			// Reload from DB
 			$this-&gt;FindFirst($this-&gt;PrimaryKeyWhere());
 		}
 	}

// CreateInsertSQL is a helper function used to build insert statements
 	private function CreateInsertSQL($setfields)
 	{
 		return 'insert into '.$this-&gt;table.' set '.implode(', ', $setfields);
 	}

// Save this row
 	public function Save()
 	{
 		$retval = false;

// Can't save a read only row
 		if ($this-&gt;readonly)
 		{
 			trigger_error(get_class($this).'.Save: Attempt to save a readonly object');
 		}
 		// Are we dirty?
 		elseif ($this-&gt;dirty)
 		{
 			// Call table-specific translation
 			$this-&gt;BeforeSave();

// Get the fields
 			$setfields = array();
 			foreach (array_keys(self::$fields[$this-&gt;table]['fields']) as $field)
 			{
 				if (isset($this-&gt;data[$field]))
 					$setfields[$field] = $field.' = '.self::Escape($this-&gt;data[$field]);
 			}

// Is this a new row?
 			if ($this-&gt;isnew)
 			{
 				// Insert a new row
 				$sql = $this-&gt;CreateInsertSQL($setfields);
 			}
 			else
 			{
 				// Update existing row
 				$sql = 'update '.$this-&gt;table.' set '.implode(', ', $setfields).' where '.$this-&gt;PrimaryKeyWhere();
 			}

$result = mysql_query($sql);
 			if ($result === false)
 			{
 				// Something bad happened!
 				trigger_error(get_class($this).'.Save: Failed to save object - '.mysql_error());
 				$retval = false;
 			}
 			else
 			{
 				// Saved successfully, we're now clean
 				$this-&gt;dirty = false;
 				$retval = true;

// If this was a new row we need to grab the auto_number'd id
 				if ($this-&gt;isnew)
 				{
 					$this-&gt;data[$this-&gt;idfield] = mysql_insert_id();
 					// We're no longer a new row
 					$this-&gt;isnew = false;
 				}
 			}

// Call table-specific stuff
 			$this-&gt;AfterLoad();
 		}
 		else
 		{
 			// Not dirty, call it a success!
 			$retval = true;
 		}

return $retval;
 	}

// Delete will delete this row from the table
 	public function Delete()
 	{
 		$retval = false;
 		// Can't delete a new row, it doesn't actually exist yet!
 		if ($this-&gt;isnew)
 		{
 			trigger_error(get_class($this).'.Delete: Attempted to delete a new object');
 		}
 		else
 		{
 			// Call table-specific stuff
 			$this-&gt;BeforeDelete();
 			// Do the delete
 			$sql = 'delete from '.$this-&gt;table.' where '.$this-&gt;PrimaryKeyWhere();
 			$retval = mysql_query($sql);
 			// Call table-specific stuff
 			$this-&gt;AfterDelete($retval);
 		}
 		return $retval;
 	}

// PrimaryKeyWhere builds a where clause from the fields in the primary key
 	// This effectively produces a where clause that will retrieve the row this object is representing
 	public function PrimaryKeyWhere()
 	{
 		$wherefields = array();
 		foreach (self::$fields[$this-&gt;table]['primarykey'] as $field)
 			$wherefields[] = $field.' = '.self::Escape($this-&gt;data[$field]);
 		return '('.implode(' and ', $wherefields).')';
 	}

// FindAll takes a set of conditions in the form of a where clause, a limit clause and an order specification,
 	// builds a query from them and executes it. The rows returned are used to create an array of objects which is
 	// then returned
 	// Note that this method can only be called on subclasses - it cannot be called directly on the Table class
 	public function FindAll($conditions = '', $limit = '', $order = '')
 	{
 		$sql = 'select * from '.$this-&gt;table;
 		if (strlen($conditions) &gt; 0)
 			$sql .= ' where '.$conditions;

if (strlen($order) &gt; 0)
 			$sql .= ' order by '.$order;

if (strlen($limit) &gt; 0)
 			$sql .= ' limit '.$limit;

$query = mysql_query($sql);

// Errors will currently cause an error to be raised. This may not be ideal for your application, you may need
 		// to change how these are handled
 		if (!$query)
 			trigger_error('MySQL error in '.get_class($this).'::FindAll: '.mysql_error());

// If the query got no rows return an empty array
 		if (mysql_num_rows($query) == 0)
 			return array();

$classname = get_class($this);
 		$retval = array();
 		while ($row = mysql_fetch_assoc($query))
 		{
 			$obj = new $classname();
 			$obj-&gt;LoadFromArray($row);
 			$retval[] = $obj;
 		}
 		return $retval;
 	}

// FindFirst does the same as FindAll but only gets a single row and returns a single object
 	public function FindFirst($conditions = '', $order = '')
 	{
 		$sql = 'select * from '.$this-&gt;table;
 		if (strlen($conditions) &gt; 0)
 			$sql .= ' where '.$conditions;

if (strlen($order) &gt; 0)
 			$sql .= ' order by '.$order;

$sql .= ' limit 1';

$query = mysql_query($sql);

// Errors will currently cause an error to be raised. This may not be ideal for your application, you may need
 		// to change how these are handled
 		if (!$query)
 			trigger_error('MySQL error in '.get_class($this).'::FindFirst: '.mysql_error());

// If the query got no rows return false rather than a new empty object
 		if (mysql_num_rows($query) == 0)
 			return false;

$row = mysql_fetch_assoc($query);
 		$this-&gt;LoadFromArray($row);

return $this;
 	}

// Paged is similar to FindAll but it will get a certain range of rows given a page number and the number of rows
 	// on each page. Returns an array where [0] is an array of rows, [1] is the current page number (in case it was
 	// adjusted) and [2] is the total number of pages available
 	public function Paged($page = 1, $perpage = 10, $conditions = '', $order = '')
 	{
 		// Get the total count
 		$sql = 'select count(1) from '.$this-&gt;table;
 		if (strlen($conditions) &gt; 0)
 			$sql .= ' where '.$conditions;
 		$query = mysql_query($sql);

// Query failed, raise an error
 		if (!$query)
 			trigger_error('MySQL error in '.get_class($this).'::Paged: '.mysql_error());

// Because it's a count query this should never happen, but handle nicely just in case
 		if (mysql_num_rows($query) == 0)
 			return array(array(), 1, 1);

$row = mysql_fetch_array($query);
 		$rowcount = $row[0];
 		// Return if there are no matching rows
 		if ($rowcount == 0)
 			return array(array(), 1, 1);

// Make sure the requested page number is in range
 		$totalpages = ceil($rowcount / $perpage);
 		if ($page &gt; $totalpages) $page = $totalpages;
 		if ($page &lt; 1) $page = 1;

// Build the query
 		$sql = 'select * from '.$this-&gt;table;
 		if (strlen($conditions) &gt; 0)
 			$sql .= ' where '.$conditions;

if (strlen($order) &gt; 0)
 			$sql .= ' order by '.$order;

$sql .= ' limit '.(($page-1) * $perpage).','.$perpage;

$query = mysql_query($sql);

// Query failed, raise an error
 		if (!$query)
 			trigger_error('MySQL error in '.get_class($this).'::Paged: '.mysql_error());

// No rows returned, this shouldn't be possible but handle nicely just in case
 		if (mysql_num_rows($query) == 0)
 			return array(array(), 1, 1);

// Create the object array
 		$classname = get_class($this);
 		$retval = array();
 		while ($row = mysql_fetch_assoc($query))
 		{
 			$obj = new $classname();
 			$obj-&gt;LoadFromArray($row);
 			$retval[] = $obj;
 		}

// Return the rows, the page number and the number of pages
 		return array($retval, $page, $totalpages);
 	}

// Count returns the number of rows that match a condition
 	public function Count($conditions)
 	{
 		$sql = 'select count(*) as count from '.$this-&gt;table;
 		if (strlen($conditions) &gt; 0)
 			$sql .= ' where '.$conditions;

$query = mysql_query($sql);
 		if (!$query or mysql_num_rows($query) == 0)
 			return false;

$row = mysql_fetch_assoc($query);
 		return $row['count'];
 	}

// Is this row new?
 	public function IsNew()
 	{
 		return $this-&gt;isnew;
 	}

//////////////////////////////
 	// Static utility functions //
 	//////////////////////////////

// Escape should be used to escape all values used in conditions
 	static public function Escape($var)
 	{
 		return '"'.mysql_real_escape_string($var).'"';
 	}

// MakeLike returns a like query for a given var and val
 	static public function MakeLike($var, $val)
 	{
 		return '(`'.$var.'` like "%'.mysql_real_escape_string($val).'%")';
 	}

// MakeLikes takes an array of vars and a single val and returns a set of likes combined by op
 	static public function MakeLikes($vars, $val, $op = 'or')
 	{
 		$likes = array();
 		foreach ($vars as $var)
 			$likes[] = self::MakeLike($var, $val);
 		return '('.implode(' '.$op.' ', $likes).')';
 	}

// Lock and Unlock wrap the table locking system
 	static protected function Lock($tables = false)
 	{
 		if ($tables === false)
 			trigger_error("Table::Lock called without a table to lock");

if (!is_array($tables))
 			$tables = array($tables);
 		return mysql_query('lock tables `'.implode('`,`', $tables).'`');
 	}
 	static protected function Unlock()
 	{
 		return mysql_query('unlock tables');
 	}

// GetSingleValue will return the first value of the first row returned by the provided SQL
 	// Caller should make sure it's only getting one field and one row
 	static public function &amp; GetSingleValue($sql)
 	{
 		$query = mysql_query($sql);
 		if ($query === false)
 		{
 			trigger_error('Query failed: '.mysql_error(), E_USER_ERROR);
 			exit;
 		}
 		if (mysql_num_rows($query) == 0)
 			return false;
 		$retval = mysql_fetch_array($query);
 		mysql_free_result($query);
 		return $retval[0];
 	}

// GetSingle will return an associative array containing the first row returned by the provided SQL
 	// Caller should make sure it's only getting one row
 	static public function &amp; GetSingle($sql)
 	{
 		$query = mysql_query($sql);
 		if ($query === false)
 		{
 			trigger_error('Query failed: '.mysql_error(), E_USER_ERROR);
 			exit;
 		}
 		if (mysql_num_rows($query) == 0)
 			return array();
 		$retval = mysql_fetch_assoc($query);
 		mysql_free_result($query);
 		return $retval;
 	}

// GetMultiple will return an array of associative arrays containing every row returned by the provided SQL
 	// Be careful not to get too many rows with this method - it loads them all into memory!!
 	static public function &amp; GetMultiple($sql)
 	{
 		$query = mysql_query($sql);
 		if ($query === false)
 		{
 			trigger_error('Query failed: '.mysql_error(), E_USER_ERROR);
 			exit;
 		}
 		$retval = array();
 		if (mysql_num_rows($query) &gt; 0)
 		{
 			while ($row = mysql_fetch_assoc($query))
 			$retval[] = $row;
 		}
 		mysql_free_result($query);
 		return $retval;
 	}

// GetSingleColumn will return an array containing the first field of each row returned by the provided SQL
 	// Be careful not to get too many rows with this method - it loads them all into memory
 	static public function &amp; GetSingleColumn($sql)
 	{
 		$query = mysql_query($sql);
 		if ($query === false)
 		{
 			trigger_error('Query failed: '.mysql_error(), E_USER_ERROR);
 			exit;
 		}
 		$retval = array();
 		if (mysql_num_rows($query) &gt; 0)
 		{
 			while ($row = mysql_fetch_array($query))
 			$retval[] = $row[0];
 		}
 		mysql_free_result($query);
 		return $retval;
 	}

// Modify is intended to execute a SQL statement that will make a change (insert, update, alter, etc)
 	static public function Modify($sql)
 	{
 		$query = mysql_query($sql);
 		if ($query === false)
 			return false;
 		return true;
 	}

// ModifyWithAutonumber is the same as Modify but returns the autonumber ID
 	static public function ModifyWithAutonumber($sql)
 	{
 		$query = mysql_query($sql);
 		if ($query === false)
 			return false;
 		return mysql_insert_id();
 	}
 }</pre>
<p>And that concludes the <em>Table</em> class, I hope you enjoyed the ride. Seriously though, it&#8217;s not too complicated and it really does make working with small numbers of rows a lot easier.</p>
<h4>A quick mention of resource usage</h4>
<p>Before we get on to the example subclass I just wanted to mention the resource implications of this class. Clearly this method of accessing a database uses more memory and be a bit more CPU-intensive than simply using the MySQL functions where they are needed. However, for me at least, the benefits far outweigh the costs. And from what I&#8217;ve seen the costs are fairly minimal anyway.</p>
<p>The key thing is to be a bit careful about what the code you&#8217;re writing will actually do. Is it going to retrieve 10,000 rows meaning it will instantiate 10,000 objects? If so then you&#8217;re better off using another method. If, on the other hand, you&#8217;re retrieving a single row that will be stored in the session and may get changed occasionally during its lifetime, this is absolutely worth the minimal cost in efficiency. I&#8217;ll go into an example of this type of usage for the <em>Account</em> class in the next section, and I&#8217;ll explain how it helps with that type of situation.</p>
<p>Something I have tried to avoid is abstracting the database too much. This class is not meant to make it easy to switch between database systems. While it would be relatively trivial to convert it to use MSSQL or PostgreSQL, some work would be needed anywhere a limit clause has been used, or a MySQL-specific feature has been used in some conditions. But it&#8217;s ok since that was not my aim. There are plenty of other database abstraction projects out there, and from my experience each one sucks just as much as the others. But I digress.</p>
<p>I was trying to explain, via a lengthy detour, why you won&#8217;t find Open, Next and Close methods in this class. It&#8217;s not what I was trying to do. If I have a situation where those methods would be needed I&#8217;d prefer to use mysql_(p)connect, mysql_fetch_assoc and mysql_close rather than waste time trying to replace something perfectly adequate for the rare times I&#8217;d need them.</p>
<p>Right, mini-rant over. On to the account class.</p>
<pre name="code" class="php">&lt;?php
 // Model: Account 

// Pull in the table definition
 require_once('table.class.php');

class Account extends Table
 {
 	// The table we're mapping to is called accounts
 	protected $table = 'accounts';
 	// We have a number of fields we want to treat differently
 	// Note that these definitions don't affect how the data is treated, it just changes what is reported
 	// by the GetFieldType method in the Table class - this was added to allow automatic generation of
 	// forms
 	// An array indicates something akin to an enumeration where the actual value used can be defined
 	// differently (look at ACM in AccountType)
 	protected $customfieldtypes = array('Status' =&gt; array('Created', 'Active', 'Expired', 'Deleted'),
 										'Created' =&gt; 'date',
 										'Expires' =&gt; 'date',
 										'AccountType' =&gt; array('Full', 'Demo', 'ACM' =&gt; 'acm'),
 										);

// AfterLoad is called whenever the Table class completes loading of the data
 	// It can be used to...
 	protected function AfterLoad()
 	{
 		// ...enforce consistency...
 		if (isset($this-&gt;data['AccountType']))
 		{
 			// If ACM, the accounttype should be lowercase
 			if (strtolower($this-&gt;data['AccountType']) == 'acm')
 				$this-&gt;data['AccountType'] = 'acm';
 		}

// ...present data to consumers in a different format to that which is stored in the table...
 		if (isset($this-&gt;data['Created']) and $this-&gt;data['Created'] &gt; 0)
 			$this-&gt;data['Created'] = date('Y-m-d', $this-&gt;data['Created']);

if (isset($this-&gt;data['Expires']) and $this-&gt;data['Expires'] &gt; 0)
 			$this-&gt;data['Expires'] = date('Y-m-d', $this-&gt;data['Expires']);

// ...including complex types...
 		if (isset($this-&gt;data['OtherInfo']))
 			$this-&gt;data['OtherInfo'] = unserialize($this-&gt;data['OtherInfo']);

// ...and it can be used to create pseudo fields that do not exist in the table but may exist in other tables
 		$this-&gt;data['projects'] = array();
 	}

// BeforeSave is called by the Table class right before it saves the data back to the table
 	// It has the reverse purpose of AfterLoad, so you can...
 	protected function BeforeSave()
 	{
 		// ...store complex types...
 		if (isset($this-&gt;data['OtherInfo']) and is_array($this-&gt;data['OtherInfo']))
 			$this-&gt;data['OtherInfo'] = serialize($this-&gt;data['OtherInfo']);

// ...convert data to a format suitable for storage in a table...
 		if (isset($this-&gt;data['Expires']) and strlen($this-&gt;data['Expires']) &gt; 0 and $this-&gt;data['Expires'] != 0)
 			$this-&gt;data['Expires'] = strtotime($this-&gt;data['Expires']);

// ...and forcing default values in new rows
 		if ($this-&gt;isnew)
 		{
 			$this-&gt;data['Created'] = time();
 			if (!isset($this-&gt;data['Status'])) $this-&gt;data['Status'] = 'Created';
 		}
 		else
 		{
 			if (isset($this-&gt;data['Created']) and strlen($this-&gt;data['Created']) &gt; 0 and $this-&gt;data['Created'] != 0)
 				$this-&gt;data['Created'] = strtotime($this-&gt;data['Created']);
 		}

// Note the absense of any reference to the projects variable
 		// This is fine since the Save method of the Table class uses its own internal list of fields to decide which parts of
 		// the data to save to the table
 	}

// In addition to overriding methods in the Table class, we can create methods that are specific to this particular table
 	// For example, this method will return true only if the AccountType is set to acm (Account Manager)
 	public function IsACM()
 	{
 		return (isset($this-&gt;data['AccountType']) and $this-&gt;data['AccountType'] == 'acm');
 	}

// Methods can also return related rows
 	// For example, GetACM will return an Account object representing this objects Account Manager
 	public function &amp; GetACM()
 	{
 		$retval = false;
 		if ($this-&gt;data['ACM'] &gt; 0)
 		{
 			$retval = new Account('id = '.$this-&gt;data['ACM']);
 		}
 		return $retval;
 	}

// In true OO tradition, any method in this class should be related to an Account
 	// For example, IsExpired will return true if the account has expired...
 	public function IsExpired()
 	{
 		return (isset($this-&gt;data['Expires']) and $this-&gt;data['Expires'] != 0 and strtotime($this-&gt;data['Expires']) &lt; time());
 	}

// ...MarkDeleted will set the status of the Account and save it back to the database...
 	public function MarkDeleted()
 	{
 		$this-&gt;Status = 'Deleted';
 		// Note that calling Save will cause BeforeSave to be called, the data will then be saved and finally AfterLoad will be called
 		// This means that the member variable named data will be reset, along with any custom fields put in by AfterLoad
 		return $this-&gt;Save();
 	}

// ...performing actions like resetting the account password...
 	public function ResetPassword($password = '')
 	{
 		// If no password was given, generate a random one
 		$pwd = $password;
 		if (strlen($pwd) == 0)
 			$pwd = generatePassword(); // Function source omitted since it's irrelevant

// Save it
 		$this-&gt;Password = $pwd;
 		if (!$this-&gt;Save())
 			return false;

if ($password == '')
 		{
 			// Password was random, email it to the address held in the account
 			if (strlen($this-&gt;data['EmailAddress']) &gt; 0)
 				mail($this-&gt;data['EmailAddress'], 'Password Updated', 'Your new password is: '.$pwd, "From: Support &lt;support@example.com&gt;", '-fsupport@example.com');
 		}

return true;
 	}

// ...getting or counting other records...
 	public function ACM_GetAccounts($countonly = false)
 	{
 		if ($countonly)
 		{
 			return $this-&gt;Count('ACM = '.self::Escape($this-&gt;data['id']));
 		}
 		else
 		{
 			return $this-&gt;FindAll('ACM = '.self::Escape($this-&gt;data['id']), '', 'name asc');
 		}
 	}

// ...ensuring that deletions get propogated to dependent data...
 	public function Destroy()
 	{
 		$accounts = $this-&gt;ACM_GetAccounts();
 		foreach ($accounts as $account)
 		{
 			$result = $account-&gt;Destroy();
 			if ($result !== true)
 				return $result;
 		}

// Call destroy on all projects first - Project is another class derived from Table
 		$tmp = new Project();
 		$projects = $tmp-&gt;FindAll('accountid = '.self::Escape($this-&gt;data['id']));
 		foreach ($projects as $project)
 		{
 			$result = $project-&gt;Destroy();
 			if ($result !== true)
 				return $result;
 		}
 		// Then delete this account
 		$result = $this-&gt;Delete();
 		if ($result !== true)
 			return 'Failed to delete account '.$this-&gt;data['id'];
 		return true;
 	}

// ...other functions omitted for clarity

// You can also define static methods
 	// Account::Current() will get you an account object from the session (see the next method, Login)
 	static public function &amp; Current()
 	{
 		$retval = false;
 		if (isset($_SESSION['account']))
 			$retval = $_SESSION['account'];
 		return $retval;
 	}

// The Login method takes an email address and a password and tries to log the user in
 	// If login is successful it stores the Account object in the session so it can be retrieved by the Current method
 	static public function Login($email, $password)
 	{
 		$account = new Account();
 		if ($account-&gt;FindFirst('EmailAddress = '.Table::Escape($email).' and Password = '.Table::Escape($password)) !== false)
 		{
 			$_SESSION['account'] = &amp;$account;
 			return true;
 		}
 		return false;
 	}

// Static functions can also be used to return multiple rows
 	// This one will return an array of objects containing the Account Manager records
 	static public function &amp; GetACMs()
 	{
 		$account = new Account();
 		return $account-&gt;FindAll('AccountType = '.Table::Escape('acm'), '', 'name asc');
 	}

// The rest is exactly the same for every class that derives from Table - absolutely nothing needs changing from class to class,
 	// but it's vital that these exist - see the next bit of the article for an explanation

// The constructor takes conditions and order and passes them, along with the class name, to the Init method
 	public function __construct($conditions = '', $order = '') { $this-&gt;Init(__CLASS__, $conditions, $order); }
 	// The magic __wakeup method is called when an object is read from serialised data (e.g. the session). It also calls the Init
 	// method but just with the class name
 	public function __wakeup() { $this-&gt;Init(__CLASS__); }
 }</pre>
<p>Ok, so there are a couple of oddities in there that need further explanation, and they both stem from the same problem. The PHP 5 implementation of objects means that inherited methods have an identity crisis. Say you have class A and class B, which extends A. In class A you have a method defined called WhoAmI which returns the name of the class. One possible implementation would use __CLASS__, another possibility would be the get_class function. You may also go as far as to pass $this into get_class. Let&#8217;s try an example&#8230;</p>
<pre name="code" class="php"> class A
 {
 	public function WhoAmI_1()
 	{
 		return __CLASS__;
 	} 

public function WhoAmI_2()
 	{
 		return get_class();
 	}

public function WhoAmI_3()
 	{
 		return get_class(\$this);
 	}
 }

class B extends A
 {
 }

print A::WhoAmI_1();
 print B::WhoAmI_1();

print A::WhoAmI_2();
 print B::WhoAmI_2();</pre>
<p>Now, any sane and reasonable person with a basic knowledge of OOP would expect this to print &#8220;ABAB&#8221;. Yeah, I wish!! What you actually get is &#8220;AAAA&#8221;. Grrrrrrr!!</p>
<p>Now there are good reasons for why PHP does this, and from what I understand fixing it is a big job. I also understand that it&#8217;s been done in PHP 6 but is unlikely to make it into the 5.x branch, which is a shame.</p>
<p>So how do we get around that. Well, you probably noticed the WhoAmI_3 method in the above example that wasn&#8217;t used. Obviously you can&#8217;t use that method statically since it uses <strong>$this</strong>, so let&#8217;s add a few lines to it so we can try it out.</p>
<pre name="code" class="php">$a = new A();
$b = new B();
print $a-&gt;WhoAmI_3();
print $b-&gt;WhoAmI_3();</pre>
<p>Perhaps unsurprisingly this gives us what we need: &#8220;AAAAAB&#8221;. But hang on a second, we had to create an instance of the object to do that. Not good. But unfortunately it&#8217;s the only way around it that I can find.</p>
<p>Hmm, you may be wondering what the heck I&#8217;m going on about. Look back at the source for account.class.php, around line 206 is a good example. This is the GetACMs method which is expected to return an array of Account objects representing the Account Manager rows. Notice how it creates a temporary instance if the Account class, calls FindAll on it and throws it away. Hopefully that&#8217;s clear now.</p>
<p>While this is a major <span class="definition" title="Pain in the Arse">PitA</span> it&#8217;s not the end of the world. And despite several people telling me it&#8217;s a waste of resources and makes any other gains worthless, I still firmly believe that it&#8217;s a very small price to pay for the convenience this system provides. Also, the only workaround I&#8217;ve found that avoids having to create a temporary object is to repeat the code for FindAll in every derived class, and that&#8217;s something I&#8217;m not prepared to do. I&#8217;d rather lose a few microseconds of time and a few bytes of memory than have to do that. It&#8217;s bad enough having to remember to put the __construct and __wakeup lines into each one. Which leads me nicely on to those.</p>
<p>For the same reason that the temporary object is required, the __construct and __wakeup methods are required to be duplicated in each derived class. Daft though it may seem, but if you remove the __construct method from the Account class, the code <strong>new Account($id)</strong> would actually try to create a Table object which would have no way of knowing what table to use. Another <span class="definition" title="Pain in the Arse">PitA</span>, but not so bad as long as you remember to copy those two lines into every class that derives from <em>Table</em>.</p>
<p>Hopefully, with the arrival of PHP 6 I should be able to modify these classes to work the way I had hoped they would.</p>
]]></content:encoded>
			<wfw:commentRss>http://stut.net/blog/2008/06/08/php-models-2/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Pop-unders</title>
		<link>http://stut.net/blog/2008/06/08/pop-unders/</link>
		<comments>http://stut.net/blog/2008/06/08/pop-unders/#comments</comments>
		<pubDate>Sun, 08 Jun 2008 20:13:20 +0000</pubDate>
		<dc:creator>Stut</dc:creator>
		
		<category><![CDATA[Coding]]></category>

		<category><![CDATA[javascript]]></category>

		<guid isPermaLink="false">http://stut.net/blog/?p=219</guid>
		<description><![CDATA[Let me start by saying I hate pop-unders. They clutter up peoples desktops and generally annoy users. Unfortunately they are one of the most effective forms of web-based advertising at the moment, although I&#8217;m not sure why. I&#8217;ve had a few questions over recent months about how to implement pop-unders so here it is.
Pop-ups are [...]]]></description>
			<content:encoded><![CDATA[<p>Let me start by saying I hate pop-unders. They clutter up peoples desktops and generally annoy users. Unfortunately they are one of the most effective forms of web-based advertising at the moment, although I&#8217;m not sure why. I&#8217;ve had a few questions over recent months about how to implement pop-unders so here it is.</p>
<p>Pop-ups are easy, you just call the javascript function window.open with the required parameters. To turn a pop-up into a pop-under is simple. On the page that&#8217;s loaded in the pop-up, simply add the following snippet&#8230;</p>
<pre name="code" class="php">
&lt;script type="text/javascript"&gt;
	self.blur();
&lt;/script&gt;
</pre>
<p>That will cause the popped-up window to sink behind whatever opened it.</p>
<p>Note that I accept no responsibility for the complaints you&#8217;ll get from users if you start doing this. You&#8217;re on your own!</p>
]]></content:encoded>
			<wfw:commentRss>http://stut.net/blog/2008/06/08/pop-unders/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Where are these backslashes coming from?</title>
		<link>http://stut.net/blog/2008/06/08/where-are-these-backslashes-coming-from/</link>
		<comments>http://stut.net/blog/2008/06/08/where-are-these-backslashes-coming-from/#comments</comments>
		<pubDate>Sun, 08 Jun 2008 20:08:14 +0000</pubDate>
		<dc:creator>Stut</dc:creator>
		
		<category><![CDATA[Coding]]></category>

		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://stut.net/blog/?p=218</guid>
		<description><![CDATA[Are you seeing backslashes (\) being inserted before quotes in the data you&#8217;re using? Have you &#8220;solved&#8221; the problem using stripslashes? Do you want to know where these are coming from and how to stop it? Of course you do&#8230; read on!

What&#8217;s causing it?
There is a configuration option called magic_quotes_gpc that is, for historic reasons, [...]]]></description>
			<content:encoded><![CDATA[<p>Are you seeing backslashes (\) being inserted before quotes in the data you&#8217;re using? Have you &#8220;solved&#8221; the problem using <a href="http://php.net/stripslashes">stripslashes</a>? Do you want to know where these are coming from and how to stop it? Of course you do&#8230; read on!<br />
<span id="more-218"></span></p>
<h2>What&#8217;s causing it?</h2>
<p>There is a configuration option called <a href="http://php.net/info#ini.magic-quotes-gpc">magic_quotes_gpc</a> that is, for historic reasons, <em>on</em> by default. It&#8217;s this option that&#8217;s causing the backslashes. It effectively runs the <a href="http://php.net/addslashes">addslashes</a> function on all GET, POST and COOKIE data.</p>
<p>The reason for this is that many years ago this was the recommended way to escape incoming data before sending it to a SQL database. Having it done automatically could be seen to be useful. Personally I hate it - I&#8217;d rather know what&#8217;s happening to the data I&#8217;m dealing with and not rely on the server being configured in a certain way.</p>
<h2>How do I stop it?</h2>
<p>The simple answer is to turn magic_quotes_gpc off. Unfortunately not everyone has the luxury of being able to do that so the following chunk of code can be placed at the top of any file to check for and undo the addslashes on the GET, POST and COOKIE superglobals. This is pretty-much required to write run-anywhere PHP scripts.</p>
<pre name="code" class="php">if (get_magic_quotes_gpc()) {
  function stripslashes_array($array) {
    return  is_array($array)
           ?
            array_map('stripslashes_array', $array)
           :
            stripslashes($array);
  }  

  $_COOKIE = stripslashes_array($_COOKIE);
  $_FILES = stripslashes_array($_FILES);
  $_GET = stripslashes_array($_GET);
  $_POST = stripslashes_array($_POST);
  $_REQUEST = stripslashes_array($_REQUEST);
}</pre>
<p>Rather than placing this in every file I&#8217;d recommend putting it in a separate file that you include at the top of each file. Alternatively you could use the auto_prepend_file php.ini directive to include it for all scripts.</p>
]]></content:encoded>
			<wfw:commentRss>http://stut.net/blog/2008/06/08/where-are-these-backslashes-coming-from/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Validating domains and email addresses</title>
		<link>http://stut.net/blog/2008/06/08/validating-domains-and-email-addresses/</link>
		<comments>http://stut.net/blog/2008/06/08/validating-domains-and-email-addresses/#comments</comments>
		<pubDate>Sun, 08 Jun 2008 19:56:35 +0000</pubDate>
		<dc:creator>Stut</dc:creator>
		
		<category><![CDATA[Coding]]></category>

		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://stut.net/blog/?p=217</guid>
		<description><![CDATA[This is a very common situation. You&#8217;re taking input from the user, including their email address. You want to make sure that they&#8217;re not feeding you a load of crap, so you want to validate their email address. The best way to do this is with a regular expression, but it&#8217;s not a simple task.
Cal Henderson (of Flickr [...]]]></description>
			<content:encoded><![CDATA[<p>This is a very common situation. You&#8217;re taking input from the user, including their email address. You want to make sure that they&#8217;re not feeding you a load of crap, so you want to validate their email address. The best way to do this is with a regular expression, but it&#8217;s not a simple task.</p>
<p><a href="http://iamcal.com/">Cal Henderson</a> (of <a href="http://flickr.com/">Flickr</a> fame) wrote an excellent article a little while ago where he wrote a regular expression against the specification document that defines these things. As Cal points out, that specification is RFC822. Now this potentially has its problems because it was written in 1982 and the rules regarding valid characters in domain names have changed since then, but as far as I can tell his solution has then covered.</p>
<p>Check out his article: <a href="http://iamcal.com/publish/articles/php/parsing_email/">http://iamcal.com/publish/articles/php/parsing_email/</a></p>
<p>Hopefully Cal won&#8217;t mind if I reproduce the end result of his work here&#8230;</p>
<pre name="code" class="php">function is_valid_email_address($email)
{
$qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
$dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
$atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c'.
 	'\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
$quoted_pair = '\\x5c[\\x00-\\x7f]';
$domain_literal = "\\x5b($dtext|$quoted_pair)*\\x5d";
$quoted_string = "\\x22($qtext|$quoted_pair)*\\x22";
$domain_ref = $atom;
$sub_domain = "($domain_ref|$domain_literal)";
$word = "($atom|$quoted_string)";
$domain = "$sub_domain(\\x2e$sub_domain)*";
$local_part = "$word(\\x2e$word)*";
$addr_spec = "$local_part\\x40$domain";
return preg_match("!^$addr_spec$!", $email) ? 1 : 0;
}</pre>
<p>For a recent project I needed a function to just validate a domain name, so I extracted the relevant parts and created the following function&#8230;</p>
<pre name="code" class="php">function is_valid_domain($domainname)
{
$dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
$atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c'.
 	'\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
$quoted_pair = '\\x5c[\\x00-\\x7f]';
$domain_literal = "\\x5b($dtext|$quoted_pair)*\\x5d"; 

$domain_ref = $atom;
$sub_domain = "($domain_ref|$domain_literal)";
$domain = "$sub_domain(\\x2e$sub_domain)*";
return preg_match("/^$domain$/i", $domainname) ? true : false;
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://stut.net/blog/2008/06/08/validating-domains-and-email-addresses/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Another new design</title>
		<link>http://stut.net/blog/2008/06/02/another-new-design/</link>
		<comments>http://stut.net/blog/2008/06/02/another-new-design/#comments</comments>
		<pubDate>Mon, 02 Jun 2008 16:10:47 +0000</pubDate>
		<dc:creator>Stut</dc:creator>
		
		<category><![CDATA[Stut.net]]></category>

		<category><![CDATA[design]]></category>

		<guid isPermaLink="false">http://stut.net/blog/?p=216</guid>
		<description><![CDATA[I&#8217;m loving this one. Content is king!!
Still a few bits that need sorting layout-wise (if the comment form below is the right width then I&#8217;ve done it!) but definitely a major improvement on the old new design.
I&#8217;ll shortly be migrating the articles into blog posts. I don&#8217;t think there&#8217;s a legitimate distinction to be made, [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m loving this one. Content is king!!</p>
<p>Still a few bits that need sorting layout-wise (if the comment form below is the right width then I&#8217;ve done it!) but definitely a major improvement on <a title="Old new Stut.net design" href="http://stut.net/blog/2008/02/15/new-stutnet-design/">the old new design</a>.</p>
<p>I&#8217;ll shortly be migrating the articles into blog posts. I don&#8217;t think there&#8217;s a legitimate distinction to be made, and blog posts come bundled with comment and trackback abilities which is handy.</p>
]]></content:encoded>
			<wfw:commentRss>http://stut.net/blog/2008/06/02/another-new-design/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>
